Compare commits

Author SHA1 Message Date
Rawley Fowler 3a3906ebc7 Update and rename Dockerfile to Containerfile 2022-05-10 14:02:58 -06:00
Rawley Fowler 5afbd3e15b Create Dockerfile 2022-05-10 13:24:44 -06:00
ivan 229ad7c457 added link to rss 2022-05-09 21:46:00 -06:00
ivan e75476daf5 added wip to tunes page 2022-05-09 21:14:57 -06:00
ivan 69335521f6 migrated music model 2022-05-09 21:09:36 -06:00
ivan ba585a6179 registered music group 2022-05-09 19:33:20 -06:00
ivan 431da4a96d fixed tunes tmpl 2022-05-09 19:31:40 -06:00
ivan 8cc150569b take one on music feature 2022-05-09 19:19:16 -06:00
ivan e5bf0c952c added music, and adjusted rss 2022-05-09 18:42:18 -06:00
ivan 2089705496 fixed rss feed 2022-05-09 18:28:53 -06:00
ivan 2c3787d3f0 fixed xml 2022-05-08 20:35:55 -06:00
ivan f2380057d6 fixed weird xml string 2022-05-08 19:55:32 -06:00
ivan fa1b95a7dd fixed xml & 2022-05-08 19:32:01 -06:00
ivan b18c571e50 fix typo in rss feed 2022-05-08 15:30:50 -06:00
ivan cde073a274 added RSS feed 2022-05-08 15:00:30 -06:00
ivan d5799a888d update init script 2022-05-07 11:24:31 -05:00
ivan 88ef95cc73 Merge branch 'main' of github.com:rawleyfowler/rawleydotxyz 2022-04-30 11:09:17 -06:00
ivan 95b1822a7b added comments, updated go.mod to reflect new repo 2022-04-30 11:07:14 -06:00
Rawley Fowler 37c291d0fc Update README.md 2022-04-30 11:04:13 -06:00
rawleyIfowler 62b614a790 fixed git links 2022-04-27 18:47:14 -05:00
Rawley Fowler 3a50e3391a fix copyright 2022-04-26 17:35:37 -05:00
Rawley Fowler 677146dca4 Add copyright disclaimer 2022-04-26 17:33:34 -05:00
Rawley Fowler 179c0196f1 Update main.sh 2022-04-26 15:19:31 -05:00
Rawley Fowler 013facbade Update README.md 2022-04-26 15:15:45 -05:00
Rawley Fowler 0aff8f5c54 Update README.md 2022-04-26 14:59:51 -05:00
ivan 1d356df03b fixed spelling 2022-04-21 15:34:16 +00:00
rawleyIfowler 64863839dd fixed mobile nav bar 2022-04-17 12:02:38 -05:00
rawleyIfowler bb4e77415a align-items: baseline; 2022-04-17 11:57:47 -05:00
rawleyIfowler 90201c8981 added id to title, changed nav bar items 2022-04-17 11:55:52 -05:00
rawleyIfowler b8d3ff951c adjusted header 2022-04-17 11:53:26 -05:00
rawleyIfowler aecac47bfd changed title size 2022-04-17 11:50:16 -05:00
rawleyIfowler c3ca3351a7 adjusted centering of nav items 2022-04-17 11:36:15 -05:00
rawleyIfowler 34b91ab454 aligned nav items in the cente 2022-04-17 11:32:38 -05:00
rawleyIfowler 14bef1b902 changed navbar 2022-04-16 19:31:45 -05:00
rawleyIfowler bd581a77e7 fixed scuffed padding 2022-04-16 17:57:46 -05:00
rawleyIfowler ac4928c471 added padding to nav 2022-04-16 17:33:59 -05:00
rawleyIfowler 0c0b01eb2d fixed nav bar for mobile 2022-04-16 16:31:55 -05:00
rawleyIfowler fbeae313b4 fixed mobile nav bar 2022-04-16 16:25:52 -05:00
rawleyIfowler b14d88ab4a added media query for nav bar 2022-04-16 15:58:31 -05:00
rawleyIfowler 540ec296f7 increased font size of h1 tag 2022-04-16 15:17:06 -05:00
rawleyIfowler 8c173c1707 fixed clamp 2022-04-16 13:14:18 -05:00
rawleyIfowler bae62cf86c adjusted header sizes 2022-04-16 13:10:28 -05:00
rawleyIfowler ff5cc951eb changed font weight for h4 and adjusted width of content 2022-04-16 13:06:56 -05:00
rawleyIfowler 9f35fb8f67 updated css to better reflect desired heading sizes 2022-04-16 13:04:59 -05:00
rawleyIfowler 5187c4fbab update readme 2022-04-16 12:55:43 -05:00
rawleyIfowler 14319a147a changed navbar item size, added donation page 2022-04-16 12:54:57 -05:00
rawleyIfowler 04bd2510f5 Merge branch 'main' of gitlab.com:rawleyIfowler/rawleydotxyz 2022-04-16 12:05:34 -05:00
rawleyIfowler b9d8e0a2a4 fixed: blog post inconsistencies, hr at end of blog post, and added br before footers 2022-04-16 12:05:18 -05:00
ivan d96ba8b2a5 fix code blocks on dark theme. 2022-04-03 03:33:40 +00:00
ivan 1453faa7f3 Fix dead link 2022-04-03 03:32:26 +00:00
rawleyIfowler bbdff02015 moved create admin form 2022-04-01 12:12:22 -05:00
rawleyIfowler b2abdb5cff added go banner to landing 2022-04-01 12:06:10 -05:00
rawleyIfowler 578008a1e5 updated project resume 2022-03-29 20:25:42 -05:00
rawleyIfowler a119cd145c adjusted resume, added new skill keywords 2022-03-22 19:11:27 -05:00
rawleyIfowler 417cca85f2 added favicon 2022-03-22 19:01:17 -05:00
rawleyIfowler 2223c4a5d0 adjusted code blocks part 7 2022-03-13 10:14:33 -05:00
rawleyIfowler bb2baaa98c adjusted code blocks part 6 2022-03-13 10:12:39 -05:00
rawleyIfowler 1cfed139b8 adjusted code blocks part 5 2022-03-13 10:11:46 -05:00
rawleyIfowler 9328fed00c adjusted code blocks part 4 2022-03-13 10:10:38 -05:00
rawleyIfowler 93d86aadf9 adjusted code blocks part 3 2022-03-13 10:08:08 -05:00
rawleyIfowler ff557bd73d changed css of pre objects to make them easier to read 2022-03-13 10:00:41 -05:00
rawleyIfowler ade5d1fd09 added overflow to css 2022-03-13 09:55:45 -05:00
rawleyIfowler 736d885f6e Fixed post updating 2022-03-13 09:46:56 -05:00
rawleyIfowler 2b62838435 fixed blog_repo from inserting empty fields on update 2022-03-12 12:29:07 -06:00
rawleyIfowler 04538fa31c added line spaces to code blocks with css 2022-03-12 12:22:14 -06:00
rawleyIfowler 459bb26042 removed error log for testing 2022-03-12 12:17:16 -06:00
rawleyIfowler d66c1fd77b Merge branch 'main' of gitlab.com:rawleyIfowler/rawleydotxyz 2022-03-12 12:16:26 -06:00
rawleyIfowler 0299e2aabc adjusted css of code blocks 2022-03-12 12:14:40 -06:00
ivan bfd615667f Update resume.tmpl 2022-03-12 16:30:54 +00:00
rawleyIfowler e08d76eae1 changed forbidden requests to http unauthorized 2022-03-11 12:18:46 -06:00
rawleyIfowler 40e03d7e08 changed forbidden requests to http unauthorized 2022-03-11 12:18:10 -06:00
ivan 218bfaee8f Update LICENSE 2022-03-10 17:29:40 +00:00
ivan b7393e7e96 Update resume.tmpl 2022-03-10 16:14:50 +00:00
ivan f6dac2ec59 Update resume.tmpl 2022-03-10 16:09:58 +00:00
ivan 348b36941c Updated code class styles 2022-03-10 14:24:15 +00:00
ivan e1ab391c9c Update index.css 2022-03-10 14:17:24 +00:00
ivan edbad824cc Update index.css 2022-03-10 14:10:53 +00:00
ivan a1fc48b9b5 Update index.css 2022-03-10 14:10:14 +00:00
ivan cf7ca17aca Update index.css 2022-03-10 03:07:15 +00:00
ivan 481f14543d updated color scheme 2022-03-09 15:25:37 +00:00
ivan e9f8f344a7 Update index.css 2022-03-09 15:18:18 +00:00
ivan e6675adda8 Fixed blog posts, added disclaimer text to be subtext 2022-03-09 15:13:15 +00:00
ivan 4763b636ef fix a tag colors for dark mode 2022-03-09 15:08:27 +00:00
ivan 5baee02a67 fix css coloring on dark mode 2022-03-09 15:07:55 +00:00
ivan edda39e0ae Merge branch 'wip' into 'main'
WIP Branch completed: Overhaul of site logic, removed comments, repositories

See merge request rawleyIfowler/rawleydotxyz!1
2022-03-09 04:20:27 +00:00
ivan 860115cbcc WIP Branch completed: Overhaul of site logic, removed comments, repositories 2022-03-09 04:20:27 +00:00
rawleyIfowler cd7a34be0c added tdd for comment repo, and comment repo 2022-03-04 17:34:43 -06:00
rawleyIfowler 4c3501cc97 added bot punishing feature 2022-03-03 21:33:39 -06:00
rawleyIfowler 4a5ad21b27 fix: issue with capturing correct client ip 2022-03-03 13:21:26 -06:00
rawleyIfowler df4cc066f2 fixed localip tracing bug with c.ClientIP() 2022-03-03 13:16:01 -06:00
rawleyIfowler a137a2fb8f added code span to css styles 2022-03-02 18:45:08 -06:00
rawleyIfowler b6d7d2b2ba removed redundant file 2022-03-02 18:38:22 -06:00
rawleyIfowler 950fc4a4a0 adjusted performance and post urls 2022-03-02 18:37:32 -06:00
rawleyIfowler 976b7f0ca8 adjusted font size 2022-03-02 14:01:26 -06:00
rawleyIfowler 0ffe1cf33d fixed bug where you could comment on non existant blog posts 2022-03-02 13:56:36 -06:00
rawleyIfowler 22811c8b0a adjusted site css to be slightly wider 2022-03-02 13:45:50 -06:00
rawleyIfowler 383e8de3b5 changed query to be descending for blog posts, so newest is first 2022-03-02 13:30:46 -06:00
rawleyIfowler d1ef7a095c added css fill in for subtext, and fixed title bug for admin making posts 2022-03-02 11:14:54 -06:00
rawleyIfowler 8604f5d720 added admin error 2022-03-02 11:08:30 -06:00
ivan 8bc5b567a9 Delete e 2022-03-02 17:05:17 +00:00
rawleyIfowler 8bac0d8407 added admin control panel for making posts, with basic authentication 2022-03-02 10:59:56 -06:00
rawleyIfowler a03c75a3e2 removed redundant error log 2022-03-02 07:42:36 -06:00
rawleyIfowler 402f36534f added admin page, adjusted way of creating posts 2022-03-02 07:42:14 -06:00
rawleyIfowler cefeeec11a adjusted spam filter to work for values around 20 characters, to stop spam of short proportions 2022-03-01 16:15:22 -06:00
rawleyIfowler ede5865307 fixed broken title for posting comments screens 2022-02-28 09:12:45 -06:00
rawleyIfowler 161f68ba85 added dynamic titles to pages for SEO 2022-02-27 12:24:52 -06:00
rawleyIfowler a2779c487c added unescaping of html values for blog posts. The function can be reused elsewhere aswell. 2022-02-27 11:00:39 -06:00
rawleyIfowler 0fae702255 minor adjustments to blog controller 2022-02-26 22:05:27 -06:00
rawleyIfowler c3a22f498b Cutting first char from comment when testing for spam 2022-02-26 15:08:41 -06:00
rawleyIfowler c296296faf added spam filter 2022-02-26 14:41:10 -06:00
rawleyIfowler 90c6d84a3e added list of why your comment might have failed 2022-02-26 13:40:22 -06:00
rawleyIfowler 807ca8c5e6 added captcha system that generates every 5 minutes 2022-02-26 12:16:53 -06:00
rawleyIfowler 28e52ad4fa added post failure screen 2022-02-26 11:45:02 -06:00
rawleyIfowler 2980011a16 added char mins for fields on comments 2022-02-26 11:37:14 -06:00
rawleyIfowler 1d09733e42 fixed workaround for empty set of blog posts on blog page 2022-02-26 10:27:01 -06:00
rawleyIfowler a13e1f5c87 added catch for empty blog posts 2022-02-26 10:20:08 -06:00
rawleyIfowler b1c76c2094 added template for spam posts, added spam filter, added new js to redirect 2022-02-26 10:15:57 -06:00
rawleyIfowler 7d8faeed62 fixed word breaking 2022-02-25 21:54:23 -06:00
rawleyIfowler d5719e934a fixed infinite comment length 2022-02-25 21:49:38 -06:00
rawleyIfowler 81b2a9a7f9 adjusted font size for post records 2022-02-25 21:34:46 -06:00
rawleyIfowler 0f37dd0786 added styles for individual comments
added margins to comments

adjusted template for comments
2022-02-25 21:27:33 -06:00
rawleyIfowler 589297bceb added 500 error screen, and adjusted index: 2022-02-25 20:01:06 -06:00
rawleyIfowler dca2efc52d added comment posted screen 2022-02-25 18:36:02 -06:00
rawleyIfowler 598f372acd added comments and added a comment end point 2022-02-25 17:59:48 -06:00
rawleyIfowler 979ef41aec added submit button to comment template 2022-02-24 21:59:58 -06:00
rawleyIfowler 8e389c7a7f added a 404 to bad blog posts 2022-02-23 19:24:31 -06:00
rawleyIfowler 4f9fcdc898 adjusted css for blog posts page 2022-02-23 17:14:15 -06:00
rawleyIfowler c5ccbec4c7 added content to blog posts, forgot about that somehow ... 2022-02-23 17:03:48 -06:00
rawleyIfowler 2ebe41eb86 added directory flag to daemon script 2022-02-23 16:51:36 -06:00
rawleyIfowler 4d688d71d3 piped stderr and stdout to stdout log file 2022-02-23 16:42:17 -06:00
rawleyIfowler 3acaa20611 fixed typo on startup script 2022-02-23 16:37:53 -06:00
rawleyIfowler f7c6407ccb simplified startup script to make it easier to deploy on my server 2022-02-23 16:37:01 -06:00
rawleyIfowler a1bdc8ca31 adjusted startup script 2022-02-23 12:08:57 -06:00
rawleyIfowler 3d69570c00 added startup script 2022-02-23 12:05:49 -06:00
rawleyIfowler 581e7e258a added blog posting 2022-02-23 11:41:29 -06:00
rawleyIfowler 724f7b0034 added end point for posting blog posts, added cookie based authentication via api key 2022-02-22 09:22:40 -06:00
rawleyIfowler 714a12d374 adjusted font size 2022-02-20 17:35:05 -06:00
rawleyIfowler 86ed8cf878 adjusted font weight of h4 2022-02-20 17:31:30 -06:00
rawleyIfowler edb3839bd7 adjusted h3 bottom margin 2022-02-20 17:28:44 -06:00
rawleyIfowler e1d1061e52 added h4 styles 2022-02-20 17:26:20 -06:00
rawleyIfowler a9b336b2e7 removed br spam from resume template 2022-02-20 17:05:35 -06:00
rawleyIfowler d4162d143d fleshed out resume template 2022-02-20 17:02:17 -06:00
rawleyIfowler 2a48444234 adjusted meta content 2022-02-20 16:05:52 -06:00
rawleyIfowler 25df70370a adjusted nav height 2022-02-20 16:01:50 -06:00
rawleyIfowler b52aa497eb added blog comment form to blog posts 2022-02-16 21:39:16 -06:00
rawleyIfowler 1bd6865c40 layed out the individual blog post template 2022-02-16 16:08:24 -06:00
rawleyIfowler 2d47cbe494 adjusted font size for mobile 2022-02-16 15:39:34 -06:00
rawleyIfowler 70f3e03e58 added subtext to opinion disclaimer: 2022-02-15 09:25:12 -06:00
rawleyIfowler 88ad8176ee added else to for loop in blog posts 2022-02-15 09:15:57 -06:00
rawleyIfowler 4a56b3f907 added opinion disclaimer to footer 2022-02-14 16:51:49 -06:00
rawleyIfowler 92ab929ce3 added period to footer 2022-02-13 21:02:09 -06:00
rawleyIfowler d78282bdef changed footer message 2022-02-13 19:09:40 -06:00
rawleyIfowler e949c64007 changed sub text size to be legible 2022-02-13 19:08:25 -06:00
rawleyIfowler 5a5bd88f27 adjusted footer text size 2022-02-13 19:05:54 -06:00
rawleyIfowler 9246e6e4fd configured mysql dsn configuration 2022-02-13 18:38:26 -06:00
rawleyIfowler 0221e3f63a added new footer template for pages (excluding index) 2022-02-13 14:09:44 -06:00
rawleyIfowler 2227a2473d implemented database connection for blog, added blog templates, and cresated a bootstrap submodule 2022-02-13 14:04:31 -06:00
rawleyIfowler f5383f27c7 changed a hover color to be not transparent, providing better coverage and accessibility 2022-02-12 15:34:55 -06:00
rawleyIfowler 2df046eecf changed a tag to be only a single line, so hover effect isn't borked 2022-02-10 09:56:28 -06:00
rawleyIfowler 82604c90a4 fixed grammar of index 2022-02-10 09:54:15 -06:00
rawleyIfowler b655906fb2 added website link to front page 2022-02-10 09:53:18 -06:00
rawleyIfowler b513516d09 fixed grammar on index.tmpl 2022-02-10 09:50:56 -06:00
rawleyIfowler 2820b57d25 added openbsd banner locally, and adjusted css on nav bar 2022-02-10 09:46:01 -06:00
rawleyIfowler c4f7e9b453 updated gitignore 2022-02-09 21:43:21 -06:00
rawleyIfowler b1f60d574c added nginx 2022-02-09 21:40:48 -06:00
rawleyIfowler 4d1cb949a0 added git ignore 2022-02-09 19:51:13 -06:00
rawleyIfowler 98bc86558c removed swp file 2022-02-09 19:50:41 -06:00
rawleyIfowler 87209e9901 aligned content to the center instead of to the left 2022-02-09 19:49:29 -06:00
rawleyIfowler 31e34572aa fixed a hover css, now correctly moves underline up 2022-02-08 21:10:23 -06:00
rawleyIfowler a2eceb7b7e added contact page, wip page, and removed background image 2022-02-07 14:08:16 -06:00
rawleyIfowler 36cb46c7e7 updated resume and adjusted skill section 2022-02-07 10:17:06 -06:00
rawleyIfowler 62f7239f85 Merge branch 'main' of https://gitlab.com/rawleyifowler/site-take-two 2022-02-07 08:25:09 -06:00
rawleyIfowler 34b20aa485 updated index text to be less happy 2022-02-07 08:24:56 -06:00
ivan 9fb0f94091 Add README.md 2022-02-07 04:15:50 +00:00
ivan b6b8c482d9 Add LICENSE 2022-02-06 23:34:55 +00:00
rawleyIfowler 4389b07f39 init 2022-02-06 17:33:47 -06:00
84 changed files with 2563 additions and 1726 deletions
+8 -11
View File
@@ -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
-3
View File
@@ -1,3 +0,0 @@
localhost
file_server
+11
View File
@@ -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"]
+674
View File
@@ -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>.
+16
View File
@@ -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.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

+43
View File
@@ -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"))
}
+205
View File
@@ -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)
}
+18
View File
@@ -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
*/
+83
View File
@@ -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(), "&", "&amp;", -1)))
}
+18
View File
@@ -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
*/
+49
View File
@@ -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)
}
+10
View File
@@ -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
)
+63
View File
@@ -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=
-22
View File
@@ -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;
}
-95
View File
@@ -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>
+45
View File
@@ -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")
}
Executable
+9
View File
@@ -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
+11
View File
@@ -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
}
+33
View File
@@ -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
}
+26
View File
@@ -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"`
}
+20
View File
@@ -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
}
-48
View File
@@ -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";
-47
View File
@@ -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"
);
-59
View File
@@ -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="/">&lt;&lt; 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>
-81
View File
@@ -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="/">&lt;&lt; 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&amp;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 &amp; 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>
-72
View File
@@ -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="/">&lt;&lt; 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_&lt;reftype&gt;_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 =&gt; /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 =&gt; /home) {
my $app = shift;
my $user_name = $app-&gt;session('user');
return "Welcome back $user_name!";
}
sub home_page : Route(post =&gt; /login) {
my $app = shift;
my $password = $app-&gt;body_parameters-&gt;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>
-145
View File
@@ -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="/">&lt;&lt; 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> &gt; 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>' &gt;&gt; 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> =&gt; <span class="hljs-function"><span class="hljs-keyword">sub</span> </span>{
<span class="hljs-keyword">shift</span>-&gt;render( <span class="hljs-string">text =&gt;</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-&gt;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">&lt;VirtualHost *:80&gt;</span>
<span class="hljs-section">&lt;Location /&gt;</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">&lt;/Location&gt;</span>
<span class="hljs-section">&lt;/VirtualHost&gt;</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 &amp;&amp; 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>
-53
View File
@@ -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="/">&lt;&lt; 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>
-32
View File
@@ -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="/">&lt;&lt; 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>
-132
View File
@@ -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="/">&lt;&lt; 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; # &lt;!DOCTYPE html&gt;&lt;html&gt; ...
</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; # &lt;div&gt;Hello, World!&lt;a href="https://www.google.com"&lt; ...
</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 =&gt; $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>
-20
View File
@@ -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="/">&lt;&lt; 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>
-102
View File
@@ -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="/">&lt;&lt; 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="/">&lt;&lt; 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&lt;A, UUID&gt;, JpaSpecificationExecutor&lt;A&gt; {
@Override
@EntityGraph(attributePaths = { "bs", "B.cs" }) // Where B's are children of A's, and C's are children of B's.
Page&lt;A&gt; findAll(@Nullable Specification&lt;A&gt; 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>
-50
View File
@@ -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="/">&lt;&lt; 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>
-44
View File
@@ -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="/">&lt;&lt; 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>
-59
View File
@@ -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="/">&lt;&lt; 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>
-558
View File
@@ -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="/">&lt;&lt; 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 =&gt; self.name, age =&gt; 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-&gt;new( name =&gt; $name, age =&gt; $age )-&gt;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 =&gt; $self-&gt;{name}, age =&gt; $self-&gt;{age} };
}
sub new {
my ( $class, $name, $age ) = @_;
my $self = { name =&gt; $name, age =&gt; $age };
return bless $self, $class;
}
1;
package main;
use strict;
use warnings;
my $name = 'Tim';
my $age = 12;
for ( 1 .. 1000000 ) {
my $user = Action-&gt;new( $name, $age )-&gt;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 =&gt; $self-&gt;{name}, age =&gt; $self-&gt;{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 =&gt; $name, age =&gt; $age };
return bless $self, $class;
}
1;
package main;
use strict;
use warnings;
my $name = 'Tim';
my $age = 12;
for ( 1 .. 1000000 ) {
my $user = Action-&gt;new( $name, $age )-&gt;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 =&gt; $name, age =&gt; $age };
}
}
my $name = 'Tim';
my $age = 12;
for ( 1 .. 1000000 ) {
Action-&gt;new( name =&gt; $name, age =&gt; $age )-&gt;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 =&gt; (
is =&gt; 'ro',
required =&gt; 1
);
has age =&gt; (
is =&gt; 'ro',
required =&gt; 1
);
sub execute {
my $self = shift;
return +{ name =&gt; $self-&gt;name, age =&gt; $self-&gt;age };
}
1;
package main;
use strict;
use warnings;
my $name = 'Tim';
my $age = 12;
for ( 1 .. 1000000 ) {
my $user = Action-&gt;new( name =&gt; $name, age =&gt; $age )-&gt;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 =&gt; (
is =&gt; 'ro',
isa =&gt; Str,
required =&gt; 1
);
has age =&gt; (
is =&gt; 'ro',
isa =&gt; Int,
required =&gt; 1
);
sub execute {
my $self = shift;
return +{ name =&gt; $self-&gt;name, age =&gt; $self-&gt;age };
}
1;
package main;
use strict;
use warnings;
my $name = 'Tim';
my $age = 12;
for ( 1 .. 1000000 ) {
my $user = Action-&gt;new( name =&gt; $name, age =&gt; $age )-&gt;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 =&gt; (
is =&gt; 'ro',
required =&gt; 1
);
has age =&gt; (
is =&gt; 'ro',
required =&gt; 1
);
sub execute {
my $self = shift;
return +{ name =&gt; $self-&gt;name, age =&gt; $self-&gt;age };
}
1;
package main;
use strict;
use warnings;
my $name = 'Tim';
my $age = 12;
for ( 1 .. 1000000 ) {
my $user = Action-&gt;new( name =&gt; $name, age =&gt; $age )-&gt;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 =&gt; (
is =&gt; 'ro',
isa =&gt; Str,
required =&gt; 1
);
has age =&gt; (
is =&gt; 'ro',
isa =&gt; Int,
required =&gt; 1
);
sub execute {
my $self = shift;
return +{ name =&gt; $self-&gt;name, age =&gt; $self-&gt;age };
}
1;
package main;
use strict;
use warnings;
my $name = 'Tim';
my $age = 12;
for ( 1 .. 1000000 ) {
my $user = Action-&gt;new( name =&gt; $name, age =&gt; $age )-&gt;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__-&gt;meta-&gt;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 =&gt; $!name, age =&gt; $!age);
}
}
my $name = 'Tim';
my $age = 12;
for 1..1000000 -&gt; $ {
my $user = Action.new(name =&gt; $name, age =&gt; $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="/">&lt;&lt; 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>
-17
View File
@@ -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="/">&lt;&lt; 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>
+93
View File
@@ -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
}
+67
View File
@@ -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")
}
}
+106
View File
@@ -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
}
+50
View File
@@ -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")
}
}
+61
View File
@@ -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
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

+231
View File
@@ -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;
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

+20
View File
@@ -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>
+29
View File
@@ -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" }}
+14
View File
@@ -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" . }}
+11
View File
@@ -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" . }}
+31
View File
@@ -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>
+22
View File
@@ -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" }}
+15
View File
@@ -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>
+8
View File
@@ -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>
+15
View File
@@ -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>
+12
View File
@@ -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" . }}
+12
View File
@@ -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" . }}
+15
View File
@@ -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>
+8
View File
@@ -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" . }}
+27
View File
@@ -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>
+19
View File
@@ -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>
+20
View File
@@ -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>
+63
View File
@@ -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" . }}
+27
View File
@@ -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>
+15
View File
@@ -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>
+11
View File
@@ -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" . }}
+9
View File
@@ -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" . }}
+38
View File
@@ -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...")
}
+32
View File
@@ -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
}
+38
View File
@@ -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...")
}
+31
View File
@@ -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
}
+42
View File
@@ -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})
}
}
+29
View File
@@ -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)
}
}
}