Compare commits

..

371 Commits

Author SHA1 Message Date
f03318f7dc Fixed some description text
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m4s
2026-03-03 13:23:13 -07:00
1db15c64e1 Deps 2026-03-01 01:16:31 -07:00
a98bf7c7c6 Update config.ts
All checks were successful
Docker Deploy / build-and-push (push) Successful in 2m41s
2026-02-25 23:45:49 -07:00
c3c8867a37 Update 2026-infra-setup.md
All checks were successful
Docker Deploy / build-and-push (push) Successful in 2m39s
2026-02-25 23:33:23 -07:00
2430f89737 Update 2026-infra-setup.md
All checks were successful
Docker Deploy / build-and-push (push) Successful in 2m46s
2026-02-25 19:54:16 -07:00
7925fab524 Updated infra post
All checks were successful
Docker Deploy / build-and-push (push) Successful in 2m50s
2026-02-25 18:20:29 -07:00
271dad89a1 Bun-ify
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m57s
2026-02-24 23:25:47 -07:00
47946c0703 deps
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m17s
2026-02-23 11:32:53 -07:00
4b78414562 Updated resume
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m8s
2026-02-16 23:52:02 -07:00
0cf1cfa2b0 Added Dart
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m51s
2026-02-16 14:13:28 -07:00
399cff82b0 Theme change
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m14s
2026-02-15 00:42:13 -07:00
cf163bb0b2 Add RSS links to each post
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m35s
2026-02-14 18:00:49 -07:00
cc3a408050 Added Matrix and Mastodon for fediiiiii
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m53s
2026-02-14 16:52:17 -07:00
46c42cd765 Optimized docker
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m1s
2026-02-12 15:04:10 -07:00
89c1c739c1 Re-worked icons
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m48s
2026-02-12 14:22:59 -07:00
33dfea1802 Added Haschel
Some checks failed
Docker Deploy / build-and-push (push) Failing after 4m8s
2026-02-11 23:11:13 -07:00
0efb72fffd Fixed opengraph again
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m4s
2026-02-07 13:18:18 -07:00
dce37681af Opengraph
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m39s
2026-02-07 00:28:33 -07:00
6b77ce091d Upgraded the projects view. Looks and acts MUCH nicer
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m21s
2026-02-03 11:16:07 -07:00
ba1193896f Improved posts UX
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m32s
2026-02-03 11:00:13 -07:00
63282cf34d New year, new post!
All checks were successful
Docker Deploy / build-and-push (push) Successful in 5m59s
2026-02-03 10:29:03 -07:00
3eac226630 Deps
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m17s
2026-01-31 23:25:49 -07:00
9518a0f18b Added 404
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m13s
2026-01-25 00:27:35 -07:00
74304dba4d Added 404 2026-01-25 00:26:45 -07:00
0512645035 More optimizations
All checks were successful
Docker Deploy / build-and-push (push) Successful in 5m6s
2026-01-24 23:58:30 -07:00
09fdbf7ec7 Updated nav a bit
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m35s
2026-01-24 18:57:44 -07:00
d3844a5870 Updated repos
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m48s
2026-01-24 18:30:50 -07:00
d06a453461 Some optimizations
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m25s
2026-01-24 18:23:59 -07:00
c048d0d47a ???
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m47s
2026-01-24 17:24:18 -07:00
a26c990a21 4.0.0
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled
2026-01-24 17:24:00 -07:00
210edc771c deps
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m11s
2026-01-16 10:59:15 -07:00
154bbc9669 Deps
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m43s
2026-01-14 18:08:26 -07:00
d354b35d05 Catppuccin Macciato because it fucking rules dude
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m8s
2025-12-31 19:58:37 -07:00
4c1def9cf9 Deps
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m16s
2025-12-30 00:43:08 -07:00
a29e81f05d Update ascently-climbing-tracker.md
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m29s
2025-12-27 22:36:56 -07:00
9a9705e8f7 Bumped Node
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m49s
2025-12-25 23:39:04 -07:00
4954fe855f Re-adding nix
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m5s
2025-12-25 02:02:45 -07:00
f4d0ae2780 Added nix icon
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m8s
2025-12-25 01:29:49 -07:00
e9fed3a5b7 Update linux-for-new-users.md
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m24s
2025-12-23 08:53:06 -07:00
d6d75eff37 Fixed RSS date/time
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m20s
2025-12-21 01:45:28 -07:00
ebb980f275 Ehhh lets not suggest KDE on Pop
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m9s
2025-12-21 01:16:25 -07:00
3b5f33aaf7 New article!
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m36s
2025-12-21 01:09:02 -07:00
70ee8a2c42 Optimizations + minor cleanups
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m15s
2025-12-18 00:30:01 -07:00
203f83bfcb Updated UX to remove the weird looking cards. No more cards!
All checks were successful
Docker Deploy / build-and-push (push) Successful in 5m47s
2025-12-15 10:28:48 -07:00
4ab28078e8 Deps
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m15s
2025-12-12 22:44:15 -07:00
a8e017caf2 ???
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m53s
2025-12-12 10:53:43 -07:00
ee10cbaf60 Update projects
Some checks failed
Docker Deploy / build-and-push (push) Failing after 3m15s
2025-12-12 00:45:45 -07:00
88c10f9690 Fixed some leftovers from a depricated feature
Some checks failed
Docker Deploy / build-and-push (push) Failing after 5m46s
2025-12-02 10:21:04 -07:00
0998bacd86 Simplified the PDF gen quite a bit :)
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m37s
2025-12-01 18:16:17 -07:00
b87357c175 Made card grids not ass
All checks were successful
Docker Deploy / build-and-push (push) Successful in 6m7s
2025-11-23 01:51:14 -07:00
b159236e59 Fixed class warnings
All checks were successful
Docker Deploy / build-and-push (push) Successful in 11m54s
2025-11-22 17:33:52 -07:00
be20d75288 Removed release and commit count its useless.
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled
2025-11-22 17:31:52 -07:00
3b1f9ae02c Remove focus
All checks were successful
Docker Deploy / build-and-push (push) Successful in 8m23s
2025-11-22 17:11:01 -07:00
a09a2faa32 Content updates
All checks were successful
Docker Deploy / build-and-push (push) Successful in 9m39s
2025-10-20 15:08:13 -06:00
517becb322 Small edits
All checks were successful
Docker Deploy / build-and-push (push) Successful in 5m27s
2025-10-16 23:11:46 -06:00
0fb9fd2009 Added phone
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m5s
2025-10-16 11:15:16 -06:00
50a4ff1332 New blog and homepage focus
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m43s
2025-10-16 10:52:20 -06:00
0d474804ec Cleaned up link code for projects and added git vs web links
All checks were successful
Docker Deploy / build-and-push (push) Successful in 5m7s
2025-10-14 12:59:10 -06:00
94146782cb ???
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m6s
2025-10-14 00:47:36 -06:00
fb260d499b Deps
Some checks failed
Docker Deploy / build-and-push (push) Failing after 2m44s
2025-10-13 23:48:14 -06:00
6daeac418b Fixed projects 2025-10-13 23:48:02 -06:00
a837b9380b Fixed link
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m9s
2025-10-09 10:44:36 -06:00
0b4ba7ba63 Deps and bump
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled
2025-10-09 09:36:47 -06:00
7d14ba51fa IDK why I didn't do this... using server islands :') 2025-10-09 09:36:08 -06:00
9b98476df6 3.1.0 - Added Gitea integration for Projects page
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m25s
2025-10-08 16:12:26 -06:00
50e1627ea3 Update resume.toml
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m31s
2025-10-02 01:18:17 -06:00
18cd6511c9 Resume optimization
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m1s
2025-10-01 22:25:46 -06:00
6d380ec376 Fixed tech used section
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m27s
2025-10-01 10:23:13 -06:00
1f6e7a2552 Deps
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m58s
2025-10-01 09:28:27 -06:00
0aab89c58c Update resume.toml 2025-10-01 09:27:58 -06:00
75931d4a43 3.0.0 - Dependency updates, improved typesafe config, improve typing
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m44s
2025-09-22 15:07:03 -06:00
6c9fabe770 Resume update
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m1s
2025-09-10 01:23:57 -06:00
fd48065550 pls
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m5s
2025-09-04 00:04:42 -06:00
a2a3b114dd Added a new project and fixed scrollupbutton 2025-09-04 00:03:02 -06:00
08537db2ab Oops
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m15s
2025-08-26 16:44:01 -06:00
c78ff8c37d Anotha one
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m35s
2025-08-19 21:19:19 -06:00
87e4d54059 MoaR
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m40s
2025-08-19 21:14:22 -06:00
294a8f2ad3 More updates
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m36s
2025-08-19 21:09:13 -06:00
4f570af33e Moar!
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m47s
2025-08-19 16:02:36 -06:00
4b30c8f794 More updates
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m32s
2025-08-19 15:26:57 -06:00
f5d622f857 Deps + Resume Update
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m49s
2025-08-19 14:23:08 -06:00
9a846f5d76 Update Resume
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m16s
2025-08-18 16:11:30 -06:00
9373b2894a Fixed more styling
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m38s
2025-08-18 11:57:19 -06:00
e053b59501 Remove period
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m37s
2025-08-18 11:50:07 -06:00
ec58d44b9d 2.0.0 - Overhaul
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m13s
2025-08-18 11:44:55 -06:00
bd71602d95 More misc style changes
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m32s
2025-08-14 15:35:54 -06:00
c2063f6feb CSS updates + deps
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled
2025-08-14 15:32:24 -06:00
6c4d1b53c6 Update README.md
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m25s
2025-08-13 15:24:07 +00:00
96add46a4d Typo
All checks were successful
Docker Deploy / build-and-push (push) Successful in 2m54s
2025-08-12 16:36:16 -06:00
fe8dc4b794 Style changes and new blog post
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled
2025-08-12 16:35:47 -06:00
9de5f46201 Resume Update
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m53s
2025-08-12 11:22:02 -06:00
485d1eaa34 Deps
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m34s
2025-08-12 01:38:28 -06:00
0e457c0c82 1.1.1 - Updated projects
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled
2025-08-12 01:37:44 -06:00
0d5dd82fd4 Deps
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m20s
2025-08-06 15:06:04 -06:00
f85cf0c719 Moved from nix-shell -> flakes
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m27s
2025-07-25 16:51:25 -06:00
14de7b0d22 One more
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m36s
2025-07-25 14:42:13 -06:00
d19830a6fa More format changes
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled
2025-07-25 14:38:25 -06:00
805eb86848 Nav border
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m44s
2025-07-25 11:52:41 -06:00
cb8d38b2d1 Resume Updates
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m40s
2025-07-25 11:38:51 -06:00
df8ff3ab20 CSS compat is hard apparently
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m9s
2025-07-23 22:48:56 -06:00
1082834f33 Various style updates
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m44s
2025-07-22 17:58:26 -06:00
bda8535c51 merge
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m34s
2025-07-22 16:49:57 -06:00
66723a2ee1 Button changes 2025-07-22 16:48:33 -06:00
e2afd3f414 Several changes :)
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m16s
2025-07-18 17:50:51 -06:00
e656c5e407 Resume Update AGAIN
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m30s
2025-07-18 14:25:52 -06:00
528060b85a Resume system overhaul :)
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m54s
2025-07-18 12:14:23 -06:00
26c0862b3e Resume update
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m55s
2025-07-17 16:01:48 -06:00
8283597859 Title update... I didnt make this match the other pages
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m58s
2025-07-10 18:16:44 -06:00
93b1260cc2 Resume changes
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m50s
2025-07-10 14:16:52 -06:00
626fa09efc Fix
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m3s
2025-07-09 16:40:35 -06:00
7dee0d1802 CSS fix
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m51s
2025-07-03 23:09:09 -06:00
8b38944954 Content updates and CSS fixes
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m3s
2025-07-03 00:25:58 -06:00
75e76fba8c Deps
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m31s
2025-07-03 00:04:12 -06:00
af5dbd1a64 LAST FIX
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled
2025-07-03 00:03:50 -06:00
5901c225c1 Ok THIS nix shell should work across architectures now... that was a huge issue earlier.
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m35s
2025-07-02 10:32:06 -06:00
cf88aa388e Updated shell.nix
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m11s
2025-07-02 01:40:03 -06:00
702062ab9a Update resume and make pdf template easier to modify
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m46s
2025-06-30 14:47:19 -06:00
1ffac7f118 README Updates
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m48s
2025-06-30 14:27:56 -06:00
3c6255a5a1 smol change
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m13s
2025-06-30 14:20:06 -06:00
6eda2edaa4 More optimizations
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled
2025-06-30 14:16:35 -06:00
831985bb3f fixed build
All checks were successful
Docker Deploy / build-and-push (push) Successful in 6m11s
2025-06-30 14:08:49 -06:00
9eb964ff06 Switched to playwright... puppeteer is kinda clunky
All checks were successful
Docker Deploy / build-and-push (push) Successful in 7m19s
2025-06-30 13:59:38 -06:00
0f6054701b Using nix for cross platform dev
All checks were successful
Docker Deploy / build-and-push (push) Successful in 6m34s
2025-06-30 13:49:26 -06:00
5649c9592f Deps
All checks were successful
Docker Deploy / build-and-push (push) Successful in 6m23s
2025-06-27 15:45:02 -06:00
9dbdd265ba Removed spotify... its hard to maintain and also fuck spotify
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled
2025-06-27 15:44:43 -06:00
6c2f4e1b81 Rename page header
All checks were successful
Docker Deploy / build-and-push (push) Successful in 7m40s
2025-06-26 23:43:15 -06:00
607ce7266d :)
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled
2025-06-26 23:42:07 -06:00
0d43c3af47 Fixed resume gen
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled
2025-06-26 23:41:29 -06:00
b4298e78ef ??
All checks were successful
Docker Deploy / build-and-push (push) Successful in 6m56s
2025-06-26 18:57:46 -06:00
43c96c73b2 Fixed term!
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled
2025-06-26 16:19:52 -06:00
f89d32d6ce Overhauled resume system
All checks were successful
Docker Deploy / build-and-push (push) Successful in 5m3s
2025-06-26 16:06:15 -06:00
5f7490d133 Fixed tags in posts
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m15s
2025-06-20 00:09:18 -06:00
a6336e293c SSE Fixes
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m27s
2025-06-19 23:48:18 -06:00
41614a49a8 Fixed spotify
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m26s
2025-06-19 23:34:44 -06:00
7161584dcd Bump astro version and moved to stable responsive images
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m22s
2025-06-19 23:17:57 -06:00
57238c9805 Fixxxx
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m34s
2025-06-15 00:34:01 -06:00
9babbd0368 img optimizations
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m40s
2025-06-15 00:22:56 -06:00
ad7cc25766 fixed terminal
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m34s
2025-06-13 00:15:30 -06:00
2a79d0da67 Pagespeed hates this one trick!
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m33s
2025-06-12 23:58:06 -06:00
28f41ac94d Optimized images and fixed tooltips
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m43s
2025-06-12 23:33:23 -06:00
9577cd8bc6 Icon handling was gross before. Fixing
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m22s
2025-06-12 16:24:55 -06:00
df33b94e38 a11y
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m36s
2025-06-12 16:16:38 -06:00
2ad6585a07 Spotify fallback
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m44s
2025-06-12 14:05:10 -06:00
55360c5e5d Delete .DS_Store
Some checks failed
Docker Deploy / build-and-push (push) Failing after 4m17s
2025-06-12 18:49:20 +00:00
b673932ba3 bump version
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m13s
2025-06-12 11:09:52 -06:00
ab2eb7eeac Major re-work!
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled
2025-06-12 11:09:24 -06:00
324449dd59 Refactor
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m35s
2025-06-12 01:13:07 -06:00
bfa3784f03 Add talks
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m45s
2025-06-12 00:41:44 -06:00
6b6b571dd6 Fixed terminal
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m34s
2025-06-09 12:44:53 -06:00
4985310ec2 Update README.md
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m46s
2025-06-09 06:20:13 +00:00
aa7dc5a339 Update README.md
Some checks failed
Docker Deploy / build-and-push (push) Failing after 1m34s
2025-06-09 06:13:34 +00:00
c5a6467de7 Fixed sl 2025-06-04 10:47:12 -06:00
ec2c2b8a8f Fix tab complete 2025-06-04 10:45:03 -06:00
146eff0a5f Merge pull request 'Adding Terminal' (#2) from terminal into main
Reviewed-on: atridad/atri.dad#2
2025-06-04 06:48:24 +00:00
3347ec2082 Added a terminal 2025-06-04 00:47:42 -06:00
5b2c4b0ede Removed. 2025-06-03 15:32:23 -06:00
69742a311c Fixed resume rendering (was bad) 2025-06-03 15:30:49 -06:00
5681e43341 oops 2025-06-03 15:13:30 -06:00
35a6992f55 fixed builds 2025-06-03 14:56:47 -06:00
5b1e13481b oops 2025-06-03 14:46:08 -06:00
5f518523e9 Removed garbage 2025-06-03 14:40:15 -06:00
3218432790 Failsafe 2025-06-03 14:33:12 -06:00
7e2a8e3bf7 Spotify integration 2025-06-03 14:31:38 -06:00
9491ef1c40 Create enshittified-dev-tools.md 2025-06-03 01:01:02 -06:00
61be83c906 Fixed home icons 2025-05-30 16:21:26 -06:00
b3c02e8098 Nav fix 2025-05-28 01:18:20 -06:00
b2426a6071 Logo and responsiveness changes 2025-05-28 01:07:13 -06:00
0b02d6d5a0 Updated Resume 2025-05-27 17:38:26 -06:00
e6b50d5ea5 Update resume animation 2025-05-23 00:32:49 -06:00
fc1a4e2560 Resume update 2025-05-22 12:51:37 -06:00
321fa66338 linter fixes 2025-05-20 00:43:02 -06:00
e0da76f503 RSS fix 2025-05-19 01:16:25 -06:00
1465b7dc24 Update .github/workflows/deploy.yml 2025-05-19 05:50:35 +00:00
44499ec6aa oops 2025-05-18 23:23:47 -06:00
f2b09dad7b Merge pull request 'Migrate to Astro' (#1) from fresh-2.0-migrate into main
Reviewed-on: atridad/atri.dad#1
2025-05-19 05:22:13 +00:00
dba6ebd497 Finalized 2025-05-18 23:21:12 -06:00
3d271e3c7c readme 2025-05-18 11:31:20 -06:00
066c1b4115 Posts working 2025-05-18 11:30:19 -06:00
6ceb4918f7 Added resume 2025-05-18 00:55:19 -06:00
d86ae2f16b Got the homepage sorted 2025-05-17 22:42:48 -06:00
b1cd87f20b Depsssss 2025-05-15 23:49:59 -06:00
4c094944db Update README.md 2025-05-14 21:22:45 +00:00
36fc01dcc4 oop 2025-05-04 00:39:30 -06:00
224fd31839 Resume page 2025-05-04 00:38:56 -06:00
68a817c0f4 deps 2025-05-03 23:28:53 -06:00
df3fcd669d Mobile fixes 2025-04-28 03:44:24 -06:00
5a9a02b772 ??? 2025-04-28 02:18:59 -06:00
ae10c14cc8 Chat changes 2025-04-27 10:53:03 -06:00
871000c333 Added a weird little chat for shits and giggles 2025-04-26 01:34:49 -06:00
a6a17e8969 Sizing fixes for mobile 2025-04-25 03:00:15 -06:00
16dd4463ae New article and homepage fix 2025-04-25 00:40:55 -06:00
9dc9225591 All gucci! 2025-04-24 20:27:25 -06:00
f859a12d2c Remove the weird ass cache stuff. Thanks stack overflow! 2025-04-24 13:14:42 -06:00
a50e73bb55 againagain 2025-04-24 13:03:04 -06:00
6194678607 This 2025-04-24 12:58:25 -06:00
67f29ec389 ? 2025-04-24 12:46:27 -06:00
5281eb71ab ?!?!?!?!?!?! 2025-04-24 12:20:48 -06:00
15b45f2a10 oeufx2 2025-04-24 12:16:29 -06:00
a0edba63bc oeuf 2025-04-24 12:13:58 -06:00
f33dc130b8 Does this work? 2025-04-24 12:03:03 -06:00
ee44847974 projects 2025-04-24 02:41:50 -06:00
8f2f8c3ff2 Posts 2025-04-24 02:24:42 -06:00
261aab9a4b Deno re-write 2025-04-23 01:15:21 -06:00
88aa5b27d7 More CSS fixes 2025-03-31 03:57:59 -06:00
9bc4df5d18 Oops 2025-03-31 03:50:38 -06:00
0a5d5df468 No more building CSS 2025-03-31 03:49:05 -06:00
2560ff159c jk 2025-03-24 01:30:59 -06:00
497abfe83f ???? 2025-03-24 01:22:20 -06:00
c812af90ad Deps 2025-03-24 01:16:24 -06:00
4547bf9f6e CORS 2025-03-24 01:15:32 -06:00
0b85919395 Deps 2025-03-24 00:58:56 -06:00
2267706737 Overhaul of stylegen 2025-03-23 03:23:45 -06:00
cff4c4715e Resume change 2025-03-14 22:30:48 -06:00
8b40a4b1c1 Switched to the OG CLI 2025-03-02 01:00:52 -06:00
0348290959 Fuck 2025-03-01 23:58:32 -06:00
caef568659 :) 2025-03-01 23:57:06 -06:00
817f317b03 Multi arch 2025-02-08 02:17:20 -06:00
355f2c5819 no 2025-02-08 02:06:20 -06:00
997f5dc540 Fix 2025-02-07 20:04:12 -06:00
33339de20a Fixed iA writer using wrong " 2025-01-29 01:44:45 -06:00
ddae816ecd New post 2025-01-29 01:28:52 -06:00
6e6207c381 Added PDSMan 2025-01-26 20:49:52 -06:00
atridad
690b902c99 Upload files to "/" 2025-01-26 23:28:10 +00:00
b6b7b58231 Updated HTMX to v2 2025-01-13 23:54:01 -06:00
237b2624b1 HTMX + Deps Updates 2025-01-12 14:47:47 -06:00
4e69bf3b50 BSKY 2025-01-12 04:20:22 -06:00
862ff8e546 Fixed formatting and added description 2025-01-11 23:08:39 -06:00
6e51164403 Oops 2025-01-04 22:23:30 -07:00
103b1219b6 Oops 2025-01-04 22:21:04 -07:00
e4028a7b5f oops 2025-01-04 21:26:57 -07:00
71633734ea README 2025-01-04 21:25:11 -07:00
62791490f6 cleaned up stylegen 2025-01-04 21:10:51 -07:00
f32ba4faf6 Updated papers section 2025-01-03 02:21:58 -07:00
546a49f550 Deps and links 2024-12-25 19:53:32 -07:00
886d6339b7 Removed air 2024-12-21 00:18:29 -07:00
99adb7ca5c new icons 2024-12-19 09:09:09 -06:00
07b5c90832 ignore 2024-12-19 02:26:20 -06:00
86b21853b3 Makefile 2024-12-18 23:55:58 -06:00
97179cfa94 Deps 2024-12-18 18:35:17 -06:00
184d20915f oop 2024-12-11 23:34:49 -07:00
259e0bd3b7 Added masto 2024-12-11 23:29:52 -07:00
ee7f3f849a :) 2024-11-25 22:59:14 -06:00
0386f55e96 Git 2024-11-23 01:32:43 -06:00
b68e1eb0b0 Pls 2024-11-22 14:17:44 -06:00
01c970a376 Docking 2024-11-22 13:39:58 -06:00
614d16c710 Docking 2024-11-22 13:37:17 -06:00
b4d6be6f78 updated workflow 2024-11-20 14:54:33 -06:00
774896fad3 new fly 2024-11-20 14:52:17 -06:00
e9ce42747a Oops again 2024-11-06 13:33:17 -06:00
41e7e50fc9 Oops! 2024-11-06 13:30:06 -06:00
4196714720 Added bluesky 2024-11-06 13:29:32 -06:00
5a58697233 Fixed HTMX import order 2024-11-06 01:31:07 -06:00
07a0816d66 Cleaned up TW binaries 2024-11-03 17:23:23 -06:00
f1be386e8b Region change 2024-11-03 13:59:37 -06:00
5b1a106b39 Removed image dependency 2024-10-31 11:37:31 -06:00
561905aabb Proper pre-loading and deps 2024-10-30 23:50:34 -06:00
24bf04aa7a Pls 2024-10-28 12:01:59 -06:00
8d29cc2b1a Pubs 2024-10-23 15:04:26 -06:00
61c969c9de Deps 2024-10-23 01:25:41 -06:00
abfee7b16d Removed talk with missing files 2024-10-23 01:25:01 -06:00
d173c828fd Added my CMPT 868 lecture 2024-10-23 01:23:59 -06:00
8f451a0177 Added my CMPT 868 lecture 2024-10-23 00:44:50 -06:00
e79fe3d9ac Optimized Dockerfile 2024-10-21 21:20:39 -06:00
bcfedb0436 Removed docker-compose 2024-10-20 22:28:01 -06:00
c709cbea06 Changed posts 2024-10-20 22:16:26 -06:00
e5fd8aa21d Auto suspend 2024-10-20 21:37:04 -06:00
e2e736e500 Swagger docs 2024-10-20 16:16:50 -06:00
3cbd0d1806 Fly fix 2024-10-19 22:24:02 -06:00
dc1f723aa7 Fly 2024-10-16 00:47:57 -06:00
3200f6d817 Nope 2024-09-23 14:28:19 -06:00
f0216d3976 Sublime baybee 2024-09-23 11:37:51 -06:00
0a0ee57171 Updated projects 2024-09-23 00:54:50 -06:00
0bcea4cf4c Wrong version 2024-09-22 16:25:31 -06:00
ce30bfcd9e Cleared up posts & fixed nav 2024-09-22 01:57:31 -06:00
07b1c3a96c Github Repo 2024-09-20 22:24:08 -06:00
23f6aec61a Github Repo 2024-09-20 22:14:47 -06:00
a6852c0163 Github Repo 2024-09-20 22:03:44 -06:00
e063a906d2 Github Repo 2024-09-20 21:58:15 -06:00
93b7b9ed68 Fixed workflow params 2024-09-20 21:29:30 -06:00
b71298a21e Fixed network 2024-09-20 02:01:52 -06:00
53143716ce Fixed network 2024-09-20 02:00:29 -06:00
70a630f359 Fixed network 2024-09-20 01:59:26 -06:00
1c54b63a6a Fixed network 2024-09-20 01:55:30 -06:00
9ed4d594d1 Added back the proxy network 2024-09-20 01:36:26 -06:00
202c3067c8 Fixed workflow 2024-09-20 01:31:33 -06:00
c03d410fdd Fixed workflow 2024-09-20 01:27:36 -06:00
e8a3bd48bf Fixed workflow 2024-09-20 01:25:09 -06:00
65468efadb Fixed workflow 2024-09-20 01:07:03 -06:00
b100ed0b09 DO Build 2024-09-20 01:04:01 -06:00
47364b586f Fixed netwok issue 2024-09-19 23:34:15 -06:00
646cda702a Proxy 2024-09-17 01:08:03 -06:00
6e32ee5573 Proxy 2024-09-17 01:06:46 -06:00
6115237f98 Fixed dependencies 2024-09-17 00:36:27 -06:00
455761e2c3 Fixed dependencies 2024-09-17 00:36:14 -06:00
706265845c Bumped go version 2024-09-14 00:14:06 -06:00
6adbdca01f README Updates 2024-09-14 00:11:23 -06:00
Atridad Lahiji
164a659c0f Socials 2024-09-14 00:09:15 -06:00
6053b07c6b no proxy 2024-09-08 23:56:14 -06:00
d05f2eb8a4 SINGLE BINARY BB 2024-09-08 23:44:36 -06:00
2edff6915a Removed S3 2024-09-08 23:42:36 -06:00
c0923aaf2b Swapped Git 2024-09-06 00:13:41 -06:00
5499a76ad3 Bluesky 2024-09-03 00:04:11 -06:00
9a328f8eeb Remove contract link 2024-08-26 12:44:29 -06:00
b85ccffe26 Cleaned up env vars 2024-08-22 15:22:12 -06:00
46f636854e Cleanup 2024-08-21 12:59:22 -06:00
c3e40dc826 README changes part 100000 2024-08-19 22:35:34 -06:00
76f6addc03 README changes 2024-08-19 22:30:56 -06:00
b33cc1a647 README changes 2024-08-19 22:25:56 -06:00
cf0c417c0e README changes 2024-08-19 22:23:37 -06:00
43d29792e6 Cleaned up some of the webhooks stuff 2024-08-19 22:19:57 -06:00
96dff182cd Update home.go 2024-08-10 10:52:19 -06:00
982f59ae4e Update Dockerfile 2024-08-09 09:46:54 -06:00
fdc16cc1aa Re-did homepage styling 2024-08-06 14:37:52 -06:00
631693db70 Update docker-compose.yml 2024-08-05 20:11:50 -06:00
241a793d0e Moved testimonials to S3 2024-08-05 12:40:25 -06:00
7cd6afc46c Update home.html 2024-08-03 13:07:51 -06:00
Atridad Lahiji
3b2294e905 removed article 2024-07-18 00:21:43 -06:00
61c3e9fb28 Dependencies! 2024-07-05 09:12:50 -06:00
2076202e3b :| 2024-06-07 15:45:11 -06:00
f357df44b9 :) 2024-06-07 15:43:27 -06:00
c9f12a1b34 Home page does not need hyperscript! 2024-06-04 23:40:56 -06:00
e57121715e Cleaned up a bit 2024-06-04 16:07:13 -06:00
e668d62b57 :D 2024-06-04 15:48:31 -06:00
924906431f Simplified the SSE system! 2024-06-04 15:40:29 -06:00
b1d82ca609 Back to GitHub 2024-06-03 17:22:32 -06:00
a982ade417 Fuck self hosting 2024-06-03 16:34:56 -06:00
0f0057805b Oops 2024-06-03 15:43:28 -06:00
008fccfb82 Docker compose issue 2024-06-03 15:25:03 -06:00
60d4a569a0 Dependency Updates 2024-06-03 15:17:58 -06:00
efb316aacb Remove spotify 2024-06-03 15:16:53 -06:00
7ed6dcbcff Deps 2024-06-02 23:43:22 -06:00
f1ee39d90f Deps 2024-05-25 01:00:45 -06:00
cf136bc9bf Updated stuff I use 2024-05-23 15:46:08 -06:00
a04f883f93 Updated fedi 2024-05-22 23:10:29 -06:00
b2b8708760 Updated git logo and link 2024-05-21 11:53:15 -06:00
atridad
881b5f8ee8 Update pages/projects.go 2024-05-15 19:19:32 +00:00
atridad
cfd92e196d Update api/webhooks/clerk.go 2024-05-15 19:11:22 +00:00
e27d19552f Deps 2024-05-15 09:15:56 -06:00
29cad5a0b2 Apparently I don't understand docker 2024-05-15 00:48:03 -06:00
dfa6fd0af6 Update with network so it doesn't break on re-deploy 2024-05-15 00:42:35 -06:00
f0c878f67e Fixed git integration 2024-05-15 00:09:47 -06:00
6f4fc38d7c How did this get through???? 2024-05-14 22:50:25 -06:00
cd3a539243 Fixed SSE 2024-05-14 22:03:39 -06:00
6f47a15d29 JK 2024-05-14 21:59:21 -06:00
979a67a329 Again! 2024-05-14 21:47:24 -06:00
a9caa1cb24 FOUND IT HOLY SHIT YES 2024-05-14 20:57:59 -06:00
d30c6c9498 I hate NGINX 2024-05-14 20:46:36 -06:00
78133f6519 This time for sure 2024-05-14 20:41:45 -06:00
fae28be16f ugh 2024-05-14 19:13:04 -06:00
cd56d16ae4 >.< 2024-05-14 19:06:08 -06:00
b5b7ef732f COMPOSE 2024-05-14 00:44:17 -06:00
a4b34d4943 Docker-ify? 2024-05-14 00:00:28 -06:00
44df2533c8 No really... this time for real now. 2024-05-09 14:32:19 -06:00
3374ed3bac OK this time for realzies 2024-05-09 01:28:11 -06:00
bc3a365dea Im real bad at this whole goroutine mutex shit but getting better! 2024-05-08 15:05:12 -06:00
8fed511607 Logging and deps 2024-05-08 14:43:57 -06:00
994a2fabd6 More message fixes 2024-05-08 01:13:39 -06:00
00bb8d776d Re-worked stylegen 2024-05-07 18:03:54 -06:00
7463d1a252 Incoming message handling fix 2024-05-06 01:23:05 -06:00
ec04d36559 docs 2024-05-02 15:03:22 -06:00
cdb51bac86 Fixed edge case where a message is being processed while a client is disconnecting. 2024-05-02 14:55:28 -06:00
595802158e Codeberg time baybeeeee 2024-05-01 15:26:58 -06:00
atridad
35b8f01728 Update README.md 2024-05-01 04:28:59 +00:00
2f3ab217bd Deps 2024-04-27 21:55:11 -06:00
768642e363 Whoops! 2024-04-24 16:16:27 -06:00
e7e669107f Fix 2024-04-24 16:13:03 -06:00
31d7438bbd Just kidding here it is! 2024-04-24 16:07:29 -06:00
cf64ab18e8 Article time! 2024-04-24 16:07:06 -06:00
4aa0793f73 Deps 2024-04-24 00:50:25 -06:00
0e1f3fc0a9 Masto change 2024-04-23 23:52:57 -06:00
91020231f8 Updated URL 2024-04-23 22:13:30 -06:00
150 changed files with 5465 additions and 3805 deletions

View File

@@ -1,46 +0,0 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = ["-ip", "127.0.0.1", "-port", "3000"]
bin = "./tmp/main"
pre_cmd = []
cmd = "go build -o ./tmp/main . & cd lib/stylegen && ./gen.sh"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata", "lib/stylegen"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html", "css"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false
keep_scroll = true

View File

@@ -1,8 +1,9 @@
**/atri.dad node_modules
**/.env .git
**/airbin .gitignore
**/tmp dist
**/*.rdb .env*
stylegen *.md
fly.toml .vscode
tailwind.config.*.js .idea
.DS_Store

View File

@@ -1,16 +1,3 @@
CLERK_SECRET_KEY="" # RSS Feed Configuration
CLERK_WEBHOOK_SECRET="" # Will default to GMT
SMTP_HOST="" PUBLIC_RSS_TIMEZONE=America/Edmonton
SMTP_PORT=""
SMTP_USERNAME=""
SMTP_PASSWORD=""
STRIPE_SECRET_KEY=""
REDIS_URL=""
BUCKET_NAME=
AWS_REGION=
AWS_ENDPOINT_URL_S3=
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
SPOTIFY_CLIENT_ID=
SPOTIFY_CLIENT_SECRET=
SPOTIFY_REFRESH_TOKEN=

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

2
.gitattributes vendored
View File

@@ -1,2 +0,0 @@
*.css linguist-vendored
*.js linguist-vendored

View File

@@ -0,0 +1,38 @@
name: Docker Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ secrets.REPO_HOST }}
username: ${{ github.repository_owner }}
password: ${{ secrets.DEPLOY_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64
push: true
tags: |
${{ secrets.REPO_HOST }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:${{ github.sha }}
${{ secrets.REPO_HOST }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:latest
provenance: false
cache-from: type=registry,ref=${{ secrets.REPO_HOST }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:buildcache
cache-to: type=registry,ref=${{ secrets.REPO_HOST }}/${{ github.repository_owner }}/${{ github.event.repository.name }}:buildcache,mode=max

32
.gitignore vendored
View File

@@ -1,7 +1,29 @@
atri.dad # build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env .env
airbin .env.production
tmp/
*.rdb # macOS-specific files
.DS_Store .DS_Store
tailwind.config.*.js
# jetbrains setting folder
.idea/
# nix
.direnv/
result

View File

@@ -1,8 +1,4 @@
{ {
"recommendations": [ "recommendations": ["astro-build.astro-vscode"],
"golang.go", "unwantedRecommendations": []
"bradlc.vscode-tailwindcss", }
"redhat.vscode-yaml",
"a-h.templ"
]
}

11
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

28
Dockerfile Normal file
View File

@@ -0,0 +1,28 @@
FROM oven/bun:1.3.9-alpine AS base
WORKDIR /app
FROM base AS prod-deps
COPY package.json bun.lock ./
RUN --mount=type=cache,id=bun,target=/root/.bun/install/cache \
bun install --production --frozen-lockfile || bun install --production
FROM base AS builder
COPY package.json bun.lock ./
RUN --mount=type=cache,id=bun,target=/root/.bun/install/cache \
bun install --frozen-lockfile || bun install
COPY . .
RUN bun run build
FROM base AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=prod-deps /app/node_modules ./node_modules
COPY package.json ./
ENV HOST=0.0.0.0
ENV PORT=4321
EXPOSE 4321
CMD ["bun", "run", "./dist/server/entry.mjs"]

View File

@@ -1,24 +1,5 @@
# atri.dad # Personal Website
This is my personal website!
## Stack: My personal website built with Astro, Vue, and Preact!
- Backend: Golang + Echo
- Rendering: Golang templates
- Style: TailwindCSS + DaisyUI (No JS Needed)
- Content format: Markdown
## Requirements: **Note:** Preact is used just for PDF generation.
- Golang 1.22.0
## Instructions:
1. Run go get
2. Duplicate the .env.example file and call it .env
3. Fill out the .env values
4. Run ```go install github.com/cosmtrek/air@latest``` to download Air for live reload
5. Run ```air``` to start the dev server
_Note that on MacOS, you need to right click and open the appropriate tailwind executable before you can run StyleGen. This is a limitation of running unsigned binaries in MacOS. Blame Tim Apple._
## Tests
Without Coverage: `go test atri.dad/lib`
With Coverage: `go test atri.dad/lib -cover`

View File

@@ -1,42 +0,0 @@
package api
import (
"net/http"
"os"
"strings"
"github.com/clerkinc/clerk-sdk-go/clerk"
"github.com/labstack/echo/v4"
)
func Authed(c echo.Context) error {
apiKey := os.Getenv("CLERK_SECRET_KEY")
client, err := clerk.NewClient(apiKey)
if err != nil {
// handle error
println(err.Error())
}
// get session token from Authorization header
sessionToken := c.Request().Header.Get("Authorization")
sessionToken = strings.TrimPrefix(sessionToken, "Bearer ")
println(sessionToken)
// verify the session
sessClaims, err := client.VerifyToken(sessionToken)
if err != nil {
println(err.Error())
return c.String(http.StatusUnauthorized, "Unauthorized!")
}
// get the user, and say welcome!
user, err := client.Users().Read(sessClaims.Claims.Subject)
if err != nil {
panic(err)
}
return c.String(http.StatusOK, "Welcome "+*user.FirstName)
}

View File

@@ -1,11 +0,0 @@
package api
import (
"net/http"
"github.com/labstack/echo/v4"
)
func Ping(c echo.Context) error {
return c.String(http.StatusOK, "Pong!")
}

View File

@@ -1,11 +0,0 @@
package api
import (
"net/http"
"github.com/labstack/echo/v4"
)
func PostCopy(c echo.Context) error {
return c.String(http.StatusOK, `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>`)
}

View File

@@ -1,54 +0,0 @@
package api
import (
"io/fs"
"net/http"
"strings"
"time"
contentfs "atri.dad/content"
"atri.dad/lib"
"github.com/gorilla/feeds"
"github.com/labstack/echo/v4"
)
func RSSFeedHandler(c echo.Context) error {
files, err := fs.ReadDir(contentfs.FS, ".")
protocol := "http"
if c.Request().TLS != nil {
protocol = "https"
}
feed := &feeds.Feed{
Title: "Atridad Lahiji's Blog",
Link: &feeds.Link{Href: protocol + "://" + c.Request().Host + "/api/rss"},
}
if err != nil {
http.Error(c.Response().Writer, "There was an issue finding posts!", http.StatusInternalServerError)
return nil
}
for _, file := range files {
if !file.IsDir() && strings.HasSuffix(file.Name(), ".md") {
frontMatter, err := lib.ExtractFrontMatter(file, contentfs.FS)
if err != nil {
http.Error(c.Response().Writer, "There was an issue rendering the posts!", http.StatusInternalServerError)
return nil
}
date, _ := time.Parse("January 2 2006", frontMatter.Date)
feed.Add(&feeds.Item{
Title: frontMatter.Name,
Link: &feeds.Link{Href: protocol + "://" + c.Request().Host + "/post/" + strings.TrimSuffix(file.Name(), ".md")},
Created: date,
})
}
}
rss, _ := feed.ToRss()
return c.Blob(http.StatusOK, "application/rss+xml", []byte(rss))
}

View File

@@ -1,30 +0,0 @@
package api
import (
"net/http"
"os"
"atri.dad/lib"
"github.com/labstack/echo/v4"
)
func NowPlayingHandler(c echo.Context) error {
clientID := os.Getenv("SPOTIFY_CLIENT_ID")
clientSecret := os.Getenv("SPOTIFY_CLIENT_SECRET")
refreshToken := os.Getenv("SPOTIFY_REFRESH_TOKEN")
playing, err := lib.GetCurrentlyPlayingTrack(clientID, clientSecret, refreshToken)
if err != nil {
http.Error(c.Response().Writer, err.Error(), http.StatusInternalServerError)
return err
}
if playing.Item != nil && playing.Playing {
songName := lib.NowPlayingTextFilter(playing.Item.Name)
artistName := lib.NowPlayingTextFilter(playing.Item.Artists[0].Name)
return c.String(http.StatusOK, `<div class="indicator-item badge badge-success"><a _='on mouseover put "🔥 Listening to `+songName+" by "+artistName+` 🔥" into my.textContent on mouseout put "🔥" into my.textContent' href="`+playing.Item.ExternalURLs["spotify"]+`" rel="noreferrer" target="_blank">🔥</a></div>`)
} else {
return c.String(http.StatusOK, "")
}
}

View File

@@ -1,46 +0,0 @@
package api
import (
"errors"
"fmt"
"atri.dad/lib"
"atri.dad/lib/pubsub"
"github.com/labstack/echo/v4"
)
func SSE(c echo.Context, pubSub pubsub.PubSub) error {
if pubSub == nil {
return errors.New("pubSub is nil")
}
channel := c.QueryParam("channel")
if channel == "" {
channel = "default"
}
// Use the request context, which is cancelled when the client disconnects
ctx := c.Request().Context()
pubsub, err := pubSub.SubscribeToChannel(channel)
if err != nil {
return fmt.Errorf("failed to subscribe to channel: %w", err)
}
lib.SetSSEHeaders(c)
// Create a client channel and add it to the SSE server
client := make(chan string)
lib.SSEServer.AddClient(channel, client)
defer lib.SSEServer.RemoveClient(channel, client)
go lib.HandleIncomingMessages(c, pubsub, client)
for {
select {
case <-ctx.Done():
// If the client has disconnected, stop the loop
return nil
}
}
}

View File

@@ -1,26 +0,0 @@
package api
import (
"net/http"
"atri.dad/lib"
"github.com/labstack/echo/v4"
)
type PayRequest struct {
SuccessUrl string `json:"successUrl"`
CancelUrl string `json:"cancelUrl"`
PriceId string `json:"priceId"`
}
func Pay(c echo.Context) error {
payReq := new(PayRequest)
if err := c.Bind(payReq); err != nil {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid request payload"})
}
lib.CreateCheckoutSession(c.Response().Writer, c.Request(), payReq.SuccessUrl, payReq.CancelUrl, payReq.PriceId)
return c.String(http.StatusOK, "Checkout session created")
}

View File

@@ -1,45 +0,0 @@
package api
import (
"fmt"
"net/http"
"strconv"
"atri.dad/lib"
"github.com/labstack/echo/v4"
)
func ResizeHandler(c echo.Context) error {
// Extract file from request
file, _, err := c.Request().FormFile("image")
if err != nil {
return c.String(http.StatusBadRequest, "Error getting image file")
}
defer file.Close()
// Get dimensions from form data parameters
widthStr := c.FormValue("width")
heightStr := c.FormValue("height")
// Validate and convert dimensions to integers
width, err := strconv.Atoi(widthStr)
if err != nil {
return c.String(http.StatusBadRequest, "Invalid width parameter")
}
height, err := strconv.Atoi(heightStr)
if err != nil {
return c.String(http.StatusBadRequest, "Invalid height parameter")
}
fileBlob, err := lib.ResizeImg(file, width, height)
if err != nil {
return c.String(http.StatusInternalServerError, err.Error())
}
c.Response().Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", "resized.png"))
return c.Blob(http.StatusOK, "image/png", fileBlob)
}

View File

@@ -1,37 +0,0 @@
package api
import (
"net/http"
"atri.dad/lib"
"atri.dad/lib/pubsub"
"github.com/labstack/echo/v4"
)
func SSEDemoSend(c echo.Context, pubSub pubsub.PubSub) error {
channel := c.QueryParam("channel")
if channel == "" {
channel = "default"
}
// Get message from query parameters, form value, or request body
message := c.QueryParam("message")
if message == "" {
message = c.FormValue("message")
if message == "" {
var body map[string]string
if err := c.Bind(&body); err != nil {
return err
}
message = body["message"]
}
}
if message == "" {
return c.JSON(http.StatusBadRequest, map[string]string{"error": "message parameter is required"})
}
lib.SendSSE(c.Request().Context(), pubSub, "default", message)
return c.JSON(http.StatusOK, map[string]string{"status": "message sent"})
}

View File

@@ -1,78 +0,0 @@
package webhooks
import (
"encoding/json"
"io"
"net/http"
"os"
"atri.dad/lib"
"github.com/labstack/echo/v4"
svix "github.com/svix/svix-webhooks/go"
)
// Types
type ClerkEventEmail struct {
EmailAddress string `json:"email_address"`
}
type ClerkEventData struct {
EmailAddresses []ClerkEventEmail `json:"email_addresses,omitempty"`
Id string `json:"id"`
}
type ClerkEvent struct {
Data ClerkEventData
Type string
}
// Event Handlers
func userCreatedHandler(event ClerkEvent) {
welcomeEmail := `
<h1>Thank you for making an atri.dad account!</h1>
<h2>There are a number of apps this account give you access to!</h2>
<br/>
<ul>
<li>Atash Demo: https://atash.atri.dad/</li>
<li>Commodore: https://commodore.atri.dad/</li>
</ul>
`
lib.SendEmail(event.Data.EmailAddresses[0].EmailAddress, "apps@atri.dad", "Atri's Apps", welcomeEmail, "Welcome to Atri's Apps!")
}
// Main Handler/Router
func ClerkWebhookHandler(c echo.Context) error {
secret := os.Getenv("CLERK_WEBHOOK_SECRET")
wh, err := svix.NewWebhook(secret)
if err != nil {
return c.String(http.StatusBadRequest, "Unknown Validation Error")
}
headers := c.Request().Header
payload, err := io.ReadAll(c.Request().Body)
if err != nil {
return c.String(http.StatusBadRequest, "Failed to read request body!")
}
err = wh.Verify(payload, headers)
if err != nil {
return c.String(http.StatusBadRequest, "Cannot validate webhook authenticity!")
}
var parsed ClerkEvent
err = json.Unmarshal(payload, &parsed)
if err != nil {
return c.String(http.StatusBadRequest, "Invalid Json!")
}
switch parsed.Type {
case "user.created":
userCreatedHandler(parsed)
}
return c.String(http.StatusOK, "Success!")
}

33
astro.config.mjs Normal file
View File

@@ -0,0 +1,33 @@
// @ts-check
import { defineConfig } from "astro/config";
import tailwindcss from "@tailwindcss/vite";
import vue from "@astrojs/vue";
import node from "@astrojs/node";
import mdx from "@astrojs/mdx";
export default defineConfig({
site: "https://atri.dad",
redirects: {
"/feed": "/rss.xml",
},
output: "server",
build: {
inlineStylesheets: "always",
},
vite: {
plugins: [tailwindcss()],
},
image: {
responsiveStyles: true,
layout: "constrained",
objectFit: "cover",
objectPosition: "center",
},
integrations: [vue(), mdx()],
adapter: node({
mode: "standalone",
}),
});

1229
bun.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +0,0 @@
---
name: "Thoughts on Cognitive Load and Programming Language Syntax"
date: "February 07 2024"
tags: ["article","thoughts"]
---
Recently, I started to pick up a new language in my spare time: Go. Was it partially influenced by the surge in HTMX popularity and how often Go is used alongside it? Almost certainly. But at some point along this journey, I noticed something: This is _so_ much more fun and _so_ much less draining. This whole post won't be me gushing about how much I love Go as a language, not directly. I started to notice a few things...
# Oh, what fun...
I come from the JavaScript and TypeScript world with frameworks like Remix and Next.js. And the DX (Developer Experience) for these is lovely! For reasons I could not quite pin down, I was having much more fun with Go + HTMX despite the hacks to get close to the same DX. It came down to how darn easily I could pull off things I had previously relied on services for. Need a message queue to run on the edge? Easy, write a wrapper over Redis to use its PubSub. Need real-time updates to the UI? No problem, write a basic Server-Sent Events system with subscriptions and channels. Anything I wanted, I could build. That, and the way concurrency _"just works"_, and I could make anything I wanted happen. This brings a level of satisfaction that is hard to convey in an article.
# What changed?
So this isn't meant to be a post to bash JavaScript, as much as I do have my reservations about the language. That being said, JS makes basic things like concurrency an afterthought. Go's lightweight threads, called goroutines, make it easy to write concurrent programs without worrying about the overhead of heavyweight threads. Even better, Go has channels that provide a simple and effective way of synchronizing communication between goroutines, making it easier to write concurrent programs free from race conditions. Similar concepts are present in Java, Rust (Tokio), C#, etc. Even better, error handling! Now, this is controversial, but forcing errors to be valued and allowing functions to assert that there _may_ be errors is a game changer for writing safer code. All of this amounts to a much lower cognitive load. I am less worried that my code is inefficient, error-prone, etc. I can just _write_.
# What does this mean?
As a field, we can and should investigate and identify key points in programming languages that increase or decrease the mental load or overhead for developers. Imagine if we could identify these points and work to reduce the overhead. This has been on my mind as I reflect on my experience learning Go. I want to dive deeper into the details of what we can do to reduce this overhead and get back to writing good software without unnecessary restrictions.
# Thanks!
Do you have any thoughts on this? Do you want to have a chat about the topic? Feel free to reach out by email at [me@atri.dad](mailto:me@atri.dad). Until next time! 🫡

View File

@@ -1,6 +0,0 @@
package contentfs
import "embed"
//go:embed *
var FS embed.FS

View File

@@ -1,78 +0,0 @@
---
name: "Introducing the GOTH Stack"
date: "January 08 2024"
tags: ["article","golang"]
---
# Enter the GOTH Stack!
The GOTH stack is something I've been trying to get to for a while now. It's not a specific repository with a fancy command that can scaffold a project for you. It's more like a set of pillars for building excellent, pleasant, full-stack web applications.
# The first pillar: Go
Go is something I learned to love later on in my career. I was mainly writing JavaScript, building on serverless platforms and growing frustrated at the performance and limitations. Go changed all of that.
What makes Go good?:
- Static types
- Incredibly easy concurrency
- Errors as values
- Incredible runtime and build time performance
- Tiny memory footprint
The tl;dr is that it is challenging to write Go code that is _not_ performant.
# The second pillar: Templates... well... Go templates
Go templates surprised me, to be completely honest. They offer just enough to get me going and perform exceptionally well. Sure, it's not as simple as a basic JSX file in Next.js since you need to make a route handler, but it works pretty well and supports basic control flow. I am interested in looking into alternatives such as TEMPL (which reads much like JSX for Go), but I need to find a real reason to move from the standard library here.
Here is an example of a route handler passing a slice over to a template for rendering:
```go
package pages
import (
"HTML/template"
"github.com/labstack/echo/v4"
"goth.stack/lib"
)
type HomeProps struct {
Socials []lib.IconLink
Tech []lib.IconLink
ContractLink string
ResumeURL string
SupportLink string
}
func Home(c echo.Context) error {
socials := []lib.IconLink{
{
Name: "Email",
Href: "mailto:example@site.com",
Icon: template.HTML(`<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M48 64C21.5 64 0 85.5 0 112c0 15.1 7.1 29.3 19.2 38.4L236.8 313.6c11.4 8.5 27 8.5 38.4 0L492.8 150.4c12.1-9.1 19.2-23.3 19.2-38.4c0-26.5-21.5-48-48-48H48zM0 176V384c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V176L294.4 339.2c-22.8 17.1-54 17.1-76.8 0L0 176z"/></svg>`),
},
}
props := HomeProps{
Socials: socials,
Tech: tech,
ContractLink: "mailto:example@site.com",
ResumeURL: "https://srv.goth.stack/Atridad_Lahiji_Resume.pdf",
SupportLink: "https://donate.stripe.com/8wMeVF25c78L0V2288",
}
// Specify the partials used by this page
partials := []string{"header", "navitems"}
// Render the template
return lib.RenderTemplate(c.Response().Writer, "base", partials, props)
}
```
As you can see, it really isn't that bad! It also comes with many of the benefits of Go and the flexibility of components!
# The third pillar: HTMX
So, up to this point, you may have been thinking: "Gee Atri... you can't do anything reactive here". Before HTMX, you would have been right. HTMX offers a more backend-centric developer a way to build complex reactivity to their front end through basic templating languages. It is one file you import in your template, and it enables anything from basic HTML swapping to WebSocket and Server-Sent Event support. It is really, really powerful and worth looking at all together.
With Go managing route handlers and API routes, the template language running the UI, and HTMX governing interactivity on the front end, you can effectively write a fully dynamic full-stack application without writing a line of JavaScript code. It even runs quite a bit faster than some of the JS world's frameworks (Astro, for instance). It is actually what powers this site right now! The fundamentals are essential here and come together for a clean and enjoyable developer experience. I encourage everyone in the JS world to give it a shot! Perhaps it is not your thing, and that's okay! But you might also just fall in love with it!
# Giving It A Shot
I have a [repository](https://github.com/atridadl/goth.stack) as well as a [demo](https://goth-stack.fly.dev/) ready to go with everything you need to start!
# Thanks!
If you found this helpful, please let me know by email at [me@atri.dad](mailto:me@atri.dad). Until next time!

View File

@@ -1,28 +0,0 @@
---
name: "Build Scalable Real-time Applications on Fly.io + Remix!"
date: "December 04 2023"
tags: ["remix.js", "fly.io", "article"]
---
# Scalability... what is it?
You often think of vertical scaling for something to scale, adding more resources to whatever issue you encounter (CPU, RAM, etc.). While this is the simplest way to scale an application or service, it can only get you so far. Horizontal scaling involves adding more machines or "nodes" running the same application. These nodes sit behind a load balancer responsible for directing client traffic to the appropriate machine to optimize load. This will work well if the application is meant to do simple CRUD operations for individual users. What if you need collaboration in real time? This complicates things...
# The real-time problem:
Real-time applications require a pub/sub or publish and subscribe model. A client will send a request to the application to perform an operation. Once done, the server will broadcast an event to all subscribing clients to trigger a re-fetch of data. In the case of a multi-node application, you need to use a service outside of your nodes to synchronize messages across all nodes.
# The stack:
For this stack, I chose Remix for its close adherence to web standards and easy support for server-sent events. These web socket connections work one way: from server to client. Next, we must synchronize all Server Sent events across different requests to a single node. For this, Node.js has its own Event Emitter API, which we can use. Now, we can use something like Redis and its Pub/Sub commands for multi-node setups to broadcast across nodes.
This is what it would look like:
![Diagram](https://link.storjshare.io/s/jwmhimh32pura4pyr5h5luou6qla/atridad%2Farticles/scalability.png?wrap=0)
# How does it work?
Once a client connects to a page with real-time enabled, a persistent connection via EventStreams is made. The client would request to make a real-time update. Once the endpoint completes the request, it triggers a Node.js event, which the EventStreams endpoint listens to. Once received, the application sends an event down to the client via a server-sent event while also passing that request on to Redis pub/sub. Every node listens to this Redis event stream, so every node will receive the event and trigger the event using Node.js events, which then start server-sent events.
As you can see, there are many moving parts here, and it can get quite complicated. I have a repo called Atash, which acts as a template to get started. You can check it out [here](https://atash.atri.dad)!
If you found this helpful, please let me know by email at [me@atri.dad](mailto:me@atri.dad). Until next time!

View File

@@ -1,18 +0,0 @@
---
name: "Thoughts on AI"
date: "April 09 2024"
tags: ["article","thoughts"]
---
I'm a bit late to the party, but AI has definitely taken off lately. Every product we use now seems to have some sort of AI integration or feature baked in to capture some of the hype. How do I feel about it? Well… thats complicated...
# Memes
If you dig around my GitHub you'll surely stuble upon Himbot, my Discord bot project. This is where I enjoy AI. For memes. I have two commands: "ask" and "pic" which generate text and images respectively. Now, I didn't write some complex algorithm for this. It uses Replicate to access open source models. As it turns out, this makes the bot quite fun. Have an unhinged discord conversation that needs image replies, just throw some no-context nonsense into "/pic" and see what happens! Its amazing! This, however, is where my love of it ends.
# Capitalism
Capitalism ruins all things. Including AI. Now what could be a useful and fun technology is rammed into every product ever at the _expense_ of its users just to check a box for shareholders. Notion? It has AI now. Snapchat? Yup… an AI assistant there too. Wan't to use windows? I hope you aren't planning to ignore Microsoft's new OS level AI Copilot! There is no escape it seems. Even DuckDuckGo and Brave Browser are using it now (although given Brave still clings to the trash that is Web3 I am less surprised there). My point is, there is definitely going to be AI fatigue, if there is not already. Capitalism won't let us forget about this though, since it seems to line the pockets of the ultra-rich anyways. Want to make a game but don't want to pay an artist? Just pay for some compute and generate images with **prompt engineering**! How did that model get the data to learn to do this? No need to worry your little heads over that detail if its making you money! All of this and more is enabled by this technology and encouraged by our economic systems.
# What can we do?
What can we do about this? Is there a hope for us trying to escape AI? Honestly, I am not sure. But I am definitely trying. I have removed copilot from my Windows install, and I refuse to use anything that forces me to use AI. Will I still use it for fun in my discord bot? Absolutely! But being concious about how you use AI is key. With so many of these models trained on the work of skilled creatives, it is _our_ responsibility as consumers of this technology to ensure that we are _always_ paying professionals for their work, and not using their skills for free via a heartless algorithm. Lastly, be causious of products that add AI for seemingly no reason. There is a good chance your data is being used to train it.
# Thanks!
Do you have any thoughts on AI? Do you use it? If so, how? Feel free to reach out by email at [me@atri.dad](mailto:me@atri.dad). Until next time! 🫡

9
docker-compose.yml Normal file
View File

@@ -0,0 +1,9 @@
services:
app:
image: ${IMAGE}
ports:
- "${APP_PORT}:4321"
environment:
NODE_ENV: production
PUBLIC_RSS_TIMEZONE: ${PUBLIC_RSS_TIMEZONE:-}
restart: unless-stopped

27
flake.lock generated Normal file
View File

@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1766473571,
"narHash": "sha256-5G1NDO2PulBx1RoaA6U1YoUDX0qZslpPxv+n5GX6Qto=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "76701a179d3a98b07653e2b0409847499b2a07d3",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

48
go.mod
View File

@@ -1,48 +0,0 @@
module atri.dad
go 1.22.0
require (
github.com/alecthomas/chroma/v2 v2.13.0
golang.org/x/oauth2 v0.19.0
)
require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.11.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/image v0.15.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)
require (
github.com/aws/aws-sdk-go v1.51.23
github.com/clerkinc/clerk-sdk-go v1.49.0
github.com/disintegration/imaging v1.6.2
github.com/fatih/color v1.16.0
github.com/gorilla/feeds v1.1.2
github.com/joho/godotenv v1.5.1
github.com/labstack/echo/v4 v4.12.0
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/redis/go-redis/v9 v9.5.1
github.com/stripe/stripe-go/v76 v76.25.0
github.com/svix/svix-webhooks v1.21.0
github.com/yuin/goldmark v1.7.1
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/zmb3/spotify v1.3.0
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/sys v0.19.0 // indirect
gopkg.in/yaml.v2 v2.4.0
)

177
go.sum
View File

@@ -1,177 +0,0 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
github.com/alecthomas/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI=
github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk=
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/aws/aws-sdk-go v1.51.21 h1:UrT6JC9R9PkYYXDZBV0qDKTualMr+bfK2eboTknMgbs=
github.com/aws/aws-sdk-go v1.51.21/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go v1.51.23 h1:/3TEdsEE/aHmdKGw2NrOp7Sdea76zfffGkTTSXTsDxY=
github.com/aws/aws-sdk-go v1.51.23/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/brianvoe/gofakeit/v6 v6.19.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clerkinc/clerk-sdk-go v1.49.0 h1:tJLIAx3qfP2cNQJ/iPq6OF1BSB0NzI3alcOuEueexoA=
github.com/clerkinc/clerk-sdk-go v1.49.0/go.mod h1:pejhMTTDAuw5aBpiHBEOOOHMAsxNfPvKfM5qexFJYlc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/feeds v1.1.2 h1:pxzZ5PD3RJdhFH2FsJJ4x6PqMqbgFk1+Vez4XWBW8Iw=
github.com/gorilla/feeds v1.1.2/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8=
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stripe/stripe-go/v76 v76.25.0 h1:kmDoOTvdQSTQssQzWZQQkgbAR2Q8eXdMWbN/ylNalWA=
github.com/stripe/stripe-go/v76 v76.25.0/go.mod h1:rw1MxjlAKKcZ+3FOXgTHgwiOa2ya6CPq6ykpJ0Q6Po4=
github.com/svix/svix-webhooks v1.21.0 h1:ZxoPU2SJGjmRy1qMaeHY1VdZhTaEkHuh3ruy4CrxW3Y=
github.com/svix/svix-webhooks v1.21.0/go.mod h1:qGeiECF5WRQElyfF0i2CqUtWk2GQJTgL+EJZ/WRCxok=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
github.com/zmb3/spotify v1.3.0 h1:6Z2F1IMx0Hviq/dpf8nFwvKPppFEMXn8yfReSBVi16k=
github.com/zmb3/spotify v1.3.0/go.mod h1:GD7AAEMUJVYc2Z7p2a2S0E3/5f/KxM/vOnErNr4j+Tw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.19.0 h1:9+E/EZBCbTLNrbN35fHv/a/d/mOBatymz1zbtQrXpIg=
golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -1,102 +0,0 @@
package lib
import (
"crypto/tls"
"log"
"net/smtp"
"os"
)
func SendEmail(to_email string, from_email string, from_name string, html string, subject string) {
log.Println("Starting email sending process")
// Set up authentication information.
auth := smtp.PlainAuth(
"",
os.Getenv("SMTP_USERNAME"),
os.Getenv("SMTP_PASSWORD"),
os.Getenv("SMTP_HOST"),
)
log.Println("Authentication set up")
// Connect to the server, authenticate, set the sender and recipient,
// and send the email all in one step.
msg := []byte("From: " + from_name + " <" + from_email + ">\r\n" +
"To: " + to_email + "\r\n" +
"Subject: " + subject + "\r\n" +
"Content-Type: text/html; charset=UTF-8" + "\r\n" +
"\r\n" +
html + "\r\n")
tlsconfig := &tls.Config{
InsecureSkipVerify: false,
ServerName: os.Getenv("SMTP_HOST"),
}
log.Println("TLS configuration set up")
c, err := smtp.Dial(os.Getenv("SMTP_HOST") + ":587")
if err != nil {
log.Println("Error dialing SMTP server:", err)
return
}
log.Println("Connected to SMTP server")
if err = c.StartTLS(tlsconfig); err != nil {
log.Println("Error starting TLS:", err)
return
}
log.Println("TLS started")
if err = c.Auth(auth); err != nil {
log.Println("Error authenticating with SMTP server:", err)
return
}
log.Println("Authenticated with SMTP server")
if err = c.Mail(from_email); err != nil {
log.Println("Error setting sender address:", err)
return
}
log.Println("Sender address set")
if err = c.Rcpt(to_email); err != nil {
log.Println("Error setting recipient address:", err)
return
}
log.Println("Recipient address set")
w, err := c.Data()
if err != nil {
log.Println("Error getting write closer:", err)
return
}
log.Println("Got write closer")
_, err = w.Write(msg)
if err != nil {
log.Println("Error writing message:", err)
return
}
log.Println("Message written")
err = w.Close()
if err != nil {
log.Println("Error closing write closer:", err)
return
}
log.Println("Write closer closed")
c.Quit()
log.Println("Email sent successfully")
}

View File

@@ -1,39 +0,0 @@
package lib
import (
"bytes"
"errors"
"image"
"image/png"
"io"
"mime/multipart"
"github.com/disintegration/imaging"
)
func ResizeImg(file multipart.File, width int, height int) ([]byte, error) {
// Read file content
fileContent, err := io.ReadAll(file)
if err != nil {
return nil, errors.New("error reading image file")
}
// Decode image
img, _, err := image.Decode(bytes.NewReader(fileContent))
if err != nil {
println(err.Error())
return nil, errors.New("error decoding image")
}
// Resize the image
resizedImg := imaging.Resize(img, width, height, imaging.Lanczos)
// Encode the resized image as PNG
buf := new(bytes.Buffer)
if err := png.Encode(buf, resizedImg); err != nil {
return nil, errors.New("error encoding image to PNG")
}
// Return the resized image as response
return buf.Bytes(), nil
}

View File

@@ -1,26 +0,0 @@
package lib
import (
"html/template"
)
type IconLink struct {
Name string
Href string
Icon template.HTML
}
type CardLink struct {
Name string
Href string
Description string
Date string
Tags []string
Internal bool
}
type ButtonLink struct {
Name string
Href string
Internal bool
}

View File

@@ -1,27 +0,0 @@
package lib
import "github.com/fatih/color"
// Error logging
var red = color.New(color.FgRed)
var LogError = red.Add(color.Bold)
// Info logging
var cyan = color.New(color.FgCyan)
var LogInfo = cyan.Add(color.Bold)
// Success logging
var green = color.New(color.FgGreen)
var LogSuccess = green.Add(color.Bold)
// Warning logging
var yellow = color.New(color.FgYellow)
var LogWarning = yellow.Add(color.Bold)
// Debug logging
var magenta = color.New(color.FgMagenta)
var LogDebug = magenta.Add(color.Bold)
// Custom logging
var white = color.New(color.FgWhite)
var LogCustom = white.Add(color.Bold)

View File

@@ -1,65 +0,0 @@
package lib
import (
"bufio"
"bytes"
"errors"
"fmt"
"io/fs"
"strings"
"github.com/yuin/goldmark"
"gopkg.in/yaml.v2"
)
type FrontMatter struct {
Name string
Date string
Tags []string
}
func ExtractFrontMatter(file fs.DirEntry, contentFS fs.FS) (CardLink, error) {
f, err := contentFS.Open(file.Name())
if err != nil {
return CardLink{}, fmt.Errorf("failed to open file: %w", err)
}
defer f.Close()
scanner := bufio.NewScanner(f)
var lines []string
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
if err := scanner.Err(); err != nil {
return CardLink{}, fmt.Errorf("failed to read file: %w", err)
}
content := strings.Join(lines, "\n")
splitContent := strings.SplitN(content, "---", 3)
if len(splitContent) < 3 {
return CardLink{}, fmt.Errorf("invalid file format: %s", file.Name())
}
frontMatter := CardLink{}
if err := yaml.Unmarshal([]byte(splitContent[1]), &frontMatter); err != nil {
return CardLink{}, fmt.Errorf("failed to unmarshal frontmatter: %w", err)
}
md := goldmark.New(goldmark.WithExtensions())
var buf bytes.Buffer
if err := md.Convert([]byte(splitContent[2]), &buf); err != nil {
return CardLink{}, fmt.Errorf("failed to convert markdown: %w", err)
}
return frontMatter, nil
}
func SplitFrontmatter(md []byte) (frontmatter []byte, content []byte, err error) {
parts := bytes.SplitN(md, []byte("---"), 3)
if len(parts) < 3 {
return nil, nil, errors.New("invalid or missing frontmatter")
}
return parts[1], parts[2], nil
}

View File

@@ -1,86 +0,0 @@
package adapters
import (
"context"
"sync"
"atri.dad/lib"
"atri.dad/lib/pubsub"
)
type LocalPubSub struct {
subscribers map[string][]chan pubsub.Message
lock sync.RWMutex
}
type LocalPubSubMessage struct {
messages <-chan pubsub.Message
}
func (ps *LocalPubSub) SubscribeToChannel(channel string) (pubsub.PubSubMessage, error) {
ps.lock.Lock()
defer ps.lock.Unlock()
if ps.subscribers == nil {
ps.subscribers = make(map[string][]chan pubsub.Message)
}
ch := make(chan pubsub.Message, 100)
ps.subscribers[channel] = append(ps.subscribers[channel], ch)
lib.LogInfo.Printf("[PUBSUB/LOCAL] Subscribed to channel %s\n", channel)
return &LocalPubSubMessage{messages: ch}, nil
}
func (ps *LocalPubSub) PublishToChannel(channel string, message string) error {
subscribers, ok := ps.subscribers[channel]
if !ok {
lib.LogWarning.Printf("\n[PUBSUB/LOCAL] No subscribers for channel %s\n", channel)
return nil
}
ps.lock.Lock()
defer ps.lock.Unlock()
lib.LogInfo.Printf("\n[PUBSUB/LOCAL] Publishing message to channel %s: %s\n", channel, message)
for _, ch := range subscribers {
ch <- pubsub.Message{Payload: message}
}
return nil
}
func (ps *LocalPubSub) UnsubscribeFromChannel(channel string, ch <-chan pubsub.Message) {
ps.lock.Lock()
defer ps.lock.Unlock()
subscribers := ps.subscribers[channel]
for i, subscriber := range subscribers {
if subscriber == ch {
// Remove the subscriber from the slice
subscribers = append(subscribers[:i], subscribers[i+1:]...)
break
}
}
if len(subscribers) == 0 {
delete(ps.subscribers, channel)
} else {
ps.subscribers[channel] = subscribers
}
}
func (m *LocalPubSubMessage) ReceiveMessage(ctx context.Context) (*pubsub.Message, error) {
for {
select {
case <-ctx.Done():
// The client has disconnected. Stop trying to send messages.
return nil, ctx.Err()
case msg := <-m.messages:
// A message has been received. Send it to the client.
lib.LogInfo.Printf("\n[PUBSUB/LOCAL] Received message: %s\n", msg.Payload)
return &msg, nil
}
}
}

View File

@@ -1,71 +0,0 @@
package adapters
import (
"context"
"os"
"atri.dad/lib"
"atri.dad/lib/pubsub"
"github.com/joho/godotenv"
"github.com/redis/go-redis/v9"
)
var RedisClient *redis.Client
type RedisPubSubMessage struct {
pubsub *redis.PubSub
}
// RedisPubSub is a Redis implementation of the PubSub interface.
type RedisPubSub struct {
Client *redis.Client
}
func NewRedisClient() (*redis.Client, error) {
if RedisClient != nil {
return RedisClient, nil
}
godotenv.Load(".env")
redis_url := os.Getenv("REDIS_URL")
opts, err := redis.ParseURL(redis_url)
if err != nil {
return nil, err
}
lib.LogInfo.Printf("\n[PUBSUB/REDIS]Connecting to Redis at %s\n", opts.Addr)
RedisClient = redis.NewClient(opts)
return RedisClient, nil
}
func (m *RedisPubSubMessage) ReceiveMessage(ctx context.Context) (*pubsub.Message, error) {
msg, err := m.pubsub.ReceiveMessage(ctx)
if err != nil {
return nil, err
}
lib.LogInfo.Printf("\n[PUBSUB/REDIS] Received message: %s\n", msg.Payload)
return &pubsub.Message{Payload: msg.Payload}, nil
}
func (ps *RedisPubSub) SubscribeToChannel(channel string) (pubsub.PubSubMessage, error) {
pubsub := ps.Client.Subscribe(context.Background(), channel)
_, err := pubsub.Receive(context.Background())
if err != nil {
return nil, err
}
lib.LogInfo.Printf("\n[PUBSUB/REDIS] Subscribed to channel %s\n", channel)
return &RedisPubSubMessage{pubsub: pubsub}, nil
}
func (r *RedisPubSub) PublishToChannel(channel string, message string) error {
err := r.Client.Publish(context.Background(), channel, message).Err()
if err != nil {
return err
}
lib.LogInfo.Printf("\n[PUBSUB/REDIS] Publishing message to channel %s: %s\n", channel, message)
return nil
}

View File

@@ -1,16 +0,0 @@
package pubsub
import "context"
type Message struct {
Payload string
}
type PubSubMessage interface {
ReceiveMessage(ctx context.Context) (*Message, error)
}
type PubSub interface {
SubscribeToChannel(channel string) (PubSubMessage, error)
PublishToChannel(channel string, message string) error
}

View File

@@ -1,52 +0,0 @@
package lib
import (
"fmt"
"os"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
)
func GeneratePublicURL(key string) string {
bucket := os.Getenv("BUCKET_NAME")
if bucket == "" {
fmt.Println("No S3 bucket specified, skipping upload.")
return ""
}
endpoint := os.Getenv("AWS_ENDPOINT_URL_S3")
accessKeyID := os.Getenv("AWS_ACCESS_KEY_ID")
secretAccessKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
region := os.Getenv("AWS_REGION")
sess, err := session.NewSession(&aws.Config{
Region: &region,
Credentials: credentials.NewStaticCredentials(
accessKeyID,
secretAccessKey,
"",
),
Endpoint: aws.String(endpoint),
})
if err != nil {
return ""
}
svc := s3.New(sess)
req, _ := svc.GetObjectRequest(&s3.GetObjectInput{
Bucket: &bucket,
Key: &key,
})
urlStr, err := req.Presign(15 * time.Minute)
if err != nil {
return ""
}
return urlStr
}

View File

@@ -1,86 +0,0 @@
package lib
import (
"context"
"os"
"strings"
"sync"
"atri.dad/lib/pubsub"
"github.com/zmb3/spotify"
"golang.org/x/oauth2"
)
var (
spotifyOAuth2Endpoint = oauth2.Endpoint{
TokenURL: "https://accounts.spotify.com/api/token",
AuthURL: "https://accounts.spotify.com/authorize",
}
config *oauth2.Config
once sync.Once
)
func NowPlayingTextFilter(s string) string {
s = strings.Replace(s, "'", "&#39;", -1)
s = strings.Replace(s, "\"", "&quot;", -1)
return s
}
func GetOAuth2Config(clientID string, clientSecret string) *oauth2.Config {
once.Do(func() {
config = &oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
Scopes: []string{spotify.ScopeUserReadCurrentlyPlaying},
Endpoint: spotifyOAuth2Endpoint,
}
})
return config
}
func GetCurrentlyPlayingTrack(clientID string, clientSecret string, refreshToken string) (*spotify.CurrentlyPlaying, error) {
// OAuth2 config
config := GetOAuth2Config(clientID, clientSecret)
// Token source
tokenSource := config.TokenSource(context.Background(), &oauth2.Token{RefreshToken: refreshToken})
// Get new token
newToken, err := tokenSource.Token()
if err != nil {
return nil, err
}
// Create new client
client := spotify.Authenticator{}.NewClient(newToken)
// Get currently playing track
playing, err := client.PlayerCurrentlyPlaying()
if err != nil {
return nil, err
}
return playing, nil
}
func CurrentlyPlayingTrackSSE(ctx context.Context, pubSub pubsub.PubSub) error {
clientID := os.Getenv("SPOTIFY_CLIENT_ID")
clientSecret := os.Getenv("SPOTIFY_CLIENT_SECRET")
refreshToken := os.Getenv("SPOTIFY_REFRESH_TOKEN")
playing, err := GetCurrentlyPlayingTrack(clientID, clientSecret, refreshToken)
if err != nil {
return err
}
if playing.Item != nil && playing.Playing {
songName := NowPlayingTextFilter(playing.Item.Name)
artistName := NowPlayingTextFilter(playing.Item.Artists[0].Name)
return SendSSE(ctx, pubSub, "spotify", `<div class="indicator-item badge badge-success"><a _='on mouseover put "🔥 Listening to `+songName+" by "+artistName+` 🔥" into my.textContent on mouseout put "🔥" into my.textContent' href="`+playing.Item.ExternalURLs["spotify"]+`" rel="noreferrer" target="_blank">🔥</a></div>`)
} else {
SendSSE(ctx, pubSub, "spotify", "")
}
return nil
}

View File

@@ -1,131 +0,0 @@
package lib
import (
"context"
"fmt"
"log"
"net/http"
"sync"
"atri.dad/lib/pubsub"
"github.com/labstack/echo/v4"
)
type SSEServerType struct {
clients map[string]map[chan string]bool
mu sync.Mutex
}
var SSEServer *SSEServerType
var mutex = &sync.Mutex{}
func init() {
SSEServer = &SSEServerType{
clients: make(map[string]map[chan string]bool),
}
}
func NewSSEServer() *SSEServerType {
return &SSEServerType{
clients: make(map[string]map[chan string]bool),
}
}
func (s *SSEServerType) AddClient(channel string, client chan string) {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.clients[channel]; !ok {
s.clients[channel] = make(map[chan string]bool)
}
s.clients[channel][client] = true
}
func (s *SSEServerType) RemoveClient(channel string, client chan string) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.clients[channel], client)
if len(s.clients[channel]) == 0 {
delete(s.clients, channel)
}
}
func (s *SSEServerType) ClientCount(channel string) int {
s.mu.Lock()
defer s.mu.Unlock()
return len(s.clients[channel])
}
func SendSSE(ctx context.Context, messageBroker pubsub.PubSub, channel string, message string) error {
// Create a channel to receive an error from the goroutine
errCh := make(chan error, 1)
// Use a goroutine to send the message asynchronously
go func() {
select {
case <-ctx.Done():
// The client has disconnected, so return an error
errCh <- ctx.Err()
default:
err := messageBroker.PublishToChannel(channel, message)
errCh <- err // Send the error to the channel
}
}()
// Wait for the goroutine to finish and check for errors
err := <-errCh
if err != nil {
return err
}
return nil
}
func SetSSEHeaders(c echo.Context) {
c.Response().Header().Set(echo.HeaderContentType, "text/event-stream")
c.Response().Header().Set(echo.HeaderConnection, "keep-alive")
c.Response().Header().Set(echo.HeaderCacheControl, "no-cache")
}
func HandleIncomingMessages(c echo.Context, pubsub pubsub.PubSubMessage, client chan string) {
for {
select {
case <-c.Request().Context().Done():
// The client has disconnected. Stop trying to send messages.
return
default:
// The client is still connected. Continue processing messages.
msg, err := pubsub.ReceiveMessage(c.Request().Context())
if err != nil {
log.Printf("Failed to receive message: %v", err)
continue
}
data := fmt.Sprintf("data: %s\n\n", msg.Payload)
mutex.Lock()
_, err = c.Response().Write([]byte(data))
mutex.Unlock()
if err != nil {
log.Printf("Failed to write message: %v", err)
return // Stop processing if an error occurs
}
// Check if the ResponseWriter is nil before trying to flush it
if c.Response().Writer != nil {
// Check if the ResponseWriter implements http.Flusher before calling Flush
flusher, ok := c.Response().Writer.(http.Flusher)
if ok {
flusher.Flush()
} else {
log.Println("Failed to flush: ResponseWriter does not implement http.Flusher")
}
} else {
log.Println("Failed to flush: ResponseWriter is nil")
}
}
}
}

View File

@@ -1,41 +0,0 @@
package lib
import (
"log"
"net/http"
"os"
"github.com/joho/godotenv"
"github.com/stripe/stripe-go/v76"
"github.com/stripe/stripe-go/v76/checkout/session"
)
// init function
func init() {
godotenv.Load(".env")
stripe.Key = os.Getenv("STRIPE_SECRET_KEY")
}
func CreateCheckoutSession(w http.ResponseWriter, r *http.Request, successUrl string, cancelUrl string, priceId string) {
params := &stripe.CheckoutSessionParams{
LineItems: []*stripe.CheckoutSessionLineItemParams{
{
// Provide the exact Price ID (for example, pr_1234) of the product you want to sell
Price: stripe.String(priceId),
Quantity: stripe.Int64(1),
},
},
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
SuccessURL: stripe.String(successUrl),
CancelURL: stripe.String(cancelUrl),
AutomaticTax: &stripe.CheckoutSessionAutomaticTaxParams{Enabled: stripe.Bool(true)},
}
s, err := session.New(params)
if err != nil {
log.Printf("session.New: %v", err)
}
http.Redirect(w, r, s.URL, http.StatusSeeOther)
}

View File

@@ -1,14 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
html,
container,
body {
height: 100%;
width: 100%;
overflow-y: auto;
position: fixed;
}
}

View File

@@ -1,79 +0,0 @@
#!/bin/sh
OS=$(uname -s)
ARCH=$(uname -m)
# Normalize OS and ARCH identifiers
case $OS in
"Darwin")
OS="macos"
;;
"Linux")
OS="linux"
;;
"CYGWIN"*|"MINGW"*|"MSYS"*)
OS="windows"
;;
*)
echo "Unknown operating system: $OS"
exit 1
;;
esac
case $ARCH in
"x86_64")
ARCH="x64"
;;
"arm64")
ARCH="arm64"
;;
*)
echo "Unsupported architecture: $ARCH"
exit 1
;;
esac
# Construct the binary file name
BINARY="./tw/${OS}-${ARCH}"
if [ "$OS" = "windows" ]; then
BINARY="${BINARY}.exe"
else
# Set execute permissions on the binary
chmod +x $BINARY
fi
echo $BINARY
# Infer pages from .html files in the pages directory
PAGES=$(ls ../../pages/templates/*.html | xargs -n 1 basename | sed 's/\.[^.]*$//')
# Run the binary for each page
for PAGE in $PAGES; do
(
# Detect which partials are being used in this page
PARTIALS=$(grep -o -E '{{template "[^"]+' ../../pages/templates/${PAGE}.html | cut -d'"' -f2 | xargs -I{} echo \"../../pages/templates/partials/{}.html\")
# Generate an array of partials and join them with commas
PARTIALS_ARRAY=$(echo $PARTIALS | tr ' ' ',')
# Always include the "header" partial and any other partials that are always used
PARTIALS_ARRAY=\"../../pages/templates/partials/header.html\",\"../../pages/templates/partials/global.html\",$PARTIALS_ARRAY
# Generate Tailwind config for this page
echo "module.exports = {
content: [\"../../pages/templates/${PAGE}.html\", \"../../pages/templates/layouts/*.html\", $PARTIALS_ARRAY],
theme: {
extend: {},
},
daisyui: {
themes: [\"night\"],
},
plugins: [require('daisyui'), require('@tailwindcss/typography')],
}" > tailwind.config.${PAGE}.js
# Run the binary with the generated config
$BINARY build -i ./base.css -c tailwind.config.${PAGE}.js -o ../../public/css/styles.${PAGE}.css --minify
) &
done
# Wait for all background processes to finish
wait

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,43 +0,0 @@
package lib
import (
"html/template"
"log"
"net/http"
"path/filepath"
"runtime"
templatefs "atri.dad/pages/templates"
)
func RenderTemplate(w http.ResponseWriter, layout string, partials []string, props interface{}) error {
// Get the name of the current file
_, filename, _, _ := runtime.Caller(1)
page := filepath.Base(filename)
page = page[:len(page)-len(filepath.Ext(page))] // remove the file extension
// Build the list of templates
templates := []string{
"layouts/" + layout + ".html",
page + ".html",
}
for _, partial := range partials {
templates = append(templates, "partials/"+partial+".html")
}
// Parse the templates
ts, err := template.ParseFS(templatefs.FS, templates...)
if err != nil {
log.Print(err.Error())
return err
}
// Execute the layout template
err = ts.ExecuteTemplate(w, layout, props)
if err != nil {
log.Print(err.Error())
return err
}
return nil
}

BIN
main

Binary file not shown.

129
main.go
View File

@@ -1,129 +0,0 @@
package main
import (
"context"
"embed"
"flag"
"fmt"
"log"
"net/http"
"time"
"atri.dad/api"
"atri.dad/api/webhooks"
"atri.dad/lib"
"atri.dad/lib/pubsub"
"atri.dad/lib/pubsub/adapters"
"atri.dad/pages"
"github.com/joho/godotenv"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
//go:embed public/*
var PublicFS embed.FS
func main() {
// Load environment variables
godotenv.Load(".env")
// Initialize Redis client
redisClient, redisError := adapters.NewRedisClient()
// Initialize pubsub
var pubSub pubsub.PubSub
if redisError != nil {
lib.LogWarning.Printf("\n[PUBSUB/INIT] Failed to connect to Redis: %v\n", redisError)
lib.LogWarning.Printf("\n[PUBSUB/INIT] Falling back to LocalPubSub\n")
pubSub = &adapters.LocalPubSub{}
} else {
adapters.RedisClient = redisClient
pubSub = &adapters.RedisPubSub{
Client: adapters.RedisClient,
}
}
// Initialize Echo router
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Pre(middleware.RemoveTrailingSlash())
e.Use(middleware.RequestID())
e.Use(middleware.Secure())
e.Use(middleware.GzipWithConfig(middleware.GzipConfig{
Level: 5,
}))
e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(50)))
// Static server
fs := http.FS(PublicFS)
e.GET("/public/*", echo.WrapHandler(http.FileServer(fs)))
// Page routes
e.GET("/", pages.Home)
e.GET("/projects", pages.Projects)
e.GET("/talks", pages.Talks)
e.GET("/testimonials", pages.Testimonials)
e.GET("/blog", pages.Blog)
e.GET("/post/:post", pages.Post)
e.GET("/tools", pages.Tools)
e.GET("/tools/resize", pages.Resize)
e.GET("/tools/ssedemo", pages.SSEDemo)
// API Routes:
apiGroup := e.Group("/api")
apiGroup.GET("/ping", api.Ping)
apiGroup.GET("/authed/ping", api.Authed)
apiGroup.POST("/pay", api.Pay)
apiGroup.GET("/rss", api.RSSFeedHandler)
apiGroup.GET("/post/copy", api.PostCopy)
apiGroup.GET("/sse", func(c echo.Context) error {
return api.SSE(c, pubSub)
})
apiGroup.POST("/tools/sendsse", func(c echo.Context) error {
return api.SSEDemoSend(c, pubSub)
})
apiGroup.GET("/spotify/nowplaying", api.NowPlayingHandler)
apiGroup.POST("/tools/resize", api.ResizeHandler)
// Webhook Routes:
webhookGroup := e.Group("/webhook")
webhookGroup.POST("/clerk", webhooks.ClerkWebhookHandler)
// Spotify Polling
go func() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for range ticker.C {
// Check if there are any clients connected to the "spotify" channel
if lib.SSEServer.ClientCount("spotify") > 0 {
// Get the currently playing track
err := lib.CurrentlyPlayingTrackSSE(context.Background(), pubSub)
if err != nil {
// Handle error
continue
}
}
}
}()
// Parse command-line arguments for IP and port
ip := flag.String("ip", "", "IP address to bind the server to")
port := flag.String("port", "3000", "Port to bind the server to")
flag.Parse()
// Start server with HTTP/2 support
s := &http.Server{
Addr: fmt.Sprintf("%s:%s", *ip, *port),
Handler: e,
}
e.Logger.Fatal(e.StartServer(s))
log.Println("Server started on port", *port)
}

31
package.json Normal file
View File

@@ -0,0 +1,31 @@
{
"name": "atridotdad",
"type": "module",
"version": "4.2.0",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro",
"nix": "nix develop"
},
"dependencies": {
"@astrojs/mdx": "5.0.0-beta.9",
"@astrojs/node": "10.0.0-beta.6",
"@astrojs/rss": "4.0.15",
"@astrojs/vue": "6.0.0-beta.1",
"@iarna/toml": "^2.2.5",
"@react-pdf/renderer": "^4.3.2",
"@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.2.1",
"astro": "6.0.0-beta.17",
"react": "^19.2.4",
"sharp": "^0.34.5",
"tailwindcss": "^4.2.1",
"vue": "^3.5.29"
},
"devDependencies": {
"@types/react": "^19.2.14",
"daisyui": "^5.5.19"
}
}

View File

@@ -1,71 +0,0 @@
package pages
import (
"io/fs"
"log"
"net/http"
"sort"
"strings"
"time"
contentfs "atri.dad/content"
"atri.dad/lib"
"github.com/labstack/echo/v4"
)
type BlogProps struct {
Posts []lib.CardLink
}
func Blog(c echo.Context) error {
var posts []lib.CardLink
files, err := fs.ReadDir(contentfs.FS, ".")
if err != nil {
log.Println(err)
http.Error(c.Response().Writer, "There was an issue finding posts!", http.StatusInternalServerError)
return nil
}
for _, file := range files {
if !file.IsDir() && strings.HasSuffix(file.Name(), ".md") {
frontMatter, err := lib.ExtractFrontMatter(file, contentfs.FS)
if err != nil {
log.Println(err)
http.Error(c.Response().Writer, "There was an issue rendering the posts!", http.StatusInternalServerError)
return nil
}
frontMatter.Href = "post/" + strings.TrimSuffix(file.Name(), ".md")
frontMatter.Internal = true
posts = append(posts, frontMatter)
}
}
const layout = "January 2 2006"
sort.Slice(posts, func(i, j int) bool {
iDate, err := time.Parse(layout, posts[i].Date)
if err != nil {
log.Fatal(err)
}
jDate, err := time.Parse(layout, posts[j].Date)
if err != nil {
log.Fatal(err)
}
return iDate.Before(jDate)
})
props := BlogProps{
Posts: posts,
}
// Specify the partials used by this page
partials := []string{"header", "navitems", "cardlinks"}
// Render the template
return lib.RenderTemplate(c.Response().Writer, "base", partials, props)
}

View File

@@ -1,22 +0,0 @@
package pages
import (
"atri.dad/lib"
"github.com/labstack/echo/v4"
)
type ExampleProps struct {
ExamplePropText string
}
func Example(c echo.Context) error {
props := ExampleProps{
ExamplePropText: "EXAMPLE TEXT HERE",
}
// Specify the partials used by this page
partials := []string{"header", "navitems"}
// Render the template
return lib.RenderTemplate(c.Response().Writer, "base", partials, props)
}

File diff suppressed because one or more lines are too long

View File

@@ -1,78 +0,0 @@
package pages
import (
"bytes"
"html/template"
"io/fs"
"net/http"
contentfs "atri.dad/content"
"atri.dad/lib"
chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
"github.com/labstack/echo/v4"
"github.com/yuin/goldmark"
highlighting "github.com/yuin/goldmark-highlighting/v2"
"gopkg.in/yaml.v2"
)
type PostProps struct {
Content template.HTML
Name string
Date string
Tags []string
}
func Post(c echo.Context) error {
postName := c.Param("post")
filePath := postName + ".md"
md, err := fs.ReadFile(contentfs.FS, filePath)
if err != nil {
println(err.Error())
http.Error(c.Response().Writer, "This post does not exist!", http.StatusNotFound)
return nil
}
frontmatterBytes, content, err := lib.SplitFrontmatter(md)
if err != nil {
http.Error(c.Response().Writer, "There was an issue rendering this post!", http.StatusInternalServerError)
return nil
}
var frontmatter lib.FrontMatter
if err := yaml.Unmarshal(frontmatterBytes, &frontmatter); err != nil {
http.Error(c.Response().Writer, "There was an issue rendering this post!", http.StatusInternalServerError)
return nil
}
var buf bytes.Buffer
markdown := goldmark.New(
goldmark.WithExtensions(
highlighting.NewHighlighting(
highlighting.WithStyle("fruity"),
highlighting.WithFormatOptions(
chromahtml.WithLineNumbers(true),
),
),
),
)
if err := markdown.Convert(content, &buf); err != nil {
http.Error(c.Response().Writer, "There was an issue rendering this post!", http.StatusInternalServerError)
return nil
}
props := PostProps{
Content: template.HTML(buf.String()),
Name: frontmatter.Name,
Date: frontmatter.Date,
Tags: frontmatter.Tags,
}
// Specify the partials used by this page
partials := []string{"header", "navitems"}
// Render the template
return lib.RenderTemplate(c.Response().Writer, "post", partials, props)
}

View File

@@ -1,61 +0,0 @@
package pages
import (
"atri.dad/lib"
"github.com/labstack/echo/v4"
)
type ProjectProps struct {
Projects []lib.CardLink
}
func Projects(c echo.Context) error {
projects := []lib.CardLink{
{
Name: "Pollo",
Description: "A dead-simple real-time voting tool.",
Tags: []string{"react", "remix.js", "product"},
Href: "https://pollo.atri.dad",
},
{
Name: "Atash",
Description: "The 🔥hottest🔥 full-stack Remix template!",
Tags: []string{"react", "remix.js", "template"},
Href: "https://github.com/atridadl/Atash",
},
{
Name: "GOTH Stack",
Description: "🚀 A Web Application Template Powered by HTMX + Go + Tailwind 🚀",
Tags: []string{"golang", "htmx", "template"},
Href: "https://github.com/atridadl/goth.stack",
},
{
Name: "Himbot",
Description: "A discord bot written in Go. Loosly named after my username online (HimbothySwaggins).",
Tags: []string{"golang", "bot"},
Href: "https://github.com/atridadl/HimBot",
},
{
Name: "Commodore",
Description: "Helpful Nightbot Helpers for Twitch",
Tags: []string{"react", "remix.js", "template"},
Href: "https://commodore.atri.dad",
},
{
Name: "loadr",
Description: "A lightweight REST load testing tool with robust support for different verbs, token auth, and performance reports.",
Tags: []string{"golang", "cli"},
Href: "https://github.com/atridadl/loadr",
},
}
props := ProjectProps{
Projects: projects,
}
// Specify the partials used by this page
partials := []string{"header", "navitems", "cardlinks"}
// Render the template
return lib.RenderTemplate(c.Response().Writer, "base", partials, props)
}

View File

@@ -1,39 +0,0 @@
package pages
import (
"atri.dad/lib"
"github.com/labstack/echo/v4"
)
type TalkProps struct {
Talks []lib.CardLink
}
func Talks(c echo.Context) error {
talks := []lib.CardLink{
{
Name: "How to ship less JavaScript",
Description: "A talk on building websites while being mindful of the JavaScript we ship. Presented at the Dev Edmonton July 2023 JS/Ruby/Python Meetup",
Href: "https://github.com/atridadl/devedmonton-july-2023",
Tags: []string{"astro", "ssr"},
Date: "July 06, 2023",
},
{
Name: "Hypermedia as the engine of application state - an Introduction",
Description: "A talk on building reactive websites using tools like HTMX instead of JSON + JS. Will be presented at the Dev Edmonton Fabruary 2024 JS/Ruby/Python Meetup",
Href: lib.GeneratePublicURL("hypermedia_talk_atridad.pdf"),
Tags: []string{"golang", "htmx", "ssr"},
Date: "February 01, 2024",
},
}
props := TalkProps{
Talks: talks,
}
// Specify the partials used by this page
partials := []string{"header", "navitems", "cardlinks"}
// Render the template
return lib.RenderTemplate(c.Response().Writer, "base", partials, props)
}

View File

@@ -1,22 +0,0 @@
{{define "title"}}
Atridad Lahiji // Blog
{{end}}
{{define "headercontent"}}
Atridad Lahiji // Blog
{{end}}
{{define "head"}}
<link rel="stylesheet" href="/public/css/styles.blog.css" />
{{end}}
{{define "main"}}
<section class="flex flex-row flex-wrap gap-2 justify-center align-middle">
{{range .Posts}}
{{template "cardlinks" .}}
{{end}}
</section>
{{end}}
{{define "foot"}}
{{end}}

View File

@@ -1,26 +0,0 @@
{{define "title"}}
Atridad Lahiji // [SOMEPAGE]
{{end}}
{{define "headercontent"}}
Atridad Lahiji // [SOMEPAGE]
{{end}}
{{define "head"}}
<link rel="stylesheet" href="/public/css/styles.[SOMEPAGE].css" />
{{end}}
{{define "main"}}
<h1 class="text-4xl font-extrabold text-white sm:text-8xl">
New <span
class="bg-gradient-to-r from-pink-500 to-blue-500 bg-clip-text text-transparent"
>Page</span
>
</h1>
{{end}}
{{define "foot"}}
<script src="/public/js/htmx.base.js"></script>
<script src="/public/js/htmx.sse.js"></script>
<script src="/public/js/hyperscript.js"></script>
{{end}}

View File

@@ -1,53 +0,0 @@
{{define "title"}}
Atridad Lahiji // Root
{{end}}
{{define "headercontent"}}
Atridad Lahiji // Root
{{end}}
{{define "head"}}
<link rel="stylesheet" href="/public/css/styles.home.css" />
{{end}}
{{define "main"}}
<h1 class="text-4xl font-extrabold text-white sm:text-8xl">
Hi, I&apos;m <span
class="text-transparent bg-clip-text bg-gradient-to-r from-pink-500 via-purple-500 to-blue-500">Atridad</span>
</h1>
<h2 class="text-2xl font-extrabold tracking-tight text-white sm:text-[2rem]">
I&apos;m a full stack developer who builds cool things for the web.
</h2>
<span>
<h2 class="mb-2 text-xl text-white sm:text-[1.5rem]">Places I exist:</h2>
<div class="flex flex-row flex-wrap items-center justify-center gap-4 text-center">
{{range .Socials}}
{{template "iconlinks" .}}
{{end}}
</div>
</span>
<span>
<h2 class="mb-2 text-xl text-white sm:text-[1.5rem]">Stuff I Use:</h2>
<div class="flex flex-row flex-wrap items-center justify-center gap-4 text-center">
{{range .Tech}}
{{template "iconlinks" .}}
{{end}}
</div>
</span>
<div class="flex flex-row flex-wrap gap-2 mx-auto justify-center">
{{range .ButtonsLinks}}
{{template "buttonlinks" .}}
{{end}}
</div>
{{end}}
{{define "foot"}}
<script src="/public/js/htmx.base.js"></script>
<script src="/public/js/htmx.sse.js"></script>
<script src="/public/js/hyperscript.js"></script>
{{end}}

View File

@@ -1,22 +0,0 @@
{{define "base"}}
<!DOCTYPE html>
<html lang="en" data-theme="night">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/public/favicon.ico" />
<title>{{template "title" .}}</title>
<meta name="description" content="Just here for the vibes...">
{{template "head" .}}
</head>
<body class="block h-[100%]">
{{template "header" .}}
<main class="container flex flex-col items-center justify-center gap-3 sm:gap-6 p-4 text-center mx-auto min-h-[calc(100%-64px)]">
{{template "main" .}}
</main>
{{template "foot" .}}
</body>
</html>
{{end}}

View File

@@ -1,58 +0,0 @@
{{define "post"}}
<!DOCTYPE html>
<html lang="en" data-theme="night">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/public/favicon.ico" />
<title>{{.Name}}</title>
<meta name="description" content="Just here for the vibes...">
{{template "head" .}}
</head>
<body class="block h-[100%]">
{{template "header" .}}
<main class="prose prose-invert mx-auto p-4">
<article>
<h1 class="title">{{.Name}}</h1>
<div class="flex flex-row flex-wrap gap-4">
{{if .Date}}
<p>
<div class="flex flex-row flex-wrap items-center gap-1 text-md">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clock-4"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
{{.Date}}
</div>
</p>
{{end}}
{{if .Tags}}
<div class="flex flex-row flex-wrap text-center items-center justify-center gap-1">
{{range .Tags}}
<div class="badge badge-accent">#{{.}}</div>
{{end}}
</div>
{{end}}
<div id="svgContainer" style="display: none;">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-circle"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/></svg>
</div>
<button id="copyButton" aria-label="Copy Link to Post" hx-get="/api/post/copy" hx-swap="innerHTML" hx-trigger="click delay:3s" _='on click put #svgContainer.innerHTML into me.innerHTML then call navigator.clipboard.writeText(window.location.href)'>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
</button>
<a href="/blog" class="btn btn-primary btn-outline">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-undo-2"><path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5v0a5.5 5.5 0 0 1-5.5 5.5H11"/></svg>
Back
</a>
</div>
<hr />
{{template "main" .}}
</article>
</main>
{{template "foot" .}}
</body>
</html>
{{end}}

View File

@@ -1,15 +0,0 @@
{{define "buttonlinks"}}
{{if eq true .Internal}}
<a class="btn btn-primary btn-outline btn-md lg:btn-lg" href={{.Href}}>
{{.Name}}
</a>
{{else}}
<a class="btn btn-primary btn-outline btn-md lg:btn-lg" href={{.Href}} target="_blank" rel="noreferrer">
{{.Name}}
</a>
{{end}}
{{end}}

View File

@@ -1,54 +0,0 @@
{{define "cardlinks"}}
<div class="card card-compact w-64 bg-secondary shadow-xl">
<div class="card-body text-base-100 flex flex-col">
<h2 class="card-title text-base-100">{{.Name}}</h2>
{{if .Description}}
<p>{{.Description}}</p>
{{end}}
{{if .Date}}
<p>
<div class="flex flex-row flex-wrap items-center gap-1 text-md">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clock-4"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
{{.Date}}
</div>
</p>
{{end}}
{{if .Tags}}
<div class="flex flex-row flex-wrap text-center items-center justify-center gap-1">
{{range .Tags}}
<div class="badge badge-accent">#{{.}}</div>
{{end}}
</div>
{{end}}
{{if .Href}}
<div class="card-actions justify-end">
{{if eq true .Internal}}
<a
role="button"
href={{.Href}}
aria-label={{.Name}}
class="btn btn-circle btn-base-100 text-primary hover:btn-accent hover:text-neutral"
>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-right"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</a>
{{else}}
<a
role="button"
href={{.Href}}
aria-label={{.Name}}
class="btn btn-circle btn-base-100 text-primary hover:btn-accent hover:text-neutral"
target="_blank"
rel="noopener noreferrer"
>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
</a>
{{end}}
</div>
{{end}}
</div>
</div>
{{end}}

View File

@@ -1,4 +0,0 @@
{{define "header"}}
<div class="fill-green-500 tooltip tooltip-top badge badge-success"></div>
{{end}}

View File

@@ -1,20 +0,0 @@
{{define "header"}}
<header class="navbar bg-base-100">
<div class="navbar-start">
<a class="btn btn-ghost normal-case text-lg sm:text-xl text-white" href="/">{{template "headercontent".}}</a>
</div>
<div class="navbar-end z-50">
<div class="dropdown dropdown-end">
<label tabindex="0" class="btn btn-sm btn-ghost text-white">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-menu"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>
</label>
<ul
tabindex="0"
class="menu menu-compact dropdown-content gap-2 mt-3 p-2 shadow bg-base-100 rounded-box"
>
{{template "navitems" .}}
</ul>
</div>
</div>
</header>
{{end}}

View File

@@ -1,18 +0,0 @@
{{define "iconlinks"}}
{{if eq .Name "Spotify"}}
<div class="indicator indicator-top indicator-end">
<div hx-ext="sse" sse-connect="/api/sse?channel=spotify" sse-swap="message">
<span class="link link-hover link-success" hx-get="/api/spotify/nowplaying" hx-trigger="load" hx-swap="self">
</span>
</div>
<a class="fill-white hover:fill-pink-500" href={{.Href}} target="_blank" rel="me" aria-label={{.Name}}>
{{.Icon}}
</a>
</div>
{{else}}
<a class="fill-white hover:fill-pink-500" href={{.Href}} target="_blank" rel="me" aria-label={{.Name}}>
{{.Icon}}
</a>
{{end}}
{{end}}

View File

@@ -1,32 +0,0 @@
{{define "navitems"}}
<li>
<a class="no-underline" href="/">
Home
</a>
</li>
<li>
<a class="no-underline" href="/projects">
Projects
</a>
</li>
<li>
<a class="no-underline" href="/talks">
Talks
</a>
</li>
<li>
<a class="no-underline" href="/testimonials">
Testimonials
</a>
</li>
<li>
<a class="no-underline" href="/tools">
Tools
</a>
</li>
<li>
<a class="no-underline" href="/blog">
Blog
</a>
</li>
{{end}}

View File

@@ -1,20 +0,0 @@
{{define "title"}}
Atridad Lahiji // Post
{{end}}
{{define "headercontent"}}
Atridad Lahiji // Post
{{end}}
{{define "head"}}
<link rel="stylesheet" href="/public/css/styles.post.css" />
{{end}}
{{define "main"}}
{{.Content}}
{{end}}
{{define "foot"}}
<script src="/public/js/htmx.base.js"></script>
<script src="/public/js/hyperscript.js"></script>
{{end}}

View File

@@ -1,22 +0,0 @@
{{define "title"}}
Atridad Lahiji // Projects
{{end}}
{{define "headercontent"}}
Atridad Lahiji // Projects
{{end}}
{{define "head"}}
<link rel="stylesheet" href="/public/css/styles.projects.css" />
{{end}}
{{define "main"}}
<section class="flex flex-row flex-wrap gap-2 justify-center align-middle">
{{range .Projects}}
{{template "cardlinks" .}}
{{end}}
</section>
{{end}}
{{define "foot"}}
{{end}}

View File

@@ -1,22 +0,0 @@
{{define "title"}}
Atridad Lahiji // Talks
{{end}}
{{define "headercontent"}}
Atridad Lahiji // Talks
{{end}}
{{define "head"}}
<link rel="stylesheet" href="/public/css/styles.talks.css" />
{{end}}
{{define "main"}}
<section class="flex flex-row flex-wrap gap-2 justify-center align-middle">
{{range .Talks}}
{{template "cardlinks" .}}
{{end}}
</section>
{{end}}
{{define "foot"}}
{{end}}

View File

@@ -1,6 +0,0 @@
package templatefs
import "embed"
//go:embed *
var FS embed.FS

View File

@@ -1,32 +0,0 @@
{{define "title"}}
Atridad Lahiji // Testimonials
{{end}}
{{define "headercontent"}}
Atridad Lahiji // Testimonials
{{end}}
{{define "head"}}
<link rel="stylesheet" href="/public/css/styles.testimonials.css" />
{{end}}
{{define "main"}}
<h2 class="text-2xl font-extrabold tracking-tight text-white sm:text-[2rem]">
What <a class="link link-secondary"
href="https://steamcommunity.com/app/1230140/reviews/?browsefilter=toprated&snr=1_5_100010_" target="_blank"
rel="noreferrer">People</a> Say About Me
</h2>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
{{range .Images}}
<div>
<img class="object-cover object-center w-full max-w-full rounded-lg" src={{.}} alt="Review of Atri"
loading="lazy" />
</div>
{{end}}
</div>
{{end}}
{{define "foot"}}
{{end}}

View File

@@ -1,22 +0,0 @@
{{define "title"}}
Atridad Lahiji // Tools
{{end}}
{{define "headercontent"}}
Atridad Lahiji // Tools
{{end}}
{{define "head"}}
<link rel="stylesheet" href="/public/css/styles.tools.css" />
{{end}}
{{define "main"}}
<section class="flex flex-row flex-wrap gap-2 justify-center align-middle">
{{range .Tools}}
{{template "cardlinks" .}}
{{end}}
</section>
{{end}}
{{define "foot"}}
{{end}}

View File

@@ -1,31 +0,0 @@
{{define "title"}}
Atridad Lahiji // Tools // Resizer
{{end}}
{{define "headercontent"}}
Atridad Lahiji // Tools // Resizer
{{end}}
{{define "head"}}
<link rel="stylesheet" href="/public/css/styles.tools.resize.css" />
{{end}}
{{define "main"}}
<h2 class="text-2xl font-extrabold tracking-tight text-white sm:text-[2rem]">Image Resizer</h2>
<form action="/api/tools/resize" method="post" enctype="multipart/form-data" class="flex-col flex gap-4">
Select image to resize:
<input type="file" name="image" accept=".png,.jpg,.jpeg"
class="file-input file-input-bordered file-input-secondary w-full max-w-xs" required />
<br>
New width (px):
<input type="number" id="newWidth" name="width" min="1" class="input input-bordered w-full max-w-xs" required>
<br>
New height (px):
<input type="number" id="newHeight" name="height" min="1" class="input input-bordered w-full max-w-xs" required>
<br>
<button type="submit" class="btn btn-secondary">Resize Image</button>
</form>
{{end}}
{{define "foot"}}
{{end}}

View File

@@ -1,36 +0,0 @@
{{define "title"}}Atridad Lahiji // Tools // SSE Demo{{end}}
{{define "headercontent"}}
Atridad Lahiji // Tools // SSE Demo
{{end}}
{{define "head"}}
<link rel="stylesheet" href="/public/css/styles.tools.ssedemo.css" />
{{end}}
{{define "main"}}
<h2 class="text-2xl font-extrabold tracking-tight text-white sm:text-[2rem]">Server Sent Events Demo</h2>
<p class="text-lg">This page demonstrates the use of the <a href="https://htmx.org/extensions/sse/">HTMX SSE
Extention</a> to receive Server Sent Events on the "default" channel.</p>
<p class="text-lg">Any events received on the "default" channel will appear below:</p>
<div hx-ext="sse" sse-connect="/api/sse" sse-swap="message">
Waiting for SSE Message...
</div>
<p class="text-lg">Here you can send messages on the default channel:</p>
<form hx-post="/api/tools/sendsse" hx-trigger="submit" hx-swap="none" class="flex-col flex gap-2">
<div class="label">
<span class="label-text">Message</span>
</div>
<input type="text" name="message" value="Hello world!" placeholder="Enter your message here"
class="input input-bordered input-primary w-full max-w-xs" />
<button type="submit" class="btn btn-primary">Send Event</button>
</form>
{{end}}
{{define "foot"}}
<script src="/public/js/htmx.base.js"></script>
<script src="/public/js/htmx.sse.js"></script>
{{end}}

View File

@@ -1,34 +0,0 @@
package pages
import (
"atri.dad/lib"
"github.com/labstack/echo/v4"
)
type TestimonialsProps struct {
Images []string
}
func Testimonials(c echo.Context) error {
images := []string{
"/public/img/testimonials/1.png",
"/public/img/testimonials/2.png",
"/public/img/testimonials/3.png",
"/public/img/testimonials/4.png",
"/public/img/testimonials/5.png",
"/public/img/testimonials/6.png",
"/public/img/testimonials/7.png",
"/public/img/testimonials/8.png",
"/public/img/testimonials/9.png",
}
props := TestimonialsProps{
Images: images,
}
// Specify the partials used by this page
partials := []string{"header", "navitems"}
// Render the template
return lib.RenderTemplate(c.Response().Writer, "base", partials, props)
}

View File

@@ -1,37 +0,0 @@
package pages
import (
"atri.dad/lib"
"github.com/labstack/echo/v4"
)
type ToolsProps struct {
Tools []lib.CardLink
}
func Tools(c echo.Context) error {
tools := []lib.CardLink{
{
Name: "Server Sent Events Demo",
Description: "Server Sent Events Demo",
Href: "/tools/ssedemo",
Internal: true,
},
{
Name: "Image Resizer",
Description: "Image Resizer Tool",
Href: "/tools/resize",
Internal: true,
},
}
props := ToolsProps{
Tools: tools,
}
// Specify the partials used by this page
partials := []string{"header", "navitems", "cardlinks"}
// Render the template
return lib.RenderTemplate(c.Response().Writer, "base", partials, props)
}

View File

@@ -1,39 +0,0 @@
package pages
import (
"atri.dad/lib"
"github.com/labstack/echo/v4"
)
type ResizeProps struct {
Talks []lib.CardLink
}
func Resize(c echo.Context) error {
talks := []lib.CardLink{
{
Name: "How to ship less JavaScript",
Description: "A talk on building websites while being mindful of the JavaScript we ship. Presented at the Dev Edmonton July 2023 JS/Ruby/Python Meetup",
Href: "https://github.com/atridadl/devedmonton-july-2023",
Tags: []string{"astro", "ssr"},
Date: "July 06, 2023",
},
{
Name: "Hypermedia as the engine of application state - an Introduction",
Description: "A talk on building reactive websites using tools like HTMX instead of JSON + JS. Will be presented at the Dev Edmonton Fabruary 2024 JS/Ruby/Python Meetup",
Href: lib.GeneratePublicURL("hypermedia_talk_atridad.pdf"),
Tags: []string{"golang", "htmx", "ssr"},
Date: "February 01, 2024",
},
}
props := TalkProps{
Talks: talks,
}
// Specify the partials used by this page
partials := []string{"header", "navitems", "cardlinks"}
// Render the template
return lib.RenderTemplate(c.Response().Writer, "base", partials, props)
}

View File

@@ -1,14 +0,0 @@
package pages
import (
"atri.dad/lib"
"github.com/labstack/echo/v4"
)
func SSEDemo(c echo.Context) error {
// Specify the partials used by this page
partials := []string{"header", "navitems"}
// Render the template
return lib.RenderTemplate(c.Response().Writer, "base", partials, nil)
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Some files were not shown because too many files have changed in this diff Show More