Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a3906ebc7 | ||
|
|
5afbd3e15b | ||
|
|
229ad7c457 | ||
|
|
e75476daf5 | ||
|
|
69335521f6 | ||
|
|
ba585a6179 | ||
|
|
431da4a96d | ||
|
|
8cc150569b | ||
|
|
e5bf0c952c | ||
|
|
2089705496 | ||
|
|
2c3787d3f0 | ||
|
|
f2380057d6 | ||
|
|
fa1b95a7dd | ||
|
|
b18c571e50 | ||
|
|
cde073a274 | ||
|
|
d5799a888d | ||
|
|
88ef95cc73 | ||
|
|
95b1822a7b | ||
|
|
37c291d0fc | ||
|
|
62b614a790 | ||
|
|
3a50e3391a | ||
|
|
677146dca4 | ||
|
|
179c0196f1 | ||
|
|
013facbade | ||
|
|
0aff8f5c54 | ||
|
|
1d356df03b | ||
|
|
64863839dd | ||
|
|
bb4e77415a | ||
|
|
90201c8981 | ||
|
|
b8d3ff951c | ||
|
|
aecac47bfd | ||
|
|
c3ca3351a7 | ||
|
|
34b91ab454 | ||
|
|
14bef1b902 | ||
|
|
bd581a77e7 | ||
|
|
ac4928c471 | ||
|
|
0c0b01eb2d | ||
|
|
fbeae313b4 | ||
|
|
b14d88ab4a | ||
|
|
540ec296f7 | ||
|
|
8c173c1707 | ||
|
|
bae62cf86c | ||
|
|
ff5cc951eb | ||
|
|
9f35fb8f67 | ||
|
|
5187c4fbab | ||
|
|
14319a147a | ||
|
|
04bd2510f5 | ||
|
|
b9d8e0a2a4 | ||
|
|
d96ba8b2a5 | ||
|
|
1453faa7f3 | ||
|
|
bbdff02015 | ||
|
|
b2abdb5cff | ||
|
|
578008a1e5 | ||
|
|
a119cd145c | ||
|
|
417cca85f2 | ||
|
|
2223c4a5d0 | ||
|
|
bb2baaa98c | ||
|
|
1cfed139b8 | ||
|
|
9328fed00c | ||
|
|
93d86aadf9 | ||
|
|
ff557bd73d | ||
|
|
ade5d1fd09 | ||
|
|
736d885f6e | ||
|
|
2b62838435 | ||
|
|
04538fa31c | ||
|
|
459bb26042 | ||
|
|
d66c1fd77b | ||
|
|
0299e2aabc | ||
|
|
bfd615667f | ||
|
|
e08d76eae1 | ||
|
|
40e03d7e08 | ||
|
|
218bfaee8f | ||
|
|
b7393e7e96 | ||
|
|
f6dac2ec59 | ||
|
|
348b36941c | ||
|
|
e1ab391c9c | ||
|
|
edbad824cc | ||
|
|
a1fc48b9b5 | ||
|
|
cf7ca17aca | ||
|
|
481f14543d | ||
|
|
e9f8f344a7 | ||
|
|
e6675adda8 | ||
|
|
4763b636ef | ||
|
|
5baee02a67 | ||
|
|
edda39e0ae | ||
|
|
860115cbcc | ||
|
|
cd7a34be0c | ||
|
|
4c3501cc97 | ||
|
|
4a5ad21b27 | ||
|
|
df4cc066f2 | ||
|
|
a137a2fb8f | ||
|
|
b6d7d2b2ba | ||
|
|
950fc4a4a0 | ||
|
|
976b7f0ca8 | ||
|
|
0ffe1cf33d | ||
|
|
22811c8b0a | ||
|
|
383e8de3b5 | ||
|
|
d1ef7a095c | ||
|
|
8604f5d720 | ||
|
|
8bc5b567a9 | ||
|
|
8bac0d8407 | ||
|
|
a03c75a3e2 | ||
|
|
402f36534f | ||
|
|
cefeeec11a | ||
|
|
ede5865307 | ||
|
|
161f68ba85 | ||
|
|
a2779c487c | ||
|
|
0fae702255 | ||
|
|
c3a22f498b | ||
|
|
c296296faf | ||
|
|
90c6d84a3e | ||
|
|
807ca8c5e6 | ||
|
|
28e52ad4fa | ||
|
|
2980011a16 | ||
|
|
1d09733e42 | ||
|
|
a13e1f5c87 | ||
|
|
b1c76c2094 | ||
|
|
7d8faeed62 | ||
|
|
d5719e934a | ||
|
|
81b2a9a7f9 | ||
|
|
0f37dd0786 | ||
|
|
589297bceb | ||
|
|
dca2efc52d | ||
|
|
598f372acd | ||
|
|
979ef41aec | ||
|
|
8e389c7a7f | ||
|
|
4f9fcdc898 | ||
|
|
c5ccbec4c7 | ||
|
|
2ebe41eb86 | ||
|
|
4d688d71d3 | ||
|
|
3acaa20611 | ||
|
|
f7c6407ccb | ||
|
|
a1bdc8ca31 | ||
|
|
3d69570c00 | ||
|
|
581e7e258a | ||
|
|
724f7b0034 | ||
|
|
714a12d374 | ||
|
|
86ed8cf878 | ||
|
|
edb3839bd7 | ||
|
|
e1d1061e52 | ||
|
|
a9b336b2e7 | ||
|
|
d4162d143d | ||
|
|
2a48444234 | ||
|
|
25df70370a | ||
|
|
b52aa497eb | ||
|
|
1bd6865c40 | ||
|
|
2d47cbe494 | ||
|
|
70f3e03e58 | ||
|
|
88ad8176ee | ||
|
|
4a56b3f907 | ||
|
|
92ab929ce3 | ||
|
|
d78282bdef | ||
|
|
e949c64007 | ||
|
|
5a5bd88f27 | ||
|
|
9246e6e4fd | ||
|
|
0221e3f63a | ||
|
|
2227a2473d | ||
|
|
f5383f27c7 | ||
|
|
2df046eecf | ||
|
|
82604c90a4 | ||
|
|
b655906fb2 | ||
|
|
b513516d09 | ||
|
|
2820b57d25 | ||
|
|
c4f7e9b453 | ||
|
|
b1f60d574c | ||
|
|
4d1cb949a0 | ||
|
|
98bc86558c | ||
|
|
87209e9901 | ||
|
|
31e34572aa | ||
|
|
a2eceb7b7e | ||
|
|
36cb46c7e7 | ||
|
|
62f7239f85 | ||
|
|
34b20aa485 | ||
|
|
9fb0f94091 | ||
|
|
b6b8c482d9 | ||
|
|
4389b07f39 |
@@ -1,12 +1,9 @@
|
||||
_esy/
|
||||
_build/
|
||||
node_modules
|
||||
esy.lock/
|
||||
*.db
|
||||
*.swp
|
||||
.swp
|
||||
main
|
||||
dsn
|
||||
*.pem
|
||||
*~
|
||||
.DS_Store
|
||||
bookshelf.txt
|
||||
site.db*
|
||||
less/cache/
|
||||
*.secret
|
||||
api_key.pem
|
||||
admin_hash.pem
|
||||
create_admin_form.html
|
||||
error.log
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
FROM --platform=${BUILDPLATFORM} golang:1.18-bullseye AS build
|
||||
WORKDIR .
|
||||
ENV CGO_ENABLED=0
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /bin/rawleydotxyz .
|
||||
|
||||
FROM scratch AS bin
|
||||
COPY --from=build /bin/rawleydotxyz /
|
||||
EXPOSE 8080/tcp
|
||||
CMD ["/rawleydotxyz"]
|
||||
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
rawley.xyz
|
||||
Copyright (C) 2022 Rawley
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
rawley.xyz Copyright (C) 2022 Rawley
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,16 @@
|
||||
# [rawley.xyz](https://rawley.xyz)
|
||||
This is my personal website and blog. It is written entirely in Go and hosted on an OpenBSD machine, reverse proxied through NGiNX. It is designed to be one-hundred-percent Javascript free, and be usable by text based browsers like [lynx](https://lynx.browser.org).
|
||||
|
||||
## Contributing
|
||||
I will only accept bug fixes. This site is my own, so I will be declining any design oriented or feature requests.
|
||||
Issues are always welcome as long as they're technical, issues regarding my blog posts, etc, will be ignored. This project only compiles with go version `>=1.17`.
|
||||
|
||||
## Blog
|
||||
I try my best to make a blog post every 15 or so days. My blogs content ranges from technical to non-technical; some of it will be informative and some if it will be observatory.
|
||||
If you like my blog or have a question feel free to send me an email at rawleyfowler@gmail.com.
|
||||
|
||||
## Donate
|
||||
There is a donation section to my website, currently I only accept BTC but eventually I'll probably support Monero. Donations are cool, but I only really have that page there so I can remember my BTC wallet.
|
||||
|
||||
## Copyright
|
||||
I reserve the right to the content that is hosted on [rawley.xyz](https://rawley.xyz). This means you can't copy my blog posts. However, I do give permission to anyone to use content on my blog for any purpose, as long as I am mentioned, either by inline citation or disclaimer. This does not apply to the source code of the website. See the `LICENSE` file for more information on what your rights are regarding the source code.
|
||||
|
Before Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 736 B |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 58 KiB |
@@ -0,0 +1,43 @@
|
||||
package bootstrap
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rawleyfowler/rawleydotxyz/controllers"
|
||||
"github.com/rawleyfowler/rawleydotxyz/utils"
|
||||
)
|
||||
|
||||
// Initializes all routes from the controller package.
|
||||
// Uses the ServePage function from utils to generate static route handlers.
|
||||
func InitializeRoutes(router *gin.Engine) {
|
||||
// No route will serve the not found page.
|
||||
router.NoRoute(utils.ServePage("not_found.tmpl"))
|
||||
// Router groups for dynamic routing and dynamic pages.
|
||||
blogGroup := router.Group("/blog")
|
||||
controllers.RegisterBlogGroup(blogGroup)
|
||||
adminGroup := router.Group("/admin")
|
||||
controllers.RegisterAdminGroup(adminGroup)
|
||||
musicGroup := router.Group("/music")
|
||||
controllers.RegisterMusicGroup(musicGroup)
|
||||
// Static routes that serve the same html every time.
|
||||
router.GET("/", utils.ServePage("index.tmpl"))
|
||||
router.GET("/resume", utils.ServePage("resume.tmpl"))
|
||||
router.GET("/contact", utils.ServePage("contact.tmpl"))
|
||||
router.GET("/donate", utils.ServePage("donate.tmpl"))
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
package controllers
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rawleyfowler/rawleydotxyz/models"
|
||||
"github.com/rawleyfowler/rawleydotxyz/repos"
|
||||
"github.com/rawleyfowler/rawleydotxyz/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
api_key string = utils.LoadApiKey("api_key.pem")
|
||||
)
|
||||
|
||||
type AdminController struct {
|
||||
Repository *repos.AdminRepo
|
||||
BlogRepository *repos.BlogRepo
|
||||
ActiveLogins map[string]int64
|
||||
LoginAttemptsByIp map[string]uint
|
||||
}
|
||||
|
||||
func NewAdminController(r *repos.AdminRepo) *AdminController {
|
||||
var a AdminController
|
||||
a.Repository = r
|
||||
a.ActiveLogins = map[string]int64{}
|
||||
a.LoginAttemptsByIp = map[string]uint{}
|
||||
go utils.TimeClearMap(a.ActiveLogins, 850)
|
||||
return &a
|
||||
}
|
||||
|
||||
func RegisterAdminGroup(r *gin.RouterGroup) {
|
||||
a := NewAdminController(repos.NewAdminRepo("dsn"))
|
||||
r.Use(a.UserBannedMiddleware)
|
||||
r.GET("/", utils.ServePage("login.tmpl"))
|
||||
r.POST("/login", a.AdminLogin)
|
||||
r.GET("/post", a.AuthMiddleware, utils.ServePage("create_post.tmpl"))
|
||||
r.POST("/post", a.AuthMiddleware, a.CRUDPost)
|
||||
r.POST("/music", a.AuthMiddleware, a.CreateMusic)
|
||||
r.POST("/user", a.CreateAdmin)
|
||||
}
|
||||
|
||||
func (a *AdminController) AuthMiddleware(c *gin.Context) {
|
||||
cookie, err := c.Request.Cookie("token")
|
||||
if err != nil {
|
||||
c.HTML(http.StatusForbidden, "forbidden.tmpl", &gin.H{})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if a.ActiveLogins[cookie.Value] == 0 {
|
||||
c.HTML(http.StatusForbidden, "session_revoked.tmpl", &gin.H{})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func (a *AdminController) UserBannedMiddleware(c *gin.Context) {
|
||||
if a.IpIsBanned(c.ClientIP()) {
|
||||
c.HTML(http.StatusForbidden, "forbidden.tmpl", &gin.H{})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func (a *AdminController) IpIsBanned(ip string) bool {
|
||||
return a.LoginAttemptsByIp[ip] >= 3
|
||||
}
|
||||
|
||||
func (a *AdminController) AdminLogin(c *gin.Context) {
|
||||
err := c.Request.ParseForm()
|
||||
if err != nil {
|
||||
c.HTML(http.StatusUnauthorized, "forbidden.tmpl", models.Page{Title: " | forbidden"})
|
||||
a.LoginAttemptsByIp[c.ClientIP()]++
|
||||
return
|
||||
}
|
||||
f := c.Request.Form
|
||||
if !f.Has("username") ||
|
||||
!f.Has("password") {
|
||||
c.HTML(http.StatusNotAcceptable, "admin_success_redirect.tmpl", false)
|
||||
a.LoginAttemptsByIp[c.ClientIP()]++
|
||||
return
|
||||
}
|
||||
ad, err := a.Repository.GetAdminByCredentials(f.Get("username"), f.Get("password"))
|
||||
if err != nil ||
|
||||
ad == nil {
|
||||
c.HTML(http.StatusNotAcceptable, "admin_success_redirect.tmpl", false)
|
||||
a.LoginAttemptsByIp[c.ClientIP()]++
|
||||
return
|
||||
}
|
||||
a.ActiveLogins[ad.Token] = time.Now().UnixMilli() + (3600 * 1000)
|
||||
c.SetCookie("token", ad.Token, 3600*1000, "/", "rawley.xyz", true, true)
|
||||
c.HTML(http.StatusAccepted, "admin_success_redirect.tmpl", true)
|
||||
}
|
||||
|
||||
func (a *AdminController) CRUDPost(c *gin.Context) {
|
||||
err := c.Request.ParseForm()
|
||||
if err != nil {
|
||||
c.HTML(http.StatusNotAcceptable, "post_success.tmpl", false)
|
||||
return
|
||||
}
|
||||
f := c.Request.Form
|
||||
if f.Get("url") == "" ||
|
||||
f.Get("op") == "" {
|
||||
c.HTML(http.StatusNotAcceptable, "post_success.tmpl", false)
|
||||
return
|
||||
}
|
||||
tempBlogRepo := repos.NewBlogRepo("dsn")
|
||||
tempPost := &models.BlogPost{
|
||||
Url: f.Get("url"),
|
||||
Content: f.Get("content"),
|
||||
Title: f.Get("title"),
|
||||
}
|
||||
switch f.Get("op") {
|
||||
case "create":
|
||||
err = tempBlogRepo.CreateBlogPost(tempPost)
|
||||
break
|
||||
case "update":
|
||||
err = tempBlogRepo.UpdateExistingPost(tempPost)
|
||||
break
|
||||
case "delete":
|
||||
err = tempBlogRepo.DeleteExistingPost(tempPost)
|
||||
break
|
||||
default:
|
||||
c.HTML(http.StatusNotAcceptable, "post_success.tmpl", false)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.HTML(http.StatusNotAcceptable, "post_success.tmpl", false)
|
||||
return
|
||||
}
|
||||
c.HTML(http.StatusAccepted, "post_success.tmpl", true)
|
||||
}
|
||||
|
||||
func (a *AdminController) CreateMusic(c *gin.Context) {
|
||||
err := c.Request.ParseForm()
|
||||
if err != nil {
|
||||
c.HTML(http.StatusNotAcceptable, "post_success.tmpl", false)
|
||||
return
|
||||
}
|
||||
f := c.Request.Form
|
||||
if f.Get("url") == "" ||
|
||||
f.Get("name") == "" {
|
||||
c.HTML(http.StatusNotAcceptable, "post_success.tmpl", false)
|
||||
return
|
||||
}
|
||||
tempMusicRepo := repos.NewMusicRepo("dsn")
|
||||
tempMusic := &models.Music{
|
||||
Url: f.Get("url"),
|
||||
Name: f.Get("name"),
|
||||
}
|
||||
err = tempMusicRepo.CreateMusic(tempMusic)
|
||||
if err != nil {
|
||||
c.HTML(http.StatusNotAcceptable, "post_success.tmpl", false)
|
||||
return
|
||||
}
|
||||
c.HTML(http.StatusAccepted, "post_success.tmpl", true)
|
||||
}
|
||||
|
||||
func (a *AdminController) CreateAdmin(c *gin.Context) {
|
||||
err := c.Request.ParseForm()
|
||||
if err != nil {
|
||||
c.HTML(http.StatusNotAcceptable, "forbidden.tmpl", &gin.H{})
|
||||
return
|
||||
}
|
||||
var at models.Administrator
|
||||
f := c.Request.Form
|
||||
if !f.Has("password") ||
|
||||
!f.Has("username") ||
|
||||
!f.Has("api_key") {
|
||||
c.HTML(http.StatusBadRequest, "forbidden.tmpl", &gin.H{})
|
||||
return
|
||||
}
|
||||
at.Username = f.Get("username")
|
||||
at.Password = f.Get("password")
|
||||
if f.Get("api_key") != api_key {
|
||||
c.HTML(http.StatusForbidden, "forbidden.tmpl", &gin.H{})
|
||||
return
|
||||
}
|
||||
err = a.Repository.CreateAdmin(&at)
|
||||
if err != nil {
|
||||
c.HTML(http.StatusForbidden, "forbidden.tmpl", &gin.H{})
|
||||
return
|
||||
}
|
||||
c.HTML(http.StatusAccepted, "post_success.tmpl", true)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package controllers
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
@@ -0,0 +1,83 @@
|
||||
package controllers
|
||||
|
||||
/* Copyright (C) 2022 Rawley Fowler
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rawleyfowler/rawleydotxyz/repos"
|
||||
)
|
||||
|
||||
type BlogController struct {
|
||||
Repository *repos.BlogRepo
|
||||
}
|
||||
|
||||
func NewBlogController(r *repos.BlogRepo) *BlogController {
|
||||
c := new(BlogController)
|
||||
c.Repository = r
|
||||
return c
|
||||
}
|
||||
|
||||
func RegisterBlogGroup(r *gin.RouterGroup) {
|
||||
c := NewBlogController(repos.NewBlogRepo("dsn"))
|
||||
r.GET("/", c.IndexBlogPage)
|
||||
r.GET("/post/:url", c.IndividualBlogPage)
|
||||
// RSS Feed
|
||||
r.GET("/feed", c.RSSFeed)
|
||||
}
|
||||
|
||||
func (bc *BlogController) IndexBlogPage(c *gin.Context) {
|
||||
posts, err := bc.Repository.GetAllBlogPosts()
|
||||
if err != nil {
|
||||
c.HTML(http.StatusInternalServerError, "internal_server_error.tmpl", &gin.H{})
|
||||
return
|
||||
}
|
||||
c.HTML(http.StatusOK, "blog.tmpl", *posts)
|
||||
}
|
||||
|
||||
func (bc *BlogController) IndividualBlogPage(c *gin.Context) {
|
||||
// Using the repository, get the plug that correlates to the url
|
||||
post, err := bc.Repository.GetBlogByUrl(c.Param("url"))
|
||||
if err != nil {
|
||||
c.HTML(http.StatusNotFound, "not_found.tmpl", &gin.H{})
|
||||
return
|
||||
}
|
||||
c.HTML(http.StatusOK, "blog_post.tmpl", post)
|
||||
}
|
||||
|
||||
func (bc *BlogController) RSSFeed(c *gin.Context) {
|
||||
posts, err := bc.Repository.GetAllBlogPosts()
|
||||
if err != nil {
|
||||
c.HTML(http.StatusInternalServerError, "internal_server_error.tmpl", &gin.H{})
|
||||
return
|
||||
}
|
||||
t, err := template.ParseFiles("templates/rss.tmpl")
|
||||
if err != nil {
|
||||
c.HTML(http.StatusInternalServerError, "internal_server_error", &gin.H{})
|
||||
return
|
||||
}
|
||||
var rawXml bytes.Buffer
|
||||
err = t.Execute(&rawXml, posts)
|
||||
if err != nil {
|
||||
c.HTML(http.StatusInternalServerError, "internal_server_error", &gin.H{})
|
||||
return
|
||||
}
|
||||
c.Data(http.StatusOK, "text/xml", []byte(strings.Replace(rawXml.String(), "&", "&", -1)))
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package controllers
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
@@ -0,0 +1,49 @@
|
||||
package controllers
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rawleyfowler/rawleydotxyz/repos"
|
||||
)
|
||||
|
||||
type MusicController struct {
|
||||
Repository *repos.MusicRepo
|
||||
}
|
||||
|
||||
func NewMusicController(r *repos.MusicRepo) *MusicController {
|
||||
c := new(MusicController)
|
||||
c.Repository = r
|
||||
return c
|
||||
}
|
||||
|
||||
func RegisterMusicGroup(r *gin.RouterGroup) {
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "wip.tmpl", &gin.H{})
|
||||
})
|
||||
}
|
||||
|
||||
func (mc *MusicController) IndexMusicPage(c *gin.Context) {
|
||||
music, err := mc.Repository.GetAllSongs()
|
||||
if err != nil {
|
||||
c.HTML(http.StatusInternalServerError, "internal_server_error.tmpl", &gin.H{})
|
||||
return
|
||||
}
|
||||
c.HTML(http.StatusOK, "tunes.tmpl", music)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
module github.com/rawleyfowler/rawleydotxyz
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.7.7
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
gorm.io/driver/mysql v1.2.3
|
||||
gorm.io/gorm v1.22.5
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
|
||||
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gorm.io/driver/mysql v1.2.3 h1:cZqzlOfg5Kf1VIdLC1D9hT6Cy9BgxhExLj/2tIgUe7Y=
|
||||
gorm.io/driver/mysql v1.2.3/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo=
|
||||
gorm.io/gorm v1.22.4/go.mod h1:1aeVC+pe9ZmvKZban/gW4QPra7PRoTEssyc922qCAkk=
|
||||
gorm.io/gorm v1.22.5 h1:lYREBgc02Be/5lSCTuysZZDb6ffL2qrat6fg9CFbvXU=
|
||||
gorm.io/gorm v1.22.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||
@@ -1,22 +0,0 @@
|
||||
:root {
|
||||
--fg: #222;
|
||||
--link-fg: #116630;
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--fg);
|
||||
font-family: georgia, serif;
|
||||
font-size: medium;
|
||||
max-width: 40em;
|
||||
margin: 0 auto;
|
||||
padding: 0 1em;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link-fg);
|
||||
text-decoration: none;
|
||||
text-decoration-line: underline;
|
||||
text-decoration-style: dashed;
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>rawley.xyz</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="description" value="Rawley Fowler's personal website, about Perl, Emacs and Software in General.">
|
||||
<meta name="keywords" value="Rawley Fowler rawley.xyz emacs netbsd functional clojure perl software architecture bugs fixing maker">
|
||||
<link rel="stylesheet" href="index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="blerb">
|
||||
<p>
|
||||
Hi, I'm Rawley. I'm a full-time Husband, Dad, Emacs evangelist and Perl aficionado.
|
||||
</p>
|
||||
<p>
|
||||
I am the creator of many <a href="https://www.gnu.org/philosophy/free-sw.en.html">free-software</a> libraries and tools.
|
||||
I have extensive knowledge of Perl, Java, JVM, and Functional Programming. I'm always working on something, whether it be my
|
||||
100 year old house, or some 100 year old code.
|
||||
</p>
|
||||
<p>
|
||||
I'm always looking for interesting things to work on.
|
||||
If you have a project you think might suite me, send me an email at <strong>rawley</strong> at <strong>molluscsoftware.com</strong>
|
||||
</p>
|
||||
<h2>Blog Posts</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/posts/html-composer.html">Perl HTML Templating and HTML::Composer</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/posts/perl-in-2025.html">Perl in 2025</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/posts/niche-software.html">Using a niche programming language</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/posts/dancer2-controllers.html">Dancer2::Controllers and Perl Attributes</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/posts/perl-oo-action-benchmark.html">Perl OO benchmarks</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/posts/maybe-escaping-evil.html">Exploring <code>evil-mode</code> alternatives</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/posts/in-defense-of-global-variables.html">How to Properly use Global Variables</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/posts/2024.html">2024</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/posts/jpa-hibernate-stupid-performance.html">Making JPA Repository stupid fast with Entity Graph</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/posts/docker-mod-perl.html">Running Mojolicious with <code>mod_perl</code> in Docker</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/posts/emacs.html">Emacs</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/posts/become-an-engineer.html">The real software path</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Noteworthy Projects</h2>
|
||||
<div class="projects">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://github.com/mollusc-labs/slapbird">SlapbirdAPM</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/rawleyfowler/Humming-Bird">Humming-Bird</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/rawleyfowler/mojolicious-Plugin-IsBot">Mojolicious::Plugin::IsBot</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="cards">
|
||||
<a href="http://www.gnu.org/software/emacs">
|
||||
<img src="/assets/gnu_emacs.png" width="88" height="31"/>
|
||||
</a>
|
||||
<a href="http://www.gnu.org/software/emacs">
|
||||
<img src="/assets/made_with_emacs.png" width="88" height="31"/>
|
||||
</a>
|
||||
<a href="https://netbsd.org">
|
||||
<img src="/assets/powered_by_netbsd.png" width="88" height="31"/>
|
||||
</a>
|
||||
<a href="#">
|
||||
<img src="/assets/no_bad_hair_days.png" width="88" height="31"/>
|
||||
</a>
|
||||
</div>
|
||||
<h2>Disclaimer</h2>
|
||||
<p>All opinions are my own, they do not reflect the thoughts expressed by my employer(s) past, present, or future.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rawleyfowler/rawleydotxyz/bootstrap"
|
||||
)
|
||||
|
||||
var router *gin.Engine
|
||||
var dsn string
|
||||
|
||||
func main() {
|
||||
router = gin.Default()
|
||||
router.Use(gin.Recovery())
|
||||
// Create function map for templates that will unescape HTML from the database
|
||||
// Credits to: https://h1z3y3.me/posts/go-html-template-script-unescape
|
||||
funcMap := make(template.FuncMap)
|
||||
funcMap["UnescapeHTML"] = func(s string) template.HTML {
|
||||
return template.HTML(s)
|
||||
}
|
||||
router.SetFuncMap(funcMap)
|
||||
// This needs to change because of RCCTL in OpenBSD, not sure if you can use a ksh variable as path in Gin but i'll test
|
||||
router.LoadHTMLGlob("templates/*.tmpl")
|
||||
router.TrustedPlatform = "X-Real-Ip"
|
||||
bootstrap.InitializeRoutes(router)
|
||||
router.Run(":8080")
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
# This script is to be run by your init service, such as rc.d
|
||||
# WIP: Do not use this at the moment.
|
||||
if [ -e "main" ]; then
|
||||
./rawleydotxyz >> /var/log/rawleydotxyz 2>&1
|
||||
else
|
||||
go build "$1/main.go"
|
||||
./rawleydotxyz >> /var/log/rawleydotxyz 2>&1
|
||||
fi
|
||||
@@ -0,0 +1,11 @@
|
||||
package models
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type Administrator struct {
|
||||
gorm.Model
|
||||
Id uint `json:"id" gorm:"primaryKey AUTO_INCREMENT"`
|
||||
Token string `json:"token" gorm:"unique"`
|
||||
Username string `gorm:"unique"`
|
||||
Password string
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package models
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type BlogPost struct {
|
||||
gorm.Model
|
||||
Url string `json:"url" gorm:"primaryKey unique"`
|
||||
Title string `json:"title" gorm:"unique"`
|
||||
Date string `json:"date" gorm:"default:NOW()"`
|
||||
Content string `json:"content"`
|
||||
Captcha [2]int `gorm:"-"`
|
||||
}
|
||||
|
||||
func (b BlogPost) Equals(m *BlogPost) bool {
|
||||
return b.Url == m.Url
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package models
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
type Music struct {
|
||||
gorm.Model
|
||||
Name string `json:"name" gorm:"primaryKey unique"`
|
||||
Url string `json:"url" gorm:"not null"`
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package models
|
||||
|
||||
/* Copyright (C) 2022 Rawley Fowler
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
type Page struct {
|
||||
Title string
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Carp qw(croak);
|
||||
use HTML::Composer;
|
||||
use File::Slurp;
|
||||
|
||||
my $post_name = $ARGV[0];
|
||||
if ( !$post_name ) {
|
||||
croak "No post named provided!";
|
||||
}
|
||||
|
||||
my $h = HTML::Composer->new;
|
||||
|
||||
my $post_template = [
|
||||
head => [
|
||||
title => ["An awesome new post!"],
|
||||
meta => { name => "description", content => "Description of post" },
|
||||
meta => { name => "robots" },
|
||||
meta => { name => "keywords", content => "Awesome keywords" },
|
||||
meta => { name => "charset", content => "utf-8", charset => "utf-8" },
|
||||
link => { href => "/index.css", rel => "stylesheet" }
|
||||
],
|
||||
body => [
|
||||
a => { href => "/" } => ["<< back"],
|
||||
h1 => ["Post Title"],
|
||||
p => ["Blah blah blah"],
|
||||
h2 => ["Sub title"],
|
||||
p => ["Blah blah blah"],
|
||||
h4 => ["Related links:"],
|
||||
ul => [
|
||||
li => [
|
||||
a => { href => "https://rawley.xyz/related-content" } =>
|
||||
["Related content"]
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
my $html = $h->html($post_template);
|
||||
|
||||
print "Writing $post_name.html\n";
|
||||
|
||||
write_file( "posts/$post_name.html", $html );
|
||||
|
||||
print "DONE\n";
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use 5.036;
|
||||
|
||||
use FindBin;
|
||||
use File::Find;
|
||||
use File::Slurp;
|
||||
use XML::LibXML;
|
||||
|
||||
find(
|
||||
sub {
|
||||
return unless $_ =~ /.*.html$/xmi;
|
||||
say "Processing: $_";
|
||||
my $loc = "$FindBin::Bin/posts/$_";
|
||||
my $html = read_file($loc);
|
||||
my $doc = XML::LibXML->new->parse_html_string($html);
|
||||
|
||||
my ($html_tag) = $doc->getElementsByTagName('html');
|
||||
$html_tag->setAttribute( lang => 'en' );
|
||||
|
||||
my @meta_tags = $doc->getElementsByTagName('meta');
|
||||
|
||||
for my $meta (@meta_tags) {
|
||||
if ( my $v = $meta->getAttribute('value') ) {
|
||||
$meta->removeAttribute('value');
|
||||
$meta->setAttribute( content => $v );
|
||||
}
|
||||
}
|
||||
|
||||
my @links = $doc->getElementsByTagName('link');
|
||||
|
||||
for my $link (@links) {
|
||||
if ( $link->getAttribute('href') eq '/index.css' ) {
|
||||
say "Already done $_";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
my $st = XML::LibXML::Element->new('link');
|
||||
$st->setAttribute( rel => 'stylesheet' );
|
||||
$st->setAttribute( href => '/index.css' );
|
||||
my ($head) = $doc->getElementsByTagName('head');
|
||||
$head->appendChild($st);
|
||||
write_file( $loc, $doc->toString() );
|
||||
},
|
||||
"$FindBin::Bin/posts"
|
||||
);
|
||||
@@ -1,59 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>2024</title>
|
||||
<meta name="description" content="Plans for 2024" />
|
||||
<link rel="stylesheet" href="/index.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a href="/"><< back</a>
|
||||
<h1>2024</h1>
|
||||
<p>
|
||||
2023 was a good year, but now it's 2024. Here's some of my technical plans for 2024.
|
||||
</p>
|
||||
<p>
|
||||
<b>NOTE</b>: These are in no particular order.
|
||||
</p>
|
||||
<h2>Technology I plan to use in 2024</h2>
|
||||
<ul>
|
||||
<li>C++23</li>
|
||||
<li>Perl 5.38 (Corrina!)</li>
|
||||
<li>Raku</li>
|
||||
<li>OCaml</li>
|
||||
<li>Go</li>
|
||||
<li>HTMX</li>
|
||||
<li>Mojolicious</li>
|
||||
<li>PostgreSQL</li>
|
||||
<li>Memcached</li>
|
||||
</ul>
|
||||
<h2>Technology I want to learn more about in 2024</h2>
|
||||
<ul>
|
||||
<li>C++23</li>
|
||||
<li>Clojure</li>
|
||||
<li>Lua</li>
|
||||
<li>FreeBSD</li>
|
||||
<li>Caddy</li>
|
||||
</ul>
|
||||
<h2>Technology I might learn in 2024</h2>
|
||||
<ul>
|
||||
<li>Common Lisp</li>
|
||||
<li>Elixir</li>
|
||||
<li>DragonflyBSD</li>
|
||||
<li>FirebirdSQL</li>
|
||||
<li>Nim</li>
|
||||
</ul>
|
||||
<h2>Technology I won't learn in 2024</h2>
|
||||
<p>
|
||||
It's not that I hate the following, it's just that they don't really interest me,
|
||||
or the people behind them do not seem to value people using them (ala Rust).
|
||||
</p>
|
||||
<ul>
|
||||
<li>Rust</li>
|
||||
<li>Next.JS</li>
|
||||
<li>Zig</li>
|
||||
<li>Kubernetes</li>
|
||||
<li>Terraform</li>
|
||||
</ul>
|
||||
<p>Have a safe and fun 2024.</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,81 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Become a software engineer</title>
|
||||
<meta name="description" value="How to become a software engineer. The real way.">
|
||||
<link rel="stylesheet" href="/index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a href="/"><< back</a>
|
||||
<h1>Become an Engineer</h1>
|
||||
<p>
|
||||
This learning track assumes you have a computer built after 2004, with
|
||||
<a href="https://debian.org">Debian Linux</a>, <a href="https://freebsd.org">FreeBSD</a> or <a href="https://openbsd.org">OpenBSD</a> installed. (Windows simply won't do.)
|
||||
</p>
|
||||
<h2>Start here (read both and do all of the exercises)</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://mitp-content-server.mit.edu/books/content/sectbyfn/books_pres_0/6515/sicp.zip/index.html">Structure and Interpretation of Computer Programs by Sussman et. al</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://en.wikipedia.org/wiki/The_C_Programming_Language">The C Programming Language by K&R</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Getting deeper (read all 3, do all exercises)</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://algs4.cs.princeton.edu/home/">Algorithms by Robert Sedgewick</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://en.wikipedia.org/wiki/Types_and_Programming_Languages">Types and Programming Languages by Benjamin C. Pierce</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.macs.hw.ac.uk/~greg/books/gjm.lambook88.pdf">An Introduction to Functional Programming Through Lambda Calculus by Greg Michaelson</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Low level learning</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://mitpress.mit.edu/9780262640688/the-elements-of-computing-systems/">The Elements of Computing Systems by Noam Nisan and Shimon Schocken</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Category theory</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://bartoszmilewski.com/2014/10/28/category-theory-for-programmers-the-preface/">Category Theory for Programmers by Bartosz Milewski</a>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>High(er) Level Languages (pick at least 2)</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://www.oreilly.com/library/view/programming-perl-4th/9781449321451/">Programming Perl by Christiansen, foy, and Orwant</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://learnyousomeerlang.com/">Learn You Some Erlang for Great Good by Fred Hebert</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://learnyouahaskell.com/">Learn You a Haskell for Great Good by Miran Lipovaca</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.stroustrup.com/tour2.html">A Tour of C++ by Stroustrup</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://www.cs.cmu.edu/~rwh/isml/book.pdf">Programming in Standard ML by Robert Harper</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.oreilly.com/library/view/the-ruby-programming/9780596516178/">The Ruby Programming Language by Flanagan & Matz</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
This is the way to becoming a truly great software engineer. I've read all of these books, and I can say that they all manifest within
|
||||
me a strong sense of confidence, and the ability to adapt to any situation that might come my way in computer science.
|
||||
</p>
|
||||
<p>
|
||||
You may be reading this list, thinking... what about Java or JavaScript??? Now, I don't think these languages are bad per-se, but I do think
|
||||
they have the ability to destroy your life. Once you've used Java or JavaScript, there is a good chance you will become closed off from
|
||||
the rest of the computing world, falling into a world of patterns and objects. You may also find that languages like these have the adverse
|
||||
effect of having incredibly low skill-ceilings, which compounds the idea of you becoming stuck. A low ceiling, and a low floor, is a prison, not
|
||||
a tool to conjure the spirits of the computer.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,72 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Dancer2::Controllers</title>
|
||||
<meta name="description" value="How to use Dancer2::Controllers for Perl's webframework, Dancer2."/>
|
||||
<meta name="keywords" value="Perl, Perl5, Dancer, Dancer2, Controllers, MVC, Spring-Boot"/>
|
||||
<link rel="stylesheet" href="/index.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a href="/"><< Back</a>
|
||||
<h1>Dancer2::Controllers and Perl Attributes</h1>
|
||||
<p>Published February 26th, 2024.</p>
|
||||
<p>
|
||||
Recently I created <a href="https://metacpan.org/pod/Dancer2::Controllers"><code>Dancer2::Controllers</code></a>, a simple
|
||||
<code>Moose</code> based approach for defining routes in Dancer2. I built this as an attempt to add Spring-Boot esq annotations to
|
||||
Dancer2.
|
||||
</p>
|
||||
<h2>The approach</h2>
|
||||
<p>
|
||||
Perl has a little know feature called "Attributes", which work as some extra arbitrary data appended to
|
||||
the various reftypes in Perl. When you append an Attribute, Perl looks for the <code>MODIFY_<reftype>_ATTRIBUTES</code> in the current
|
||||
namespace, and uses it to decide if that attribute is allowed, and lets you execute some arbitrary code as well. Perl attributes can also take "arguments".
|
||||
These "arguments" actually are just part of the Attribute, and you need to parse the entire Attribute to get the arguments.
|
||||
</p>
|
||||
<p>
|
||||
Since Perl Attributes are the closest thing I could find to annotations, I decided a generic <code>Route</code> attribute would be nice.
|
||||
With a Perly looking argument pattern: <code>Route(get => /foo)</code>, this feels a Perl version of Spring-Boot's
|
||||
<code>@RequestMapping(value = "/foo", method = GET)</code>,
|
||||
which is exactly what I was looking for.
|
||||
</p>
|
||||
<h2>Speed Bumps</h2>
|
||||
<p>
|
||||
Dancer2 is built on-top of <code>Moo</code>, unfortunately I couldn't find a nice way to handle Attributes using Moo, and inheritance. So, I settled for adding
|
||||
<code>Moose</code> and the wonderfully helpful <code>MooseX::MethodAttributes</code> dist, which drastically simplified the work I needed to do.
|
||||
</p>
|
||||
<h2>In action</h2>
|
||||
<p>The following is an example of how to use <code>Dancer2::Controllers</code></p>:
|
||||
<pre>
|
||||
<code>
|
||||
package My::Controller;
|
||||
|
||||
use Moose;
|
||||
|
||||
BEGIN { extends 'Dancer2::Controllers::Controller' }
|
||||
|
||||
sub home_page : Route(get => /home) {
|
||||
my $app = shift;
|
||||
my $user_name = $app->session('user');
|
||||
return "Welcome back $user_name!";
|
||||
}
|
||||
|
||||
sub home_page : Route(post => /login) {
|
||||
my $app = shift;
|
||||
my $password = $app->body_parameters->get('password');
|
||||
return "Here is your password: $password";
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
package main;
|
||||
|
||||
use Dancer2;
|
||||
use Dancer2::Controllers;
|
||||
|
||||
controllers([ 'My::Controller' ]);
|
||||
|
||||
dance;
|
||||
</code>
|
||||
</pre>
|
||||
<p>Thanks for reading :)</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,145 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Running mod_perl with Mojolicious in Docker</title>
|
||||
<meta name="description" content="Running Apache mod_perl and Mojolicious in Docker" />
|
||||
<meta name="keywords" content="mojolicious mojo mod_perl docker apache" />
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="stylesheet" href="/index.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<a href="/"><< Back</a>
|
||||
<h1 id="running-mod_perl-with-mojolicious-in-docker">Running mod_perl with Mojolicious in Docker</h1>
|
||||
<p>
|
||||
I've been working under an old Apache <code>mod_perl</code> setup. I wanted to use Mojolicious for some new services,
|
||||
but I needed to stay within <code>mod_perl</code>, so I decided to go all the way and Dockerize it. After scouring the <code>mod_perl</code> documentation,
|
||||
Plack + Mojolicious configs, and finding an image, I hit the ground running and documented my steps.
|
||||
</p>
|
||||
<a href="http://perl.apache.org/">
|
||||
<img src="/assets/mod_perl.gif" alt="mod_perl banner" />
|
||||
</a>
|
||||
<p>I love old banners :)</p>
|
||||
<h3 id="generate-your-config">Generate your config</h3>
|
||||
<p>Thanks to <a href="https://github.com/motemen/docker-mod_perl">motemen</a> for some nicely pre-configured
|
||||
<code>mod_perl</code> and <code>Apache2</code> images. We're targetting <code>Perl v5.36</code>, and <code>Apache 2.4.58</code> here.
|
||||
We'll also add the include statement for our <code>VirtualHost</code> configuration file that we'll make later.
|
||||
</p>
|
||||
<pre>
|
||||
<code>docker <span class="hljs-keyword">run</span> --<span class="hljs-keyword">rm</span> motemen/mod_perl:5.36.0-2.4.58 <span class="hljs-keyword">cat</span> /usr/<span class="hljs-keyword">local</span>/apache2/<span class="hljs-keyword">conf</span>/httpd.<span class="hljs-keyword">conf</span> > httpd.<span class="hljs-keyword">conf</span>
|
||||
echo '<span class="hljs-keyword">Include</span> /usr/<span class="hljs-keyword">local</span>/apache2/<span class="hljs-keyword">conf</span>/mojolicious.<span class="hljs-keyword">conf</span>' >> httpd.<span class="hljs-keyword">conf</span>
|
||||
</code>
|
||||
</pre>
|
||||
<p>
|
||||
This will leave you with a <code>httpd.conf</code> that will get picked up by Apache when we run it under docker.
|
||||
</p>
|
||||
<h3 id="enable-prefork-mpm-instead-of-event-mpm">Enable Prefork MPM instead of Event MPM</h3>
|
||||
<p>
|
||||
I couldn't get Apache to run without disabling Event MPM and enabling Prefork MPM.
|
||||
</p>
|
||||
<p>In <code>httpd.conf</code>:</p>
|
||||
<pre>
|
||||
<code>
|
||||
<span class="hljs-selector-id">#LoadModule</span> mpm_event_module modules/mod_mpm_event<span class="hljs-selector-class">.so</span>
|
||||
LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
|
||||
</code>
|
||||
</pre>
|
||||
<h3 id="write-your-application">Write your application</h3>
|
||||
<p>
|
||||
All you need to do is write a Mojo application. In this case, I'm using <code>Mojolicious::Lite</code> but this can
|
||||
be done with the whole MVC setup easily as well. Just make sure you have a sub that returns your <code>app</code> so you can
|
||||
pass it to <code>Plack::Builder</code>.
|
||||
</p>
|
||||
<p>In <code>lib/App.pm</code>:</p>
|
||||
<pre>
|
||||
<code class="lang-perl">
|
||||
<span class="hljs-keyword">package</span> App;
|
||||
<span class="hljs-keyword">use</span> <span class="hljs-number">5.036</span>;
|
||||
<span class="hljs-keyword">use</span> Mojolicious::Lite -signatures;
|
||||
|
||||
<span class="hljs-function"><span class="hljs-keyword">sub</span> <span class="hljs-title">get_app</span> </span>{ app; }
|
||||
|
||||
get <span class="hljs-string">'/'</span> => <span class="hljs-function"><span class="hljs-keyword">sub</span> </span>{
|
||||
<span class="hljs-keyword">shift</span>->render( <span class="hljs-string">text =></span> <span class="hljs-string">"Hello World!"</span> );
|
||||
};
|
||||
|
||||
<span class="hljs-number">1</span>;
|
||||
</code></pre>
|
||||
<h3 id="write-your-plack-shim">Write your Plack shim</h3>
|
||||
<p>
|
||||
Here we write a simple <code>.psgi</code> file that
|
||||
will use our <code>App</code> library and <code>Plack::Builder</code> to
|
||||
create a <code>PSGI</code> application for <code>Plack::Handler::Apache2</code> to pickup.
|
||||
</p>
|
||||
<p>In <code>app.psgi</code>:</p>
|
||||
<pre>
|
||||
<code>use <span class="hljs-number">5.036</span>;
|
||||
|
||||
use <span class="hljs-class"><span class="hljs-keyword">lib</span> '<span class="hljs-title">lib</span>';</span>
|
||||
use App;
|
||||
use Plack::Builder;
|
||||
|
||||
builder {
|
||||
<span class="hljs-symbol">App:</span>:get_app->start;
|
||||
};
|
||||
</code>
|
||||
</pre>
|
||||
<h3 id="write-your-mod_perl-apache-conf">Write your mod_perl Apache config</h3>
|
||||
<p>
|
||||
Very straight-forward, just tell Apache where to find our script, and what handler to use.
|
||||
</p>
|
||||
<p>In <code>mojolicious.conf</code>:</p>
|
||||
<pre>
|
||||
<code>
|
||||
<span class="hljs-attribute"><span class="hljs-nomarkup">LoadModule</span></span> perl_module modules/mod_perl.so
|
||||
|
||||
<span class="hljs-section"><VirtualHost *:80></span>
|
||||
<span class="hljs-section"><Location /></span>
|
||||
<span class="hljs-attribute"><span class="hljs-nomarkup">SetHandler</span></span> perl-script
|
||||
<span class="hljs-attribute">PerlResponseHandler</span> Plack::Handler::Apache2
|
||||
<span class="hljs-attribute">PerlSetVar</span> psgi_app /app.psgi
|
||||
<span class="hljs-section"></Location></span>
|
||||
<span class="hljs-section"></VirtualHost></span>
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
<h3 id="write-your-dockerfile">Write your Dockerfile</h3>
|
||||
<p>
|
||||
Now all thats left is making a <code>Dockerfile</code>.
|
||||
We'll use one of <a href="https://github.com/motemen/docker-mod_perl">motemen</a>'s images,
|
||||
and copy all of the pre-reqs over, install some libraries, you know, Docker stuff.
|
||||
</p>
|
||||
<p>In <code>Dockerfile</code>:</p>
|
||||
<pre>
|
||||
<code class="lang-docker">
|
||||
<span class="hljs-keyword">FROM</span> motemen/mod_perl:<span class="hljs-number">5.36</span>.<span class="hljs-number">0</span>-<span class="hljs-number">2.4</span>.<span class="hljs-number">58</span>-<span class="hljs-number">2.0</span>.<span class="hljs-number">13</span>
|
||||
<span class="hljs-keyword">COPY</span><span class="bash"> app.psgi /app.psgi
|
||||
</span><span class="hljs-keyword">COPY</span><span class="bash"> lib /usr/<span class="hljs-built_in">local</span>/apache2/lib
|
||||
</span><span class="hljs-keyword">COPY</span><span class="bash"> httpd.conf /usr/<span class="hljs-built_in">local</span>/apache2/conf/httpd.conf
|
||||
</span><span class="hljs-keyword">COPY</span><span class="bash"> mojolicious.conf /usr/<span class="hljs-built_in">local</span>/apache2/conf/mojolicious.conf
|
||||
</span><span class="hljs-keyword">RUN</span><span class="bash"> apt-get update -y && apt-get install -y wget make build-essential
|
||||
</span><span class="hljs-keyword">RUN</span><span class="bash"> cpan -iT Plack Mojolicious
|
||||
</span><span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">80</span>
|
||||
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"httpd-foreground"</span>]</span>
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
<h3 id="run-with-docker">Run with Docker</h3>
|
||||
<p>Now we can make it go!</p>
|
||||
<pre>
|
||||
<code class="lang-shell"><span class="hljs-symbol">$</span> docker build . -t <span class="hljs-built-in">mod</span>-perl
|
||||
<span class="hljs-symbol">$</span> docker run -p <span class="hljs-number">80</span>:<span class="hljs-number">80</span> <span class="hljs-built-in">mod</span>-perl
|
||||
<span class="hljs-symbol">$</span> curl http:<span class="hljs-comment">//localhost:80/</span>
|
||||
|
||||
"Hello World!"
|
||||
</code>
|
||||
</pre>
|
||||
<h3 id="running-with-auto-reloading-for-development-">Running with auto-reloading (for development)</h3>
|
||||
<p>You can make this auto-reload if you use a docker volume for <code>lib/</code> against <code>/usr/local/apache2/lib/</code></p>
|
||||
<h3 id="source-code">An example?</h3>
|
||||
<p>
|
||||
An <a href="https://github.com/rawleyfowler/mojolicious-mod-perl-example">example</a> is available on my github.
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,53 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Emacs</title>
|
||||
<meta charset="UTF-8"/>
|
||||
<link href="/index.css" rel="stylesheet"/>
|
||||
</head>
|
||||
<body>
|
||||
<a href="/"><< back</a>
|
||||
<h1>Why Emacs?</h1>
|
||||
<p>
|
||||
I reccommend GNU Emacs (not Spacemacs, or Doom Emacs) to everyone. If you're just starting to program
|
||||
then you're at an even bigger advantage if you pickup Emacs early. If you're a battle hardened developer
|
||||
you should still be using Emacs, though it may be harder to pickup, assuming you've been using modern "IDE's"
|
||||
for most of your career. I also recommend Emacs to pretty much anyone that processes text often.
|
||||
</p>
|
||||
<p>
|
||||
Emacs provides the perfect balance of customization, to effectiveness of any editor. I think that it's foolish
|
||||
to use tools that don't let you mold them to <b>your</b> idea of how to use them. Software tools aren't like traditional
|
||||
tools like Hammers and Pipe-wrenches, and it's foolish to use them in this way. With Emacs a <a href="https://www.gnu.org/gnu/rms-lisp.en.html">Secretary</a>, Software Engineer (obviously),
|
||||
Math Student, Professor, <a href="https://www.masteringemacs.org/article/how-to-write-a-book-in-emacs">Novelist</a>, etc, can all find ways to implement Emacs into their day-to-day.
|
||||
Emacs is a tool for empowering <b>people</b>. It's not just a text-processor (though it's really good at that), it's also a journal,
|
||||
a lisp interpreter, and really, anything you want.
|
||||
</p>
|
||||
<h1>For Software</h1>
|
||||
<p>
|
||||
I'm going to focus on Software Developers for the rest of this, seeing as it's who I can relate too most.
|
||||
Software Developers often get stuck in ruts, I truly think Emacs is the tool to pull you out and keep you out
|
||||
of said ruts. Emacs will kick you in the rear, and make you cry, and force you to work in a subpar fashion for a long time,
|
||||
the GNU is not an easy beast to tame. But once you've put in the work, and elevated yourself upon it's reigns, the power you
|
||||
attain is so incredibly wizard-like that you may very well consider wearing a pointed hat, and robes, at work.
|
||||
</p>
|
||||
<p>
|
||||
Currently, I use Emacs for everything at work. Compiling, code editing, file-system stuff, docker, git, documentation, etc.
|
||||
I've used other editors and <b>nothing</b>, comes even close to Emacs. Vim users will probably recoil, but Vim makes you context
|
||||
switch so often between the terminal and your editor that I really think it doesn't even belong in the same category as Emacs.
|
||||
What I would recommend to the Emacs novice, is to focus on setting up your workflows 1 flow at a time. For instance, say you want
|
||||
to work on Perl code. Setup all the Perl related stuff in one-shot and don't worry about making your configuration super ergonomic
|
||||
and extensible. Just make it work for Perl. Then say, you get a job in Scala, do the same thing, but refactor your configuration
|
||||
to enable both Perl and Scala nicely, rinse and repeat until your hands bleed. That's really all there is to it, as you find things
|
||||
you want to improve, improve them one-by-one. Don't rush in and expect to setup a perfect IDE for every language and every workflow.
|
||||
Emacs really is what you make of it, but you can't really know what to make of it, until you identify the problems you want to solve.
|
||||
</p>
|
||||
<p>
|
||||
A good way to think of Emacs, is, it's the instrument equivalent of text-processors.
|
||||
</p>
|
||||
<h4>Links:</h4>
|
||||
<ul>
|
||||
<li><a href="https://github.com/rawleyfowler/emacs">My Emacs</a></li>
|
||||
<li><a href="https://emacs.amodernist.com/">Config Generator</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,32 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Fix Apache 421 SNI Errors Behind AWS ALB</title>
|
||||
<meta content="Description of post" name="description">
|
||||
<meta name="robots">
|
||||
<meta content="keywords" name="Apache SNI Fix 421">
|
||||
<meta content="description" name="How to fix the Apache SNI issues (421 misdirected) behind AWS ALB">
|
||||
<meta charset="utf-8" content="utf-8" name="charset">
|
||||
<link href="/index.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<a href="/"><< back</a>
|
||||
<h1>Fixing Apache SNI 421 Errors Behind AWS ALB</h1>
|
||||
<h2>Understanding the Problem</h2>
|
||||
<p>
|
||||
SNI (Server Name Indicator) is a flag set in the "Client Hello" of a TLS request. It tells the server what domain you want,
|
||||
without having to decrypt the request later to get the <code>Host</code> header, or whatever else you might be using to route traffic with.
|
||||
Apache uses SNI to figure out what cert to use to decrypt the request. When SNI is missing, or Apache can't find a VHost associated
|
||||
with the domain in the SNI, it will route to the default VHost for decryption and request handling. This is now the default behaviour in 2.4.64 and up,
|
||||
it was applied to fix CVE-2025-23048. Note this is a good change, and means that Apache will reject potentially dangerous requests where SNI isn't the same as the Host header.
|
||||
</p>
|
||||
<h2>Solution</h2>
|
||||
<p>
|
||||
If you're using HTTPS on your Apache, behind an AWS ALB, you're going to run into this issue, because AWS ALB's seemingly don't set SNI at all.
|
||||
The solution is to tell Apache to relax its policy, and allow the default VHost to decrypt the message,
|
||||
and then route on the Host header to the next VHost (I think this is how it works, I could be wrong).
|
||||
You can do this with the <code>SSLVHostSNIPolicy</code> directive, setting it to <code>authonly</code> will
|
||||
return the pre 2.4.64 functionality, however it supposedly only works on 2.4.65, so you may have to skip 2.4.64.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,132 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>HTML::Composer and Perl HTML templating</title>
|
||||
<meta content="Description of post" name="description">
|
||||
<meta name="robots">
|
||||
<meta content="Awesome keywords" name="keywords">
|
||||
<meta charset="utf-8" content="utf-8" name="charset">
|
||||
<link href="/index.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<a href="/"><< back</a>
|
||||
<h1>HTML::Composer and Perl HTML templating</h1>
|
||||
<div><small>June 25th, 2026</small></div>
|
||||
<p>
|
||||
Perl has some great templating libraries for HTML. Most of them are pretty fast, and do what you want.
|
||||
<a href="https://metacpan.org/pod/Template::Toolkit">Template::Toolkit</a> is sort of the "gold standard", and you see it most
|
||||
often in the wild. <a href="https://metacpan.org/pod/Mojo::Template">Mojo::Template</a> is the "newer" kid on the block, and is most
|
||||
associated with the <a href="https://metacpan.org/pod/Mojolicious">Mojolicious</a> framework/toolkit, if you're writing a Mojolicious application
|
||||
you're likely using Mojo::Template. There are also a few niche templating tools, like <a href="https://metacpan.org/pod/Text::Xslate">Text::Xslate</a> (unfortunately, pretty much dead), and
|
||||
<a href="https://metacpan.org/pod/Template::Tiny">Template::Tiny</a> (supported, but not seen very often). Like I said earlier, all of these libraries are great,
|
||||
you write templated text, and they give you HTML back, awesome. However, they all share a common "problem", they make me write HTML. I'm a Perl developer,
|
||||
I like writing Perl, I want to write Perl, so when I have to switch contexts to HTML it annoys me. To compound this problem, I just got off a project that used a tool called
|
||||
<a href="https://github.com/weavejester/hiccup">hiccup</a>, hiccup let me write code in its native toungue (Clojure), and get back HTML in the form of a string. This rocked!
|
||||
Not only did I not have to write HTML, it was actually <i>intuitive</i>, it provided me with a DSL for HTML. So I took this idea, and ported it to Perl, in the form of
|
||||
<a href="https://metacpan.org/pod/HTML::Composer">HTML::Composer</a>. HTML::Composer is a library that does its best to emulate that sort of intuitiveness, inside of Perl. To my surprise
|
||||
it seems to, and even more suprisingly, it performs incredibly well.
|
||||
</p>
|
||||
<h2>HTML::Composer</h2>
|
||||
<p>
|
||||
HTML::Composer only outputs HTML. It is a Perl data-structure to HTML converter, it takes an ARRAY ref, and outputs a string:
|
||||
</p>
|
||||
<pre><code class="language-perl">
|
||||
use HTML::Composer;
|
||||
|
||||
my $h = HTML::Composer->new();
|
||||
my $html = $h->html([
|
||||
head => [
|
||||
title => ["My Site"],
|
||||
script => {
|
||||
src => "/js/myScript.js",
|
||||
type => "text/javascript"
|
||||
}
|
||||
],
|
||||
body => [
|
||||
h1 => ["Hello World!"],
|
||||
br => {},
|
||||
div => { class => [ "p-3", "background-red" ] } => [
|
||||
"Hello World!", h2 => ["Test 123"]
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
say $html; # <!DOCTYPE html><html> ...
|
||||
</code></pre>
|
||||
<p>
|
||||
You can also render partial HTML:
|
||||
</p>
|
||||
<pre><code class="language-perl">
|
||||
my $h = HTML::Composer->new;
|
||||
my $html = $h->partial([
|
||||
div => [
|
||||
"Hello, World!",
|
||||
a => { href => "https://www.google.com" } => ["www.google.com"]
|
||||
]
|
||||
]);
|
||||
|
||||
say $html; # <div>Hello, World!<a href="https://www.google.com"< ...
|
||||
</code></pre>
|
||||
<p>
|
||||
Since HTML::Composer operates inside of Perl data structures, and doesn't need to do any string parsing, it's quite fast. Faster than any other templating library outputting HTML I could find.
|
||||
The following benchmark is the average of five different one-hundred-million iteration runs rendering a medium sized HTML page that changed between iterations,
|
||||
with the caveat being, each of these libraries was expected to take an input scalar and output to a scalar variable (Text::Xslate is optimized to read from a file not a string, so it hated this benchmark):
|
||||
</p>
|
||||
<pre><code>
|
||||
Rate Text::Xslate Mojo::Template Template::Tiny Template::Toolkit HTML::Composer
|
||||
Text::Xslate 433/s -- -78% -93% -93% -94%
|
||||
Mojo::Template 1969/s 354% -- -69% -70% -72%
|
||||
Template::Tiny 6410/s 1379% 226% -- -1% -9%
|
||||
Template::Toolkit 6494/s 1398% 230% 1% -- -8%
|
||||
HTML::Composer 7042/s 1525% 258% 10% 8% --
|
||||
</code></pre>
|
||||
<p>
|
||||
And, while being very quick, it also performs basic HTML validations to ensure you're sending something proper (it does not make sure the tags you pass are correct, this is very expensive).
|
||||
Instead of spending time parsing and building the HTML tree, the developer has already done the work, so all we need to do is <i>compose</i> it to HTML. It also performs the necessary escapes by default, if you need to avoid escaping,
|
||||
you can use the unsafe method <pre><code class="language-perl">[ div => $h->unsafe("Stuff that shouldn't be escaped!"); ]</code></pre>
|
||||
</p>
|
||||
<h3>How it works</h3>
|
||||
<p>
|
||||
HTML::Composer uses a depth-first search, with a stack to traverse the HTML array you provide,
|
||||
based on the ordering of the data it makes assumptions about what you want, a string followed by a hash means you want to
|
||||
write a tag with some attributes, if that is then followed by an array, a tag with attributes and children. We verify if the tag
|
||||
is allowed to have children, then, we insert a reference to the current tag's scalar after the hash, and array. If a tag has no children, ie a tag with just
|
||||
a hash following, then we can finish that tag right now by pushing the HTML string output to the output array. When we encounter an array,
|
||||
simply push all of its elements onto the stack. Rinse and repeat until we encounter a scalar reference, and close the tag.
|
||||
</p>
|
||||
<h3>Optimizations + learnings</h3>
|
||||
<p>
|
||||
I learned Perl string concatenation is really slow. In the first pass at this project I used a <pre><code class="language-perl">$root</code></pre> scalar to be the final output,
|
||||
and concatenated the HTML in place, however after doing some r&d, I realised that pushing to an array, and then using Perl's <pre><code class="language-perl">join('', @array)</code></pre>
|
||||
function, was almost one order of magnitude faster. The reason this is faster, is Perl's <a href="https://github.com/Perl/perl5/blob/909c414e5450fe64ba145e6a3f3ed2e97999f060/doop.c#L666">do_join</a> function
|
||||
is highly optimized, and seems to allocate the size of string it thinks it needs ahead of time, which saves a lot of CPU time.
|
||||
</p>
|
||||
<p>
|
||||
The main logic tree uses ref and string eq's, I attempted to swap this to using regexp with a prefix <pre><code class="language-perl">ref($foo) =~ /^SC/ # SCALAR</code></pre>, this is typically how I've optimized
|
||||
logic in C code in the past, though, instead of regexp its referencing the bytes directly, which in retrospect is obviously going to be much faster than this.
|
||||
However I did expect the prefix check with regexp to be at least slightly faster, but alas it's quite a bit slower.
|
||||
</p>
|
||||
<h2>Conclusion</h2>
|
||||
<p>
|
||||
Perl has great HTML templating libraries. I hate writing HTML so I wrote one that lets me stay in Perl, and export templates via sub-routines.
|
||||
It performs really well, which is to be expected, as it does no parsing of any actual text. I hope someone else with similar pains will try it out at some point.
|
||||
Thanks for reading!
|
||||
</p>
|
||||
<h4>Related links:</h4>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://codeberg.org/rawleyfowler/perl-HTML-Composer">HTML::Composer source code</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://metacpan.org/pod/HTML::Composer">HTML::Composer</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
Footnote: yes your eyes do not decieve you, that is codeberg, I'm going to be migrating my work off Github over the next couple weeks.
|
||||
AI was not involved in this project at all.
|
||||
</p>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
|
||||
<script>hljs.highlightAll();</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>2024</title>
|
||||
<meta name="description" content="Plans for 2024" />
|
||||
<link rel="stylesheet" href="/index.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a href="/"><< back</a>
|
||||
<h1>Perl and HTTP/2</h1>
|
||||
<p>
|
||||
Perl is a great language for building applications that interface over HTTP. It has some of the best tooling for
|
||||
database access, sending/receiving HTTP requests:
|
||||
<a href="https://metacpan.org/pod/LWP">LWP</a>, <a href="https://metacpan.org/pod/Plack">Plack</a> and <a href="https://metacpan.org/pod/Mojolicious">Mojolicious</a>.
|
||||
The only glaring weakness to a Perl application as it stands, is typically, Perl servers only support HTTP/1.1. 1.1 is a "fine"
|
||||
protocol, especially for its time. However, in 2026, it's starting to show it's age. HTTP/2 isn't just "faster", it's much more efficient with your server's resources.
|
||||
It uses a single TCP socket also called a "stream" for client's to use, instead of opening multiple sockets per page load.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,102 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>How to Properly Use Global Variables</title>
|
||||
<meta name="description" value="In defense of global variables. Why globals should be considered over more complex solutions. And how to use them!"/>
|
||||
<link rel="stylesheet" href="/index.css"/></head>
|
||||
<body>
|
||||
<a href="/"><< back</a>
|
||||
<h1>How to Properly Use Global Variables</h1>
|
||||
<p>
|
||||
Global variables, and global state in general have been under fire for as long as I can remember.
|
||||
The argument is, you couple, and introduce unpredictability through managing global variables
|
||||
since "anyone can update them". This makes sense in a cosmological world, filled with people
|
||||
who WANT to write bad code. It's not the global variables that make code bad, it's how they're used.
|
||||
We've spent many years inventing abstraction after abstraction, that could just be boiled down to global variables,
|
||||
and global constants. In the end, applications will always have global state, why make it so dang difficult?
|
||||
</p>
|
||||
<h2>Simplicity, and avoiding traps</h2>
|
||||
<p>
|
||||
Globals are simple, so simple, that even the thought of using them might turn people off. It's hard to accept
|
||||
that the simplest answer is always the best one, but it almost always is. 99% of the code I run into, is complex for the sake of "reusability", "readability", or
|
||||
"scalability", but in the end none of these come to pass. You'll see interfaces, and abstract classes defined for single implementations,
|
||||
horribly generic code written in obscure places, and a mess of a file structure with hundreds of files. Do you really need a generic interface for
|
||||
each of your database tables, or what about a "service" for each "domain"? In reality, the most readable, resuable, and scalable code, is the code that
|
||||
compiles well, and avoids mentally expensive abstraction. If I have to walk through the inheritence graph to figure out what something is doing, it's already neither
|
||||
scalable, or readable.
|
||||
</p>
|
||||
<p>
|
||||
You'll often see global variables hidden behind some "global context" like abstraction. This is down right deplorable, instead, hide your globals behind the simplest
|
||||
abstraction on the market, the function. <code>GetDatabaseHandler()</code> is much simpler to reason about than <code>GlobalContextHolder.getSingleton().getContext("database")</code>.
|
||||
These abstractions also, are prone to error, for instance if you use a hash-table to lookup values with a string key, what happens if I miss type the string? The compiler
|
||||
won't catch that for you, and you'd be adding a lot of extra instructions just to access some state somewhere (which at its fastest is a pointer dereference). Another common way to do this,
|
||||
is to pass a class object, <code>.class</code> in Java. However, this is even worse. Typically, a language will use reflection which or runtime type checks which can be extraordinarily slow.
|
||||
To compound this, you also have no gurantees on the existence of the values, for instance, if you want a <code>DatabaseHandler</code>, and you make the request for it, you have no idea
|
||||
if that is actually the right name or class until runtime. Contrast that with a function that will be picked up by your LSP or produce a compile-time error message.
|
||||
Global contexts, and contexts in general are a huge mess of slow, hard to optimize code, you should avoid them at all costs.
|
||||
</p>
|
||||
<h2>What to do instead</h2>
|
||||
<p>
|
||||
As mentioned earlier, global state should exist one layer under a function. There is absolutely nothing wrong with running a <code>database.InitializeDatabase()</code> function, to
|
||||
initialize your globals at startup, or having some sort of lazy-init built into your global accessor function. Keeping things simple is how you keep them readable, and scalable.
|
||||
What's funny, is that's pretty much it.
|
||||
</p>
|
||||
<p>
|
||||
Another thing I recommend is not accessing your globals inside of your functions, instead, pass them as arguments. This is just good-practice in general, your functions should
|
||||
do their best to be as pure as possible. This also makes your code truly re-usable and adaptable, since you may have multiple different global variables with the same types.
|
||||
This also makes your code easy to test, since it's not relying on any outside values existing or not.
|
||||
</p>
|
||||
<h2>Examples</h2>
|
||||
<p>
|
||||
Let's compare some code. In the first example, we'll do the traditional <code>GlobalContextHolder</code> OOP style,
|
||||
where we literally do our best to make it look like we're not working on global variables. In the second example, we allow our globals to exist,
|
||||
but instead of accessing them in a foolish way, we accept them as parameters.
|
||||
</p>
|
||||
<p>Example 1:</p>
|
||||
<pre>
|
||||
<code lang="java">
|
||||
public void saveUser(User u) {
|
||||
// Where did this guy come from? Idk, it's probably defined in some XML file somewhere :)
|
||||
DatabaseHandler dbh = GlobalContextHolder.getContext("database");
|
||||
dbh.table("users").save(u);
|
||||
}
|
||||
|
||||
// Main.java
|
||||
public static void main(String[] args) {
|
||||
User user = new User("Bob", 23);
|
||||
saveUser(user);
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
<p>Example 2:</p>
|
||||
<pre>
|
||||
<code lang="java">
|
||||
// Database.java
|
||||
private static DatabaseHandler DBH;
|
||||
|
||||
public static DatabaseHandler getDatabase() {
|
||||
if (DBH == null) {
|
||||
DBH = DatabaseHandler.newConnection("mysql://foo@bar:foo.com:3306");
|
||||
}
|
||||
|
||||
return DBH;
|
||||
}
|
||||
|
||||
// Main.java
|
||||
public static void saveUser(User u, DatabaseHandler dbh) {
|
||||
dbh.table("users").save(u);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Database.initialize();
|
||||
User user = new User("Bob", 23);
|
||||
saveUser(user, Database.get());
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
<p>
|
||||
Overall, this drastically reduces the mental overhead of the system, allowing for simple unit-testing (if you want), and
|
||||
a simplistic approach to scaling, since you don't have to worry about costly abstractions that don't actually simplify anything.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,56 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Achieving Stupid Performance with JPA and Hibernate</title>
|
||||
<meta name="description" content="Making JPA and Hibernate break the sound barrier on a ridiculous data set."/>
|
||||
<link rel="stylesheet" href="/index.css"/></head>
|
||||
<body>
|
||||
<a href="/"><< Back</a>
|
||||
<h1 id="jpa-is-slow">JPA is slow, lets make it stupid fast</h1>
|
||||
<p>
|
||||
JPA is <strong>not</strong> performant. Mainly because of things like batching, and this incessant idea that
|
||||
querying relationships lazily is somehow fast, its literally just deferring the work, which makes sense
|
||||
if you potentially don't want to use the data, but most people want that data anyways, or serialize it.
|
||||
</p>
|
||||
<p>
|
||||
I experienced this recently with a massive data set, this database was 1 core table of 4 million rows, followed by multiple child One-To-Many table
|
||||
of close to 10 million rows, and a single One-to-One with ~4 million rows as well. To use the service built on top of this massive data, you had to search
|
||||
with JPA search specifications, which turns out also to be insanely slow. The benchmark starts us at 15 or so minutes, which is obviously unacceptable.
|
||||
</p>
|
||||
<h2 id="go-fast">Time to go fast</h2>
|
||||
<p>
|
||||
By enabling <code>logging.level.org.hibernate.SQL=DEBUG</code> and <code>logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE</code>
|
||||
you're able to see the ridiculous number queries JPA is executing, they're un-countable, there's so many!! How can we fix this?
|
||||
</p>
|
||||
<p>
|
||||
If you're thinking of using <code>@Batch(100)</code>, it could work, only issue is JPA will batch each relationship, so each of our tables will still require
|
||||
<code>n/100</code> calls where <code>n</code> is the number of rows with relationships. So theoretically a 100x speed bump right? Well, it's actually more
|
||||
like a 33x performance bump, since you still have the overhead of making the database connections, and need to get a lock every time you select. Doing this we moved to around
|
||||
3-5 minutes depending on load per large call. This however, was still unacceptable.
|
||||
</p>
|
||||
<p>
|
||||
What if instead we just forced JPA to use <code>OUTER JOIN</code>? Isn't that exactly what the ANSI SQL wizards would tell you to do?
|
||||
Obviously, this is the solution, one single connection, 1 lock, and let the B-Tree optimizations inside of the database handle all of this ugliness for us.
|
||||
To do this you need to use something called <a href="https://www.baeldung.com/jpa-entity-graph"><code>@EntityGraph</code></a>
|
||||
on your JPA repository, what this does is forces JPA to use a <code>OUTER JOIN</code>
|
||||
to join relationships. This even works on nested entities, say I have Entity A with a child B which also has a One-to-Many on C, all you need to do
|
||||
is add the relationship to the entity graph with the <code>@NamedEntityGraph</code> annotation, then provide it with a few nice args like name,
|
||||
and attributeNodes which will point to your sub entity. Then finally on your JPA repository call you stitch the entity graph back together.
|
||||
</p>
|
||||
<pre>
|
||||
<code>
|
||||
@Repository
|
||||
public interface ARepository extends JpaRepository<A, UUID>, JpaSpecificationExecutor<A> {
|
||||
@Override
|
||||
@EntityGraph(attributePaths = { "bs", "B.cs" }) // Where B's are children of A's, and C's are children of B's.
|
||||
Page<A> findAll(@Nullable Specification<A> specification, Pageable pageable);
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
<p>
|
||||
After applying these, the query time went down from 15 minutes to ... *drum-roll* ... 3 seconds. So please, for the love of all that is computationally efficient, force your JPA
|
||||
to use <code>OUTER JOIN</code>, or even roll your own SQL, ORM's really are one of the biggest trade-offs, and they really can bite you. This is why tend to
|
||||
stay in the <code>DBI</code> universe when I write Perl. It's funny shocking Ruby on Rails and Java developers with my ~15ms queries :D
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,50 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Escaping evil-mode, maybe?</title>
|
||||
<meta name="description" content="Trying to escape evil mode"/>
|
||||
<meta name="keywords" content="Emacs, Perl, Vim, evil-mode escape"/>
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="stylesheet" href="/index.css"/></head>
|
||||
<body>
|
||||
<a href="/"><< Back</a>
|
||||
<h1 id="escaping-evil">Escaping evil-mode</h1>
|
||||
<p>
|
||||
I've been using <a href="https://www.emacswiki.org/emacs/Evil"><code>evil-mode</code></a> for almost 4 years at this point, and Vim keybinds even longer.
|
||||
However, I think it's almost time for me to change. In 2022 I spent the later half of the year just on default, vanilla emacs keybinds.
|
||||
This was really enjoyable and let me learn more about how the puritans do Emacs, but in general,
|
||||
I just felt a lot slower. So I came back to <code>evil-mode</code>, and have been using it daily ever since.
|
||||
But with some recent annoyances in Evil, and having to almost double my config size to use Evil integrations with other packages,
|
||||
I've been thinking of using Vanilla again, or exploring some of the other modal plugins like: <a href="https://github.com/meow-edit/meow">meow</a>,
|
||||
<a href="https://github.com/xahlee/xah-fly-keys">Xah-Fly-Keys</a>, or <a href="https://github.com/emacsorphanage/god-mode">god-mode</a> (don't love the name of this one :P).
|
||||
So, to the disdain of my productivity, I am going to try to use each of these for a week at a time, and report back each week with an update.
|
||||
</p>
|
||||
<h2 id="the-test">The test</h2>
|
||||
<p>
|
||||
I will be comparing each mode on the following all scored out of 10:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Learnability. How long does it take for a seasoned Vim and Emacs user to learn?</li>
|
||||
<li>
|
||||
Feel. How ergonomic, and how fast does it "feel"? This is fairly subjective but it does matter to me.
|
||||
I like to feel like i'm hacking on a 80's mainframe, with a nuclear threat on the horizon.
|
||||
</li>
|
||||
<li>Config. How difficult is it to configure, and get working with other packages</li>
|
||||
</ul>
|
||||
<h2 id="evil-review"><code>evil-mode</code>'s rating</h2>
|
||||
<p>
|
||||
Here is my review of <code>evil-mode</code>:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Learnability: 10 it's just Vim's keybinds (mostly)</li>
|
||||
<li>Feel: 7 it does feel fast, and fairly ergonomic, but the use of escape to escape modes kind of sucks</li>
|
||||
<li>Config: 3 you have to install extra packages for almost everything you could think of to add evil support</li>
|
||||
</ul>
|
||||
<p>
|
||||
Overall I do like <code>evil-mode</code>, and i'm very productive with it, but I find that a lot of little issues pop-up on a day to day basis
|
||||
that could be ironed out. Things like not having re-do automatically working. At the end of the day I use Emacs for both, speed, but also,
|
||||
for extensability, something that I think <code>evil-mode</code> restricts.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,44 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Using Niche Programming Languages</title>
|
||||
<meta name="description" value="How using Niche Programming Languages can drastically improve your hiring candidates, and in-turn your products" />
|
||||
<link rel="stylesheet" href="/index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a href="/"><< Back</a>
|
||||
<p>Published March 7th, 2024</p>
|
||||
<h1>Using Niche Programming Languages</h1>
|
||||
<p>
|
||||
Earlier this week,
|
||||
<a href="https://bleacherreport.com">Bleacher Report</a>
|
||||
was bought out and forced to re-write their Elixir/Erlang backend in something "more-mainstream" by the
|
||||
buying party (Warner), this is a HUGE mistake, and an example of how main-stream thought has eroded the foundations of software engineering. Bleacher Report had used
|
||||
Elixir/Erlang since at least <a href="https://www.erlang-solutions.com/case-studies/bleacher-report-case-study/">2017</a>,
|
||||
when their case-study of going from 150 servers to 5 via converting their Ruby code to Elixir/Erlang was published.
|
||||
All of this resliency, and engineering effort is now going to be rewritten in none other than JavaScript, which of course will be a tremedous effort.
|
||||
</p>
|
||||
<p>
|
||||
So why did they do it? Well, they had lay-offs, and then they needed to re-fill the seats they laid off a few years later. Instead of leveraging this "gold card"
|
||||
that is a niche language, Warner decides that it will be too hard to fill N seats. Which is also another issue in the software world, the "butts in seats typing"
|
||||
phalacy that more will get done. Anyways, what Warner could have done, is use this fantastical power bestowed by a niche language, and got the best developers
|
||||
in that niche technology. If they hired 15 Node devs for 100k a year each, they could afford 5 Erlang masterminds for 300k a year each (and then you wouldn't need a
|
||||
rewrite!) The other option is to hire a few Erlang devs and have them train other devs which could be an effective cost-cutting measure as well, without
|
||||
having to rewrite your codebase.
|
||||
</p>
|
||||
<p>
|
||||
I've been asked many times by developers, "should I learn X, it's so niche but it looks cool", and to every single one of those questions, I shout "YES!!!".
|
||||
Learning is <u>ALWAYS</u> good. But also, if you get really good at these niche technologies, you can become a SME (subject matter expert), and get hired
|
||||
by a business looking for people like you. Jane Street is notorious for using OCaml for literally everything. Yaron Minsky, mentions at Jane Street states
|
||||
that while there aren't many OCaml developers, the small set that does exist is typically <u>really good</u>, and a grade above the usual Python, Java, JavaScript developer
|
||||
(you'll choke on your coffee when you read Jane Streets developer salaries as well).
|
||||
</p>
|
||||
<p>
|
||||
When starting a new project I think you should consider using a niche language.
|
||||
You learn a lot, but also when it comes time to scale your idea, and you need to hire a partner or two,
|
||||
you'll be greatful you chose it, when extremely talented and passionate individuals send you their resume solely because you chose a backend written in Haskell, Scala,
|
||||
Perl, OCaml, Elixir, Clojure, Lua, etc. This is what happened for NoRedInk, after writing their original MVP in Ruby, they ended up using Haskell and Elm for new
|
||||
services, and now, they're one of the most well known Functional Programming shops, and they've managed to hire some brilliant people, like the creator of Elm himself!
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,59 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Why I still use Perl in 2025</title>
|
||||
<meta name="description" value="An overview of using the Perl programming language in 2025">
|
||||
<meta name="keywords" value="Perl Perl5 2025 Programming PL Theory Software">
|
||||
<link rel="stylesheet" href="/index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a href="/"><< back</a>
|
||||
<p>October 15th, 2025</p>
|
||||
<h1>Why I still use Perl</h1>
|
||||
<p>
|
||||
Perl is my favorite language, and I use it for everything. At work I often get questions like, "why do you use Perl?", or
|
||||
"Perl seems cool, but isn't it super old?". This is an all inclusive guide to why Perl rocks, and why I use it in 2025 to solve real-world problems.
|
||||
</p>
|
||||
<h2>What sets Perl apart for me</h2>
|
||||
<p>
|
||||
Perl is fun by default. It's a language jam-packed with flavor and culture, it oozes charm and doesn't really care what you think of it.
|
||||
I like to think of Perl as a cathedral (perhaps as to why we have Perl Monks?), sure it's old, mostly underfunded and losing its time in the spotlight,
|
||||
but its masonry was built by such dedicated and whimsical craftspeople, that it will continue to stand, like a monolith to the past for years and years to come.
|
||||
Perl will continue to work in its own eclectic way long after the newly constructed churches of current-time have collapsed. You can't build something like Perl anymore,
|
||||
the ways of olde are long gone. That's why I love Perl, it's unapologetically itself, and it will continue to power thousands of systems world-wide, long into the future.
|
||||
Perhaps we could all learn a thing or two about being like that.
|
||||
</p>
|
||||
<p>
|
||||
Technically speaking, I can write robust code that performs well, without the abuse of a type-checker, or the utopianism of a language ideology (ie. FP or OOP).
|
||||
Is it procedural, is it object-oriented, is it functional, no. It's Perl. It can be whatever you, or your team want it to be, and that's pretty cool. I usually use a mixture of all
|
||||
paradigms within a Perl codebase, based on the problem at hand. Perl has a unique motto, easy things should <i>feel</i> be easy, and hard things should <i>feel</i> be possible.
|
||||
I like to think a lot about how I feel when I write code, when I write Java I feel sort of bored, and problems are almost just rocks to jump over, when I write Perl I feel as if
|
||||
the roof is removed from the house, and I have freedom to approach the problem however I'd like. Finally, when I have to upgrade a Perl project,
|
||||
no matter its age I can rest assured that it will work, and continue to work. Some people call this stagnation, I like to think of it as standardization, there will be no Perl version
|
||||
of the Python 2.7 -> Python 3, or Scala 2 -> Scala 3, Perl doesn't want to change itself, why would it? The language as I explained before, is comfortable in its own skin.
|
||||
</p>
|
||||
<p>
|
||||
At the end of the day, Perl is a fast, developer oriented language that feels like rocket science. It is incredibly fun to work with,
|
||||
and allows developers to express themselves with simple constructs, in a way no other language (except maybe Raku) allows. Through my ~10 years of development experience, Perl has been
|
||||
my go to since the beginning. I've used many languages professionally, and casually, and none have brought me the joy that Perl has.
|
||||
</p>
|
||||
<h2>How I use Perl for income in 2025</h2>
|
||||
<p>
|
||||
My current company: <a href="https://molluscsoftware.com">Mollusc Software Solutions Inc.</a> is an exclusively Perl oriented business. We have a few modern web applications we
|
||||
support that are written using Perl. <a href="https://slapbirdapm.com">SlapbirdAPM</a> (Mojolicious, DBI, et al) and <a href="https://simplexpense.ca">SimplExpense</a> (Plack, Kelp, DBI, et al).
|
||||
Perl is used for everything at Mollusc, we write one-off scripts, web applications, data tasks, etc all in Perl. Also, we orchestrate our containers using a simple Perl-based tool that we're
|
||||
planning to open-source at some point.
|
||||
</p>
|
||||
<p>
|
||||
Outside of Mollusc, I have a contract with a sector of the Federal Government of Canada, working in DevOps. This puts me within close access of a lot of UNIX servers, all of which have Perl.
|
||||
I use Perl at this job to 1. bug my co-workers, and 2. quickly solve text, administration, and one-off problems quickly.
|
||||
</p>
|
||||
<h2>Closing</h2>
|
||||
<p>
|
||||
Perl is a great language you can use it for great fun, and great profit. It performs well and is suitable for most tasks you'll run into in 2025.
|
||||
If you're getting bored of your current goto language, and are looking for a fun language to satiate you for the next 10 years, Perl is probably one of your best bets.
|
||||
If you're a manager looking to start a greenfield project, and are worried about finding talent, Perl has a ton of extremely talented individuals (<a href="https://molluscsoftware.com">*cough* *cough*</a>)
|
||||
who are always looking for new/existing Perl projects.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,558 +0,0 @@
|
||||
<?xml version="1.0" standalone="yes"?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Perl OO benchmarking</title>
|
||||
<meta name="description" content="Perl object-orientation performance benchmarks between Moose, Moo, Modules, Corinna with a cross-language comparison against Ruby and Raku (Perl 6)"/>
|
||||
<link rel="stylesheet" href="/index.css"/></head>
|
||||
<body>
|
||||
<a href="/"><< back</a>
|
||||
<h1>Perl OO benchmarking</h1>
|
||||
<p>
|
||||
I've spent a lot of time working in projects that use the <a href="https://en.wikipedia.org/wiki/Command_pattern"><code>Action pattern</code></a> (also called the <code>Command pattern</code>).
|
||||
Now, regardless of how i've land on OO vs Functional in the last 5 years, there has always been a question I have yet to answer: "How much does this abstraction cost,
|
||||
and more importantly, how much does object instantiation actually cost?" (since the action pattern has you instantiating A LOT of objects)
|
||||
</p>
|
||||
<p>
|
||||
So I set out to benchmark, Perl OO, using the traditional Module OO, <code>Corinna</code>, <code>Moo</code> and <code>Moose</code>. But I thought it may also
|
||||
provide some value to compare these findings to other languages, namely ones in the same sort of realm that Perl has lives in: <code>Ruby</code> and <code>Raku (formerly Perl 6)</code>.
|
||||
</p>
|
||||
<h2 id="theBenchmark">The benchmark</h2>
|
||||
<p>
|
||||
The benchmark is fairly simple, since the goal here is to test the actual instantiation costs, so the method associated with the implemented action is very simple.
|
||||
An <code>Action</code> object has two members, <code>name</code> and <code>age</code>. It's <code>execute</code> method returns a Map/Hash/Dictionary, with the following keys
|
||||
<code>{ name => self.name, age => self.age }</code>. Basically just accessing the instantiated objects <code>age</code> and <code>name</code> attributes, and associating them to
|
||||
keys of the same name.
|
||||
</p>
|
||||
<p>
|
||||
One-million <code>Action</code> objects will be instantiated in a O(n) loop, and executed, then destroyed all within the same scope. Like so:
|
||||
</p>
|
||||
<pre>
|
||||
<code>
|
||||
my $name = 'Tim';
|
||||
my $age = 12;
|
||||
for ( 1 .. 1000000 ) {
|
||||
my $user = Action->new( name => $name, age => $age )->execute();
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
<p>
|
||||
This will be performed one-hundred times, then, the average of the times taken to do the one-million iterations will be the benchmark result.
|
||||
It is also important to note that the initial load-time of the language run-times is included in the calculation, though they are mostly negligble especially
|
||||
in the Perl case, and the actual instantiation takes the vast-majority of the time (close to 99% on average).
|
||||
</p>
|
||||
<p>
|
||||
This benchmark was performed on an Intel i7 12700KF, using DDR5 memory.
|
||||
</p>
|
||||
<h3>Summary Table</h3>
|
||||
<table style="border: 1px solid black;">
|
||||
<tr>
|
||||
<th style="text-align: left;">Language</th>
|
||||
<th style="text-align: left;">Performance</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
Perl Module OO
|
||||
</td>
|
||||
<td>
|
||||
454.627ms
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Perl Module OO + Types
|
||||
</td>
|
||||
<td>
|
||||
505.276ms
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Corinna</td>
|
||||
<td>564.411ms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Moo
|
||||
</td>
|
||||
<td>
|
||||
960.03ms
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Moo + Types
|
||||
</td>
|
||||
<td>
|
||||
2308.226ms
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Moose
|
||||
</td>
|
||||
<td>
|
||||
11420.455ms
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Moose + Immutable</td>
|
||||
<td>1182.109ms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
Moose + Types
|
||||
</td>
|
||||
<td>
|
||||
13649.37ms
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ruby</td>
|
||||
<td>167.627ms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Raku (MoarVM start-up subtracted)</td>
|
||||
<td>801.947ms</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h2 id="timesAndImplementations">Times and implementations</h2>
|
||||
<h3 id="perlImpls">Perl</h3>
|
||||
<h4 id="perlModuleOO">Module OO</h4>
|
||||
<p>
|
||||
Using the traditional Perl approach to OO using ref's and modules, is by far the fastest.
|
||||
</p>
|
||||
<h5>Code:</h5>
|
||||
<pre>
|
||||
<code>
|
||||
package Action;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
sub execute {
|
||||
my $self = shift;
|
||||
return +{ name => $self->{name}, age => $self->{age} };
|
||||
}
|
||||
|
||||
sub new {
|
||||
my ( $class, $name, $age ) = @_;
|
||||
my $self = { name => $name, age => $age };
|
||||
return bless $self, $class;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $name = 'Tim';
|
||||
my $age = 12;
|
||||
for ( 1 .. 1000000 ) {
|
||||
my $user = Action->new( $name, $age )->execute();
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
<p>
|
||||
This implementation is alright, it's fast, but it doesn't provide any validations, which is what I assume most OO frameworks like <code>Moo</code> and <code>Moose</code> do, especially
|
||||
if you use modules like <code>Type::Tiny</code>.
|
||||
</p>
|
||||
<h5>Time:</h5>
|
||||
<pre>
|
||||
<code>
|
||||
PERL TIME : 454.627ms
|
||||
</code>
|
||||
</pre>
|
||||
<h4 id="perlModuleOOTypes">Module OO with some type checks</h4>
|
||||
<p>
|
||||
Considering my assumptions about other OO libraries I decided to add a few checks to the Module OO implementation,
|
||||
using <code>Scalar::Util</code> we can test if <code>age</code> is a number, and also ensure the defined-ness of our
|
||||
attributes using the built-in <code>defined</code>.
|
||||
</p>
|
||||
<pre>
|
||||
<code>
|
||||
package Action;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Scalar::Util qw(looks_like_number);
|
||||
|
||||
sub execute {
|
||||
my $self = shift;
|
||||
return +{ name => $self->{name}, age => $self->{age} };
|
||||
}
|
||||
|
||||
sub new {
|
||||
my ( $class, $name, $age ) = @_;
|
||||
|
||||
if ( !defined $name || !defined $age ) {
|
||||
die 'name and age, should be defined.';
|
||||
}
|
||||
|
||||
if ( !looks_like_number($age) ) {
|
||||
die 'age should be a number.';
|
||||
}
|
||||
|
||||
my $self = { name => $name, age => $age };
|
||||
return bless $self, $class;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $name = 'Tim';
|
||||
my $age = 12;
|
||||
for ( 1 .. 1000000 ) {
|
||||
my $user = Action->new( $name, $age )->execute();
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
<p>This only came at a net-cost of around 50ms extra on average, which is surprisingly fast.</p>
|
||||
<pre>
|
||||
<code>
|
||||
PERL + TYPES TIME : 505.276ms
|
||||
</code>
|
||||
</pre>
|
||||
<h5>Code:</h5>
|
||||
<h4 id="perlCorinnaModuleOO">Corinna</h4>
|
||||
<p>
|
||||
Corinna is a new OO system added to Perl 5.38 but still marked as experimental. It performs very well compared to the features it provides,
|
||||
but it is definitely not something you'll see in the wild too often.
|
||||
</p>
|
||||
<pre>
|
||||
<code>
|
||||
use feature 'class';
|
||||
no warnings;
|
||||
|
||||
class Action {
|
||||
field $name : param;
|
||||
field $age : param;
|
||||
|
||||
method execute () {
|
||||
return +{ name => $name, age => $age };
|
||||
}
|
||||
}
|
||||
|
||||
my $name = 'Tim';
|
||||
my $age = 12;
|
||||
for ( 1 .. 1000000 ) {
|
||||
Action->new( name => $name, age => $age )->execute();
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
<p>
|
||||
I had to disable warnings on this because I wasn't sure what flags I needed to set in the <code>feature</code> or the <code>experimental</code>,
|
||||
also the lack of documentation definitely hurt, it took me a while to figure out I had to <code>use feature 'class';</code>. For some reason I thought
|
||||
the feature flag was <code>corinna</code> or something.
|
||||
</p>
|
||||
<h5>Time:</h5>
|
||||
<pre>
|
||||
<code>
|
||||
CORINNA TIME : 564.411ms
|
||||
</code>
|
||||
</pre>
|
||||
<h4 id="perlMoo">Moo</h4>
|
||||
<p>
|
||||
<code>Moo</code> is a "Minimalist Object Orientation" for Perl. It's what I've used the most, and it performs quite well for all of the
|
||||
features it gives you. However, there is definitely a cost, one that a lot of developers ignore. I've done two implementations here, one with
|
||||
<code>Type::Tiny</code>, which adds optional type-checking to attributes, and one without. The difference was quite compelling!
|
||||
</p>
|
||||
<h5>Code:</h5>
|
||||
Without <code>Type::Tiny</code>:
|
||||
<pre>
|
||||
<code>
|
||||
package Action;
|
||||
|
||||
use Moo;
|
||||
use namespace::clean;
|
||||
|
||||
has name => (
|
||||
is => 'ro',
|
||||
required => 1
|
||||
);
|
||||
|
||||
has age => (
|
||||
is => 'ro',
|
||||
required => 1
|
||||
);
|
||||
|
||||
sub execute {
|
||||
my $self = shift;
|
||||
return +{ name => $self->name, age => $self->age };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $name = 'Tim';
|
||||
my $age = 12;
|
||||
for ( 1 .. 1000000 ) {
|
||||
my $user = Action->new( name => $name, age => $age )->execute();
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
With <code>Type::Tiny</code>:
|
||||
<pre>
|
||||
<code>
|
||||
package Action;
|
||||
|
||||
use Moo;
|
||||
use Types::Standard qw(Int Str);
|
||||
use namespace::clean;
|
||||
|
||||
has name => (
|
||||
is => 'ro',
|
||||
isa => Str,
|
||||
required => 1
|
||||
);
|
||||
|
||||
has age => (
|
||||
is => 'ro',
|
||||
isa => Int,
|
||||
required => 1
|
||||
);
|
||||
|
||||
sub execute {
|
||||
my $self = shift;
|
||||
return +{ name => $self->name, age => $self->age };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $name = 'Tim';
|
||||
my $age = 12;
|
||||
for ( 1 .. 1000000 ) {
|
||||
my $user = Action->new( name => $name, age => $age )->execute();
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
<p>
|
||||
To my shock, <code>Type::Tiny</code> degrades performance by close to 300%. However, the more I looked into <code>Type::Tiny</code>, the more it made sense.
|
||||
<code>Type::Tiny</code>, to enforce types uses a lot of complex meta-programming, and ref checks, that add up to a ton of extra operations. Perhaps it would be
|
||||
worth using <code>B::Concise</code> or <code>B::Deparse</code> to see how many more operations this actually adds, but thats something for another day.
|
||||
</p>
|
||||
<h5>Time:</h5>
|
||||
<pre>
|
||||
<code>
|
||||
MOO TIME : 960.03ms
|
||||
MOO + TYPES TIME : 2308.226ms
|
||||
</code>
|
||||
</pre>
|
||||
<h4 id="perlMoose">Moose</h4>
|
||||
<p>
|
||||
<code>Moose</code> is a heavyweight, industrial purpose OO framework for Perl, at which <code>Moo</code> derives from. It is big, and heavy, and unfortunately,
|
||||
really slow for tasks like this. After starting this benchmark, I thought that the slowness was coming from loading the module between runs, but after timing the average load time,
|
||||
the time spent was negligble (31ms at most).
|
||||
</p>
|
||||
<p>
|
||||
Like in the <code>Moo</code> benchmark, I did two implementations, one using <code>Type::Tiny</code>, and one not.
|
||||
</p>
|
||||
<h5>Code:</h5>
|
||||
<pre>
|
||||
<code>
|
||||
package Action;
|
||||
|
||||
use Moose;
|
||||
use namespace::clean;
|
||||
|
||||
has name => (
|
||||
is => 'ro',
|
||||
required => 1
|
||||
);
|
||||
|
||||
has age => (
|
||||
is => 'ro',
|
||||
required => 1
|
||||
);
|
||||
|
||||
sub execute {
|
||||
my $self = shift;
|
||||
return +{ name => $self->name, age => $self->age };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $name = 'Tim';
|
||||
my $age = 12;
|
||||
for ( 1 .. 1000000 ) {
|
||||
my $user = Action->new( name => $name, age => $age )->execute();
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
With <code>Type::Tiny</code>:
|
||||
<pre>
|
||||
<code>
|
||||
package Action;
|
||||
|
||||
use Moose;
|
||||
use Types::Standard qw(Int Str);
|
||||
use namespace::clean;
|
||||
|
||||
has name => (
|
||||
is => 'ro',
|
||||
isa => Str,
|
||||
required => 1
|
||||
);
|
||||
|
||||
has age => (
|
||||
is => 'ro',
|
||||
isa => Int,
|
||||
required => 1
|
||||
);
|
||||
|
||||
sub execute {
|
||||
my $self = shift;
|
||||
return +{ name => $self->name, age => $self->age };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
my $name = 'Tim';
|
||||
my $age = 12;
|
||||
for ( 1 .. 1000000 ) {
|
||||
my $user = Action->new( name => $name, age => $age )->execute();
|
||||
}
|
||||
</code>
|
||||
</pre>
|
||||
<p>
|
||||
Just like <code>Moo</code> with types, <code>Type::Tiny</code> hits the final <code>Moose</code> result by around 2000ms overall.
|
||||
</p>
|
||||
<p>
|
||||
<b>Amendment</b>: After publishing my original findings I was alerted to a <a href="https://github.com/rawleyfowler/perl-oo-action-benchmark/pull/1">setting you could apply to Moose classes</a> that make them immutable.
|
||||
Immutable often means faster in-terms of creation but slower in terms of manipulation, but since this workload spends most of its time creating, and none of its time editing,
|
||||
this produced an incredible 10x speed improvement.
|
||||
</p>
|
||||
<h5>Immutable trick for Moose</h5>
|
||||
<pre>
|
||||
<code>
|
||||
__PACKAGE__->meta->make_immutable;
|
||||
</code>
|
||||
</pre>
|
||||
<h5>Time:</h5>
|
||||
<pre>
|
||||
<code>
|
||||
MOOSE TIME : 11420.455ms
|
||||
MOOSE + TYPES TIME : 13649.37ms
|
||||
MOOSE IMMU TIME : 1182.109ms
|
||||
</code>
|
||||
</pre>
|
||||
<h3 id="rubyImpl">Ruby</h3>
|
||||
<p>
|
||||
I also did a quick Ruby implementation, since its a "true" Object-Oriented language, I assumed it would have
|
||||
a lot of optimizations built-in for this sort of work, and based on its runtime, that seems to be the case.
|
||||
</p>
|
||||
<p>
|
||||
Also, note, this was on Ruby 3.1.2, not Ruby 3.3 with YJIT, so this could theoretically be faster.
|
||||
</p>
|
||||
<h5>Code:</h5>
|
||||
<pre>
|
||||
<code>
|
||||
class Action
|
||||
def initialize(age, name)
|
||||
@age = age
|
||||
@name = name
|
||||
end
|
||||
|
||||
def execute
|
||||
{ name: @name, age: @age }
|
||||
end
|
||||
end
|
||||
|
||||
age = 12
|
||||
name = 'Tim'
|
||||
1000000.times do
|
||||
user = Action.new(age, name).execute()
|
||||
end
|
||||
</code>
|
||||
</pre>
|
||||
<h5>Time:</h5>
|
||||
<pre>
|
||||
<code>
|
||||
RUBY TIME : 167.627ms
|
||||
</code>
|
||||
</pre>
|
||||
<h3 id="rakuImpl">Raku (formerly Perl 6)</h3>
|
||||
<p>
|
||||
Finally, we have Raku, which unfortunately doesn't do too well. To make this a little more competitive,
|
||||
I subtracted the run-time of MoarVM from the end-result. Though, I think Raku's implementation is the simplest to
|
||||
understand in terms of langauge design, so it gets a +1 from me for that.
|
||||
</p>
|
||||
<h5>Code:</h5>
|
||||
<pre>
|
||||
<code>
|
||||
class Action {
|
||||
has $.name;
|
||||
has $.age;
|
||||
|
||||
method execute {
|
||||
return %(name => $!name, age => $!age);
|
||||
}
|
||||
}
|
||||
|
||||
my $name = 'Tim';
|
||||
my $age = 12;
|
||||
for 1..1000000 -> $ {
|
||||
my $user = Action.new(name => $name, age => $age).execute();
|
||||
}
|
||||
|
||||
</code>
|
||||
</pre>
|
||||
<h5>Time (with MoarVM startup time removed):</h5>
|
||||
<pre>
|
||||
<code>
|
||||
RAKU TIME : 801.947ms
|
||||
</code>
|
||||
</pre>
|
||||
<h2>So what?</h2>
|
||||
<p>
|
||||
Object instantiation has a cost. A lot of people ignore this, especially when using heavyweight libraries like <code>Moose</code>.
|
||||
You can put this into practical terms simply, using basic Perl module OO you effectively double the number of objects of this type you can create and execute a
|
||||
method on given any time-frame compared to <code>Moo</code> without types, and twenty-two times more objects versus <code>Moose</code> without types.
|
||||
But these abstractions exist for reasons, and they are used for a reason.
|
||||
</p>
|
||||
<p>
|
||||
<code>Moo</code> and <code>Moose</code> have their places, especially in web-applications where the number of instantiations is low, and the expensive
|
||||
operations happen during side-effects like database operations.
|
||||
This also goes without mentioning some of the great features modules like <code>Moo</code> and <code>Moose</code> provide.
|
||||
It is valuable to know that if you need something to perform well, <code>Moo</code> and <code>Moose</code>
|
||||
may incur overhead you hadn't previously considered. This is compounded when enforcing types.
|
||||
For hot-code that will be hit repeatedly within a short amount of time, this can drastically effect performance, think of things like a landing page, or a search-bar API.
|
||||
</p>
|
||||
<p>
|
||||
<code>Corinna</code>, is a very nice prospect, it offers a lot of great things from <code>Moo</code> and <code>Moose</code> but seems to
|
||||
exalt far less of a performance penalty. It will definitely be on my radar in the future, especially when the documentation improves.
|
||||
</p>
|
||||
<p>
|
||||
Another approach is to simply use functions, which of course will perform the best. But, it's hard to argue to do this unless performance is the number one goal,
|
||||
especially on codebases that follow this pattern.
|
||||
</p>
|
||||
<div>
|
||||
You can view the <a href="https://github.com/rawleyfowler/perl-oo-action-benchmark">Source code</a>.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Rootless Podman on Alpine Linux</title>
|
||||
<meta name="description" value="My experience setting up rootless podman on alpine linux">
|
||||
<meta name="keywords" value="Alpine Linux Podman OpenRC Rootless">
|
||||
<link rel="stylesheet" href="/index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a href="/"><< back</a>
|
||||
<p>October 17th, 2025</p>
|
||||
<h1>Why I still use Perl</h1>
|
||||
<div>
|
||||
This website, my <a href="https://molluscsoftware.com">companies website</a>, and soon,
|
||||
<a href="https://github.com/perl-ide/perl-ide-poll">the Perl IDE survey</a> all run on the same
|
||||
Alpine Linux machine. My website and mollusc's website run as static-files served by <a href="https://caddyserver.com">Caddy</a>.
|
||||
In an attempt to simplify my life, I decided that the new Perl-IDE-Survey
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Using LiteXL!</title>
|
||||
<meta name="description" content="Trying Lite XL, my thoughts on the Lite XL text editor"/>
|
||||
<meta name="keywords" content="Lite-XL text-editor text editor perl emacs"/>
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="stylesheet" href="/index.css"/></head>
|
||||
<body>
|
||||
<a href="/"><< back</a>
|
||||
<h1 id="trying-lite-xl">Trying Lite-XL</h1>
|
||||
<p>
|
||||
<!-- TODO: Write about why I like and dislike Lite-XL -->
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,93 @@
|
||||
package repos
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rawleyfowler/rawleydotxyz/models"
|
||||
"github.com/rawleyfowler/rawleydotxyz/utils"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type AdminRepo struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
func NewAdminRepo(dsnPath string) *AdminRepo {
|
||||
c := AdminRepo{}
|
||||
var err error
|
||||
c.DB, err = utils.CreateDatabase(utils.LoadDSN(dsnPath))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.DB.AutoMigrate(&models.Administrator{})
|
||||
return &c
|
||||
}
|
||||
|
||||
func (ar *AdminRepo) CreateAdmin(a *models.Administrator) error {
|
||||
if a.Password == "" ||
|
||||
a.Username == "" {
|
||||
return errors.New("Administrators must have a valid username and password")
|
||||
}
|
||||
if len(a.Password) < 8 {
|
||||
return errors.New("Passwords must have a length of atleast 8")
|
||||
}
|
||||
a.Token = uuid.New().String()
|
||||
a.Password = fmt.Sprintf("%x", string(sha256.New().Sum([]byte(a.Password))))
|
||||
err := ar.DB.Table("administrators").Save(a).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ar *AdminRepo) GetAdminByToken(k string) (*models.Administrator, error) {
|
||||
if k == "" {
|
||||
return nil, errors.New("Session key cannot be blank")
|
||||
}
|
||||
if len(k) != 36 {
|
||||
return nil, errors.New("Valid sessions keys are always 36 byte utf-8 strings")
|
||||
}
|
||||
var a *models.Administrator
|
||||
err := ar.DB.Table("administrators").Where(&models.Administrator{Token: k}).Scan(a).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (ar *AdminRepo) GetAdminByCredentials(uname, pword string) (*models.Administrator, error) {
|
||||
if uname == "" ||
|
||||
pword == "" {
|
||||
return nil, errors.New("Username or password mustn't be empty")
|
||||
}
|
||||
if len(pword) < 8 {
|
||||
return nil, errors.New("Password length must be no less than 8")
|
||||
}
|
||||
pword = fmt.Sprintf("%x", string(sha256.New().Sum([]byte(pword))))
|
||||
var a models.Administrator
|
||||
err := ar.DB.Table("administrators").Where(&models.Administrator{Username: uname, Password: pword}).First(&a).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &a, nil
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package repos
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitlab.com/rawleyifowler/site-rework/models"
|
||||
)
|
||||
|
||||
var test_admin_repo = NewAdminRepo("dsn")
|
||||
|
||||
func TestCreateAdmin(t *testing.T) {
|
||||
err := test_admin_repo.CreateAdmin(&models.Administrator{})
|
||||
if err == nil {
|
||||
t.Fatalf("Expected: You cannot create an admin with empty credentials")
|
||||
}
|
||||
err = test_admin_repo.CreateAdmin(&models.Administrator{Username: "", Password: "12345678"})
|
||||
if err == nil {
|
||||
t.Fatalf("Expected: You cannot create an admin with empty credentials")
|
||||
}
|
||||
err = test_admin_repo.CreateAdmin(&models.Administrator{Username: "12345678", Password: ""})
|
||||
if err == nil {
|
||||
t.Fatalf("Expected: You cannot create an admin with empty credentials")
|
||||
}
|
||||
err = test_admin_repo.CreateAdmin(&models.Administrator{Username: "valid_username", Password: "invalid"})
|
||||
if err == nil {
|
||||
t.Fatalf("Expected: You cannot create an admin with a password less than 8 characters")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAdminByToken(t *testing.T) {
|
||||
_, err := test_admin_repo.GetAdminByToken("")
|
||||
if err == nil {
|
||||
t.Fatalf("Expected: Error should be thrown when an empty session key is provided")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAdminByCredentials(t *testing.T) {
|
||||
_, err := test_admin_repo.GetAdminByCredentials("", "")
|
||||
if err == nil {
|
||||
t.Fatalf("Expected: Error should be thrown if empty credentials are given")
|
||||
}
|
||||
_, err = test_admin_repo.GetAdminByCredentials("1234", "")
|
||||
if err == nil {
|
||||
t.Fatalf("Expected: Error should be thrown if empty credentials are given")
|
||||
}
|
||||
_, err = test_admin_repo.GetAdminByCredentials("", "123456789")
|
||||
if err == nil {
|
||||
t.Fatalf("Expected: Error should be thrown if empty credentials are given")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package repos
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/rawleyfowler/rawleydotxyz/models"
|
||||
"github.com/rawleyfowler/rawleydotxyz/utils"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type BlogRepo struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
func NewBlogRepo(dsnPath string) *BlogRepo {
|
||||
b := BlogRepo{}
|
||||
var err error
|
||||
b.DB, err = utils.CreateDatabase(utils.LoadDSN(dsnPath))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
b.DB.AutoMigrate(&models.BlogPost{})
|
||||
return &b
|
||||
}
|
||||
|
||||
func (b *BlogRepo) CreateBlogPost(p *models.BlogPost) error {
|
||||
if p == nil {
|
||||
return errors.New("Blog post must not be nil")
|
||||
}
|
||||
if p.Title == "" ||
|
||||
p.Content == "" ||
|
||||
p.Url == "" {
|
||||
return errors.New("Blog posts must be fully formed")
|
||||
}
|
||||
err := b.DB.Table("blog_posts").Create(p).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BlogRepo) GetBlogByUrl(url string) (*models.BlogPost, error) {
|
||||
if url == "" {
|
||||
return nil, errors.New("Cannot query by empty value")
|
||||
}
|
||||
var post models.BlogPost
|
||||
err := b.DB.Table("blog_posts").Where(&models.BlogPost{Url: url}).Scan(&post).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
func (b *BlogRepo) GetAllBlogPosts() (*[]models.BlogPost, error) {
|
||||
posts := new([]models.BlogPost)
|
||||
err := b.DB.Table("blog_posts").Order("date desc").Scan(posts).Error
|
||||
if err != nil {
|
||||
return nil, errors.New("Could not load all posts from database")
|
||||
}
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
func (b *BlogRepo) UpdateExistingPost(p *models.BlogPost) error {
|
||||
if p == nil {
|
||||
return errors.New("Cannot update with nil value for post")
|
||||
}
|
||||
if p.Url == "" {
|
||||
return errors.New("Url must exist to update a blog post record")
|
||||
}
|
||||
err := b.DB.Table("blog_posts").Where(&models.BlogPost{Url: p.Url}).Save(p).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BlogRepo) DeleteExistingPost(p *models.BlogPost) error {
|
||||
if p == nil {
|
||||
return errors.New("Cannot delete with nil value for post")
|
||||
}
|
||||
if p.Url == "" {
|
||||
return errors.New("Url must exist to delete a blog post record")
|
||||
}
|
||||
err := b.DB.Table("blog_posts").Delete(p).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package repos
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitlab.com/rawleyifowler/site-rework/models"
|
||||
)
|
||||
|
||||
var test_blog_repo = NewBlogRepo("dsn")
|
||||
|
||||
func TestGetBlogByUrl(t *testing.T) {
|
||||
_, err := test_blog_repo.GetBlogByUrl("")
|
||||
if err == nil {
|
||||
t.Fatalf("Expected: Empty values of url for get blog by url should return an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateBlogPost(t *testing.T) {
|
||||
err := test_blog_repo.CreateBlogPost(&models.BlogPost{})
|
||||
if err == nil {
|
||||
t.Fatalf("Expected: Blog posts must be fully formed")
|
||||
}
|
||||
|
||||
err = test_blog_repo.CreateBlogPost(&models.BlogPost{Title: "abc"})
|
||||
if err == nil {
|
||||
t.Fatalf("Expected: Blog posts must be fully formed, title, content, url")
|
||||
}
|
||||
|
||||
err = test_blog_repo.CreateBlogPost(nil)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected: Cannot create blog posts from nil values")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package repos
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/rawleyfowler/rawleydotxyz/models"
|
||||
"github.com/rawleyfowler/rawleydotxyz/utils"
|
||||
)
|
||||
|
||||
type MusicRepo struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
func NewMusicRepo(dsnPath string) *MusicRepo {
|
||||
mr := new(MusicRepo)
|
||||
var err error
|
||||
mr.DB, err = utils.CreateDatabase(utils.LoadDSN(dsnPath))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mr.DB.AutoMigrate(&models.Music{})
|
||||
return mr
|
||||
}
|
||||
|
||||
func (mr *MusicRepo) GetAllSongs() (*[]models.Music, error) {
|
||||
songs := new([]models.Music)
|
||||
err := mr.DB.Table("music").Scan(songs).Error
|
||||
if err != nil {
|
||||
return nil, errors.New("Could not load music from database")
|
||||
}
|
||||
return songs, nil
|
||||
}
|
||||
|
||||
func (mr *MusicRepo) CreateMusic(m *models.Music) error {
|
||||
if m.Name == "" || m.Url == "" {
|
||||
return errors.New("Cannot add music with empty or partially filled values")
|
||||
}
|
||||
if err := mr.DB.Table("music").Save(m).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
/* Color Scheme from here: https://draculatheme.com */
|
||||
html {
|
||||
max-width: 100vw;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #282a36;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
-webkit-font-smoothing: antialias;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
background-color: #f8f8f2;
|
||||
font-size: calc(12px + 0.33vw);
|
||||
margin: auto;
|
||||
padding: 1.3em;
|
||||
max-width: clamp(400px, 750px + 3vw, 900px);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.8em;
|
||||
font-weight: 800;
|
||||
margin-bottom: 0;
|
||||
margin-top: 12px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.6em;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: clamp(1.3em, 1.4em, 1.6em);
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: clamp(1.1em, 1.2em, 1.3em);
|
||||
font-weight: bold;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: clamp(0.9em, 1em, 1.1em);
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
max-width: 50em;
|
||||
}
|
||||
|
||||
li {
|
||||
padding-top: 6px;
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
transition: 100ms linear;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
a::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
top: 83%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
transition: 102ms cubic-bezier(0, 0.86, 0.1, 1);
|
||||
background-color: #fff42b;
|
||||
}
|
||||
|
||||
a:hover:after {
|
||||
top: 8%;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin-top: calc(12px + 1vh);
|
||||
margin-bottom: 0.66vw;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
margin-bottom: 0;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 567px) {
|
||||
.nav-bar {
|
||||
justify-content: normal;
|
||||
}
|
||||
.nav-item {
|
||||
margin-left: 12px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
margin: 12px;
|
||||
}
|
||||
|
||||
.subtext, .sub-text {
|
||||
font-size: clamp(0.65em, 0.72em, 0.8em);
|
||||
}
|
||||
|
||||
.footer > a {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.post-record {
|
||||
padding-right: 6px;
|
||||
width: 200px + 2vw;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.post-record-title {
|
||||
font-size: 1.3em;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.skill-keywords {
|
||||
overflow: wrap;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.skill-keywords > i::before {
|
||||
content: " '";
|
||||
}
|
||||
|
||||
.skill-keywords > i::after {
|
||||
content: "' ";
|
||||
}
|
||||
|
||||
.wrap {
|
||||
word-wrap: break-word;
|
||||
overflow: wrap;
|
||||
}
|
||||
|
||||
/* For code snippets */
|
||||
pre {
|
||||
overflow: auto;
|
||||
background-color: #44475a;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
code {
|
||||
color: #50fa7b;
|
||||
font-family: monospace;
|
||||
font-size: 0.9em;
|
||||
margin-left: 3px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* For the music page */
|
||||
.music-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.music {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.music::before {
|
||||
content: "`";
|
||||
}
|
||||
|
||||
.music::after {
|
||||
content: "`";
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
color: #f8f8f2;
|
||||
background-color: #282a36;
|
||||
}
|
||||
body * {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
body > * {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
code {
|
||||
color: #50fa7b;
|
||||
}
|
||||
a::after {
|
||||
background-color: #bd93f9;
|
||||
}
|
||||
code {
|
||||
color: #50fa7b;
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
@@ -0,0 +1,20 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="keywords" content="blog resume rawley-fowler rawleyportfolio rawleyxyz rawleyfowler RF rawley fowler Rawley Fowler portfolio RawleyFowler RawleyXYZ rawleyXYZ RawleyXYZ">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/static/index.css" rel="stylesheet" type="text/css">
|
||||
<title>admin login</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ template "nav.tmpl" . }}
|
||||
{{ if . }}
|
||||
<h2>Login was <u>successful</u>!</h2>
|
||||
<p>Click <a href="/admin/post">here</a> to go to the post creation area.</p>
|
||||
{{ else }}
|
||||
<h2>Login was <u>not</u> successful...</h2>
|
||||
<p>Click <a href="/admin/">here</a> to return to the login page to try again.</p>
|
||||
{{ end }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="keywords" content="blog rawley-fowler rawleyportfolio rawleyxyz rawleyfowler RF rawley fowler Rawley Fowler portfolio RawleyFowler RawleyXYZ rawleyXYZ RawleyXYZ">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/static/index.css" rel="stylesheet" type="text/css">
|
||||
<title>rawley.xyz | blog</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ template "nav.tmpl" . }}
|
||||
<h2>Blog posts</h2>
|
||||
<p>
|
||||
These blog posts are mine and only mine, please do not steal them. You can cite them in your works without permission though. If you want to keep up with my blog I have an <a href="/blog/feed">rss feed</a>.
|
||||
</p>
|
||||
{{ range $val := . }}
|
||||
<div class="post-record">
|
||||
<div>
|
||||
<a class="post-record-title" href="/blog/post/{{ $val.Url }}">{{ $val.Title }}</a>
|
||||
</div>
|
||||
<div>
|
||||
{{ $val.Date }}
|
||||
</div>
|
||||
</div>
|
||||
{{ else }}
|
||||
<p>No blog posts...</p>
|
||||
{{ end }}
|
||||
|
||||
{{ template "footer_no_banners.tmpl" }}
|
||||
@@ -0,0 +1,14 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="keywords" content="blog resume rawley-fowler rawleyportfolio rawleyxyz rawleyfowler RF rawley fowler Rawley Fowler portfolio RawleyFowler RawleyXYZ rawleyXYZ RawleyXYZ">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/static/index.css" rel="stylesheet" type="text/css">
|
||||
<title>{{ .Title }}</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ template "nav.tmpl" . }}
|
||||
<h2>{{ .Title }}</h2>
|
||||
<div>{{ .Content | UnescapeHTML }}</div>
|
||||
{{ template "footer_no_banners.tmpl" . }}
|
||||
@@ -0,0 +1,11 @@
|
||||
{{ template "header.tmpl" . }}
|
||||
<h2>Ways to contact me</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="mailto:rawleyfowler@gmail.com">Email</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://unix.stackexchange.com/users/507434/rawley-fowler">Stack Exchange</a>
|
||||
</li>
|
||||
</ul>
|
||||
{{ template "footer_no_banners.tmpl" . }}
|
||||
@@ -0,0 +1,31 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="keywords" content="blog resume rawley-fowler rawleyportfolio rawleyxyz rawleyfowler RF rawley fowler Rawley Fowler portfolio RawleyFowler RawleyXYZ rawleyXYZ RawleyXYZ">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/static/index.css" rel="stylesheet" type="text/css">
|
||||
<title>crud post</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ template "nav.tmpl" . }}
|
||||
<h2>Create post</h2>
|
||||
<form action="/admin/post" method="POST">
|
||||
<input required type="text" placeholder="url" name="url" />
|
||||
<input type="text" placeholder="title" name="title" />
|
||||
<textarea placeholder="post" name="content"></textarea>
|
||||
<select required name="op">
|
||||
<option value="create">create</option>
|
||||
<option value="update">update</option>
|
||||
<option value="delete">delete</option>
|
||||
</select>
|
||||
<button type="submit">Make post</button>
|
||||
<button type="clear">Clear form</button>
|
||||
</form>
|
||||
<form action="/admin/music" method="POST">
|
||||
<input required type="text" placeholder="url" name="url"/>
|
||||
<input required type="text" placeholder="name" name="name"/>
|
||||
<button type="submit">Add Band</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,22 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="keywords" content="blog rawley-fowler rawleyportfolio rawleyxyz rawleyfowler RF rawley fowler Rawley Fowler portfolio RawleyFowler RawleyXYZ rawleyXYZ RawleyXYZ">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/static/index.css" rel="stylesheet" type="text/css">
|
||||
<title>rawley.xyz | blog</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ template "nav.tmpl" . }}
|
||||
<h2>Donate</h2>
|
||||
<p>
|
||||
Donations are greatly appreciated, but nowhere near necessary.
|
||||
</p>
|
||||
<p class="wrap">
|
||||
<h3>
|
||||
<b>Bitcoin</b>:
|
||||
</h3>
|
||||
bc1qkvh6r23lv8t0tquwgzet9t07qwyk8ng96hkw7r
|
||||
</p>
|
||||
{{ template "footer_no_banners.tmpl" }}
|
||||
@@ -0,0 +1,15 @@
|
||||
<br>
|
||||
<div class="footer">
|
||||
<p class="footer-text">This site is powered by:</p>
|
||||
<a href="https://www.openbsd.org/">
|
||||
<img src="/static/openbsd.gif" alt="Powered by OpenBSD!" width="150px" height="50px"/>
|
||||
</a>
|
||||
<a href="https://www.nginx.org">
|
||||
<img src="/static/nginx.png" alt="Powered by NGiNX!" width="150px" height="50px"/>
|
||||
</a>
|
||||
<a href="https://go.dev">
|
||||
<img src="/static/go.png" alt="Powered by Go!" widht="150px" height="50px" />
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,8 @@
|
||||
<br>
|
||||
<hr></hr>
|
||||
<p class="subtext">Checkout the source code for this site on <a href="https://github.com/rawleyfowler/rawleydotxyz">github</a>.</p>
|
||||
<p class="subtext">
|
||||
All opinions expressed on this website are that of my own, they do not reflect those of my employer(s); future, past, or present.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="keywords" content="blog resume rawley-fowler rawleyportfolio rawleyxyz rawleyfowler RF rawley fowler Rawley Fowler portfolio RawleyFowler RawleyXYZ rawleyXYZ RawleyXYZ">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/static/index.css" rel="stylesheet" type="text/css">
|
||||
<title>forbidden</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ template "nav.tmpl" . }}
|
||||
<h2>403 You're not allowed here >:(</h2>
|
||||
<p>Click <a href="/">here</a> to return home.</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="keywords" content="blog resume rawley-fowler rawleyportfolio rawleyxyz rawleyfowler RF rawley fowler Rawley Fowler portfolio RawleyFowler RawleyXYZ rawleyXYZ RawleyXYZ">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/static/index.css" rel="stylesheet" type="text/css">
|
||||
<title>rawley.xyz{{ .Title }}</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ template "nav.tmpl" . }}
|
||||
@@ -0,0 +1,12 @@
|
||||
{{ template "header.tmpl" . }}
|
||||
<h2>Hi, my name is Rawley.</h2>
|
||||
<p>
|
||||
I am an independent software engineer, and self proclaimed systems administrator from Canada. Currently, I am pursuing a degree in Computer Science, with hopes of participating in a graduate program later down the road.
|
||||
</p>
|
||||
<p>
|
||||
I am a GNU/Linux and OpenBSD enthusiast. You'll find me active in the Debian Linux, Void Linux and OpenBSD communities. My favourite programming languages are Go and Clojure. Currently, I am working on my <a href="https://github.com/rawleyfowler/rawleydotxyz">website</a>, and an open-source community based blogging network called <a href="https://gitlab.com/xrn">xrn</a>. All of my work is open source, you can check it out on my <a href="https://github.com/rawleyfowler">github</a>.
|
||||
</p>
|
||||
<p>
|
||||
When I'm not programming, I'm either reading, cooking, playing table-top games, or walking my dog.
|
||||
</p>
|
||||
{{ template "footer.tmpl" . }}
|
||||
@@ -0,0 +1,15 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="keywords" content="blog resume rawley-fowler rawleyportfolio rawleyxyz rawleyfowler RF rawley fowler Rawley Fowler portfolio RawleyFowler RawleyXYZ rawleyXYZ RawleyXYZ">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/static/index.css" rel="stylesheet" type="text/css">
|
||||
<title>internal server error</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ template "nav.tmpl" . }}
|
||||
<h2>500 Internal server error :(</h2>
|
||||
<p>Click <a href="/">here</a> to return home.</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,8 @@
|
||||
{{ template "header.tmpl" . }}
|
||||
<h2>Admin Login</h2>
|
||||
<form action="/admin/login" method="POST">
|
||||
<input type="text" name="username" placeholder="username" />
|
||||
<input type="password" name="password" placeholder="password" />
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
{{ template "footer_no_banners.tmpl" . }}
|
||||
@@ -0,0 +1,27 @@
|
||||
<div class="header">
|
||||
<div class="nav-bar">
|
||||
<div>
|
||||
<h1 class="header-title">
|
||||
<a href="/" id="title">Rawley.xyz</a>
|
||||
</h1>
|
||||
</div>
|
||||
<div>
|
||||
<a class="nav-item" href="/blog">blog</a>
|
||||
</div>
|
||||
<div>
|
||||
<a class="nav-item" href="/resume">resume</a>
|
||||
</div>
|
||||
<div>
|
||||
<a class="nav-item" href="/contact">contact</a>
|
||||
</div>
|
||||
<div>
|
||||
<a class="nav-item" href="/donate">donate</a>
|
||||
</div>
|
||||
<div>
|
||||
<a class="nav-item" href="/music">tunes</a>
|
||||
</div>
|
||||
<div>
|
||||
<a class="nav-item" href="https://github.com/rawleyfowler">software</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,19 @@
|
||||
<!--not_found.html-->
|
||||
<html>
|
||||
<head>
|
||||
<meta lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="keywords" content="blog resume rawley-fowler rawleyportfolio rawleyxyz rawleyfowler RF rawley fowler Rawley Fowler portfolio RawleyFowler RawleyXYZ rawleyXYZ RawleyXYZ">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/static/index.css" rel="stylesheet" type="text/css">
|
||||
<title>not found</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ template "nav.tmpl" . }}
|
||||
|
||||
<h2>404 Not found :(</h2>
|
||||
<p>
|
||||
Click <a href="/">here</a> to return home.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,20 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="keywords" content="blog resume rawley-fowler rawleyportfolio rawleyxyz rawleyfowler RF rawley fowler Rawley Fowler portfolio RawleyFowler RawleyXYZ rawleyXYZ RawleyXYZ">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/static/index.css" rel="stylesheet" type="text/css">
|
||||
<title>forbidden</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ template "nav.tmpl" . }}
|
||||
{{ if . }}
|
||||
<h2>Operation performed successfully!</h2>
|
||||
<p>Click <a href="/admin/post">here</a> to return to the management page.</p>
|
||||
{{ else }}
|
||||
<h2>Operation failed to perform...</h2>
|
||||
<p>Click <a href="/admin/post">here</a> to return to the management page.</p>
|
||||
{{ end }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,63 @@
|
||||
<!--resume.tmpl-->
|
||||
{{ template "header.tmpl" . }}
|
||||
<h3>Skill keywords</h3>
|
||||
<p class="skill-keywords">
|
||||
<i>GNU/Linux</i>
|
||||
<i>Debian</i>
|
||||
<i>C/C++</i>
|
||||
<i>OpenBSD</i>
|
||||
<i>Go</i>
|
||||
<i>Bash</i>
|
||||
<i>Clojure</i>
|
||||
<i>Git</i>
|
||||
<i>Gentoo</i>
|
||||
<i>Java</i>
|
||||
<i>NGiNX</i>
|
||||
<i>TypeScript</i>
|
||||
<i>Vim</i>
|
||||
<i>NetBSD</i>
|
||||
<i>JavaScript</i>
|
||||
<i>HTML</i>
|
||||
<i>CSS</i>
|
||||
<i>Unix</i>
|
||||
<i>X11</i>
|
||||
<i>Ruby</i>
|
||||
<i>Vue</i>
|
||||
<i>MariaDB</i>
|
||||
<i>Korn Shell</i>
|
||||
<i>Lua</i>
|
||||
<i>React</i>
|
||||
<i>NodeJS</i>
|
||||
<i>Ruby-on-Rails</i>
|
||||
</p>
|
||||
<h3>Education</h3>
|
||||
<p>
|
||||
<h4>Bachelors of Science in Computer Science</h4>
|
||||
(2022-present)
|
||||
</p>
|
||||
<p>
|
||||
<h4>2 Year Diploma in Computer Systems Technology</h4>
|
||||
(2020-2022)
|
||||
</p>
|
||||
<h3>Projects</h3>
|
||||
<p>
|
||||
<h4><a href="https://gitlab.com/rawleyIfowler/rawleydotxyz">this website</a></h4>
|
||||
Website written in Go (templates and all), hosted on a OpenBSD server using a NGiNX reverse proxy.
|
||||
</p>
|
||||
<p>
|
||||
<h4><a href="https://gitlab.com/rawleyIfowler/wiki-bot">wiki-bot</a></h4>
|
||||
A Discord bot written entirely in Clojure, that responds to users with random wikipedia articles. Hosted on Heroku.
|
||||
</p>
|
||||
<p>
|
||||
<h4><a href="https://gitlab.com/xrn">xrn</a></h4>
|
||||
A free and open source community based blogging application similar to <a href="https://stackexchange.com">Stack Exchange</a>.
|
||||
</p>
|
||||
<p>
|
||||
<h4><a href="https://gitlab.com/rawleyIfowler/parser">parser</a></h4>
|
||||
A simple bash utility designed to save time when parsing files against patterns.
|
||||
</p>
|
||||
<p>
|
||||
<h4><a href="https://gitlab.com/rawleyIfowler/nfs-util">nfs-util</a></h4>
|
||||
Bash utility for easily creating and validating nfs shares. Including dry runs for connectivity tests.
|
||||
</p>
|
||||
{{ template "footer_no_banners.tmpl" . }}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>rawley.xyz</title>
|
||||
<link>https://www.rawley.xyz</link>
|
||||
<description>
|
||||
Rawley's music and tech blog.
|
||||
</description>
|
||||
<language>en-us</language>
|
||||
<image>
|
||||
<title>rawley.xyz</title>
|
||||
<link>https://www.rawley.xyz</link>
|
||||
<url>https://rss.com/blog/wp-content/uploads/2019/10/social_style_3_rss-512-1.png</url>
|
||||
<width>150</width>
|
||||
<height>150</height>
|
||||
</image>
|
||||
{{range $post := .}}
|
||||
<item>
|
||||
<title>{{$post.Title}}</title>
|
||||
<pubDate>{{$post.Date}}</pubDate>
|
||||
<link>https://rawley.xyz/blog/post/{{$post.Url}}</link>
|
||||
<description>{{$post.Title}}</description>
|
||||
<author>rawleyfowler@gmail.com</author>
|
||||
</item>
|
||||
{{end}}
|
||||
</channel>
|
||||
</rss>
|
||||
@@ -0,0 +1,15 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="keywords" content="blog resume rawley-fowler rawleyportfolio rawleyxyz rawleyfowler RF rawley fowler Rawley Fowler portfolio RawleyFowler RawleyXYZ rawleyXYZ RawleyXYZ">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/static/index.css" rel="stylesheet" type="text/css">
|
||||
<title>session revoked</title>
|
||||
</head>
|
||||
<body>
|
||||
{{ template "nav.tmpl" . }}
|
||||
<h2>403 Your session no longer exists</h2>
|
||||
<p>Click <a href="/">here</a> to return home.</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!--music.tmpl-->
|
||||
{{ template "header.tmpl" . }}
|
||||
<p>
|
||||
This serves as a compilation of every band that I like. Genres may vary, however I mostly listen to Hardcore, various genres of Metal, and Ska.
|
||||
</p>
|
||||
<div class="music-container">
|
||||
{{ range $val := . }}
|
||||
<a href="{{ $val.Url }}" class="music">{{ $val.Name }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ template "footer_no_banners.tmpl" . }}
|
||||
@@ -0,0 +1,9 @@
|
||||
{{ template "header.tmpl" . }}
|
||||
<h2>Work in progress...</h2>
|
||||
<p>
|
||||
This page is not finished yet, check back later!
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://github.com/rawleyfowler/rawleydotxyz">Development progress</a>
|
||||
</p>
|
||||
{{ template "footer_no_banners.tmpl" . }}
|
||||
@@ -0,0 +1,38 @@
|
||||
package utils
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Loads the api key from a local file given by path.
|
||||
// This api key is used to make admins.
|
||||
func LoadApiKey(path string) string {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
panic("Could not find DSN for database...")
|
||||
}
|
||||
defer file.Close()
|
||||
reader := bufio.NewScanner(file)
|
||||
if reader.Scan() {
|
||||
return reader.Text()
|
||||
}
|
||||
panic(path + " is empty...")
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package utils
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import (
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func CreateDatabase(dsn string) (*gorm.DB, error) {
|
||||
var err error
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package utils
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Loads the dsn via OS file system and then returns the value as a string
|
||||
func LoadDSN(dsnPath string) string {
|
||||
file, err := os.Open(dsnPath)
|
||||
if err != nil {
|
||||
panic("Could not find DSN for database...")
|
||||
}
|
||||
defer file.Close()
|
||||
reader := bufio.NewScanner(file)
|
||||
// The dsn should be the first line of the file
|
||||
if reader.Scan() {
|
||||
return reader.Text()
|
||||
}
|
||||
panic(dsnPath + " is empty...")
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package utils
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import (
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func InitializeDatabase(dsn string) *gorm.DB {
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
|
||||
if err != nil {
|
||||
panic("Failed to open database with dsn -> " + dsn)
|
||||
}
|
||||
return db
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package utils
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Serves a static page, based on its name.
|
||||
// Returns a handler func that renders a given page.
|
||||
// Page input t should always be <Page Name>.tmpl
|
||||
// Also sets the title for the page if it allows for dyanmic titles.
|
||||
// index.tmpl will have an empty title.
|
||||
func ServePage(t string) gin.HandlerFunc {
|
||||
title := strings.Split(t, ".")[0]
|
||||
if title == "index" {
|
||||
title = ""
|
||||
} else {
|
||||
title = " | " + title
|
||||
}
|
||||
return func(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, t, struct{ Title string }{Title: title})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package utils
|
||||
|
||||
/*
|
||||
Copyright (C) 2022 Rawley Fowler
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.Rawley Fowler, 2022
|
||||
*/
|
||||
|
||||
import "time"
|
||||
|
||||
func TimeClearMap(m map[string]int64, t int64) {
|
||||
time.Sleep(time.Duration(t) * time.Millisecond)
|
||||
for k := range m {
|
||||
if m[k] < time.Now().UnixMilli()+t {
|
||||
delete(m, k)
|
||||
}
|
||||
}
|
||||
}
|
||||