188 Commits

Author SHA1 Message Date
c2169a0a87 Update dependency svelte to v5.36.16 2025-07-24 17:00:28 +00:00
ngn
91a11e4611 give a static height to the page header
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 27s
Signed-off-by: ngn <ngn@ngn.tf>
2025-07-24 18:45:09 +03:00
ngn
13a703352b fix link parsing for the locales
Signed-off-by: ngn <ngn@ngn.tf>
2025-07-24 18:30:42 +03:00
ngn
2c5388bcce add border to separate service list from paragraph
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 27s
Signed-off-by: ngn <ngn@ngn.tf>
2025-07-24 17:49:11 +03:00
ngn
f053b93c24 fix the service component style
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 29s
Signed-off-by: ngn <ngn@ngn.tf>
2025-07-24 17:40:18 +03:00
ngn
babbe50c00 add missing locales
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 29s
Signed-off-by: ngn <ngn@ngn.tf>
2025-07-24 17:19:27 +03:00
ngn
92f594f27f don't ignore all yaml files in gitignore
Signed-off-by: ngn <ngn@ngn.tf>
2025-07-24 17:19:10 +03:00
ngn
cb5f5628a3 stay on the same page after switching locale
Signed-off-by: ngn <ngn@ngn.tf>
2025-07-24 17:18:00 +03:00
ngn
8adbc9d354 update node container for the web app
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 56s
Signed-off-by: ngn <ngn@ngn.tf>
2025-07-24 17:09:37 +03:00
ngn
8a084c4e95 Merge branch 'dev'
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 35s
Build the docker image for the doc server / build (push) Successful in 12s
2025-07-24 16:56:12 +03:00
ngn
897d445cdc use runes mode, allow switching locale without js
Signed-off-by: ngn <ngn@ngn.tf>
2025-07-24 16:50:32 +03:00
ngn
ee4b8a1d50 switch to a simpler css based typing animation
Signed-off-by: ngn <ngn@ngn.tf>
2025-07-24 15:40:38 +03:00
ngn
53a0fc3927 cleanup doc.js for a better export interface
Signed-off-by: ngn <ngn@ngn.tf>
2025-07-24 14:27:27 +03:00
ngn
84763e7ef9 fix the clean command for the web app
Signed-off-by: ngn <ngn@ngn.tf>
2025-07-24 14:11:08 +03:00
ngn
8b0cf20bf2 fix clean command and fix formatting for doc pages
Signed-off-by: ngn <ngn@ngn.tf>
2025-07-24 14:09:21 +03:00
ngn
e3692f90b1 remove bloat fonts, get rid of svelte-i18n
Signed-off-by: ngn <ngn@ngn.tf>
2025-07-24 06:15:19 +03:00
ngn
bf95c575eb Merge pull request 'Update dependency marked to v16.1.1' (#93) from renovate/marked-16.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 36s
Reviewed-on: #93
2025-07-19 16:42:42 +03:00
ngn
60087fc306 Merge pull request 'Update dependency @sveltejs/kit to v2.25.1' (#94) from renovate/sveltejs-kit-2.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 45s
Reviewed-on: #94
2025-07-19 16:38:22 +03:00
ngn
f20883cb26 Merge pull request 'Update dependency svelte to v5.36.8' (#92) from renovate/svelte-5.x-lockfile into main
Some checks failed
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #92
2025-07-19 16:38:13 +03:00
924461b9a2 Update dependency marked to v16.1.1 2025-07-18 17:00:41 +00:00
0b065172f5 Update dependency svelte to v5.36.8 2025-07-18 17:00:36 +00:00
c2c9893ab3 Update dependency @sveltejs/kit to v2.25.1 2025-07-18 12:04:29 +00:00
ngn
704430b91a Merge pull request 'Update dependency svelte to v5.36.6' (#90) from renovate/svelte-5.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 49s
Reviewed-on: #90
2025-07-17 18:42:36 +03:00
ngn
9f443c66b2 Merge pull request 'Update dependency @sveltejs/kit to v2.25.0' (#91) from renovate/sveltejs-kit-2.x-lockfile into main
Some checks failed
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #91
2025-07-17 18:42:23 +03:00
065df723a9 Update dependency svelte to v5.36.6 2025-07-17 12:05:47 +00:00
557cea36d8 Update dependency @sveltejs/kit to v2.25.0 2025-07-17 12:05:40 +00:00
ngn
c287ffa086 fix showing the active link in the navbar
All checks were successful
Build the docker image for the API / build (push) Successful in 2m16s
Build the docker image for the frontend application / build (push) Successful in 40s
Signed-off-by: ngn <ngn@ngn.tf>
2025-07-11 22:00:56 +03:00
ngn
26d656896e Merge pull request 'Update dependency @sveltejs/kit to v2.22.5' (#85) from renovate/sveltejs-kit-2.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 53s
Reviewed-on: #85
2025-07-11 19:00:38 +03:00
ngn
734bbdb998 Merge pull request 'Update dependency prettier to v3.6.2' (#82) from renovate/prettier-3.x-lockfile into main
Some checks failed
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #82
2025-07-11 19:00:24 +03:00
ngn
7e8a927bb3 Merge pull request 'Update dependency svelte to v5.35.6' (#84) from renovate/svelte-5.x-lockfile into main
Some checks failed
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #84
2025-07-11 19:00:13 +03:00
ngn
f08b04e710 Merge pull request 'Update dependency marked to v16' (#86) from renovate/marked-16.x into main
Some checks failed
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #86
2025-07-11 19:00:03 +03:00
ngn
fa6c1d8b2c Merge pull request 'Update golang Docker tag to v1.24.5' (#88) from renovate/golang-1.x into main
All checks were successful
Build the docker image for the API / build (push) Successful in 3m18s
Reviewed-on: #88
2025-07-11 18:59:50 +03:00
ngn
0c1e07b779 Merge pull request 'Update dependency @sveltejs/adapter-node to v5.2.13' (#87) from renovate/sveltejs-adapter-node-5.x-lockfile into main
Some checks failed
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #87
2025-07-11 18:59:40 +03:00
c615409a42 Update dependency svelte to v5.35.6 2025-07-11 02:05:09 +00:00
da50365380 Update dependency @sveltejs/kit to v2.22.5 2025-07-10 12:04:52 +00:00
68f5480ff1 Update golang Docker tag to v1.24.5 2025-07-09 21:04:42 +00:00
12216ca0f7 Update dependency @sveltejs/adapter-node to v5.2.13 2025-07-09 17:04:35 +00:00
7e407b181d Update dependency marked to v16 2025-06-27 07:05:17 +00:00
4f0e911407 Update dependency prettier to v3.6.2 2025-06-27 07:05:11 +00:00
ngn
b447c8cf13 Merge pull request 'Update dependency svelte to v5.34.7' (#80) from renovate/svelte-5.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 41s
Reviewed-on: #80
2025-06-20 11:36:23 +03:00
ngn
e212d43bf2 Merge pull request 'Update dependency @sveltejs/kit to v2.22.0' (#81) from renovate/sveltejs-kit-2.x-lockfile into main
Some checks failed
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #81
2025-06-20 11:36:13 +03:00
2bf94c0f3c Update dependency @sveltejs/kit to v2.22.0 2025-06-20 07:05:19 +00:00
3f49d8294a Update dependency svelte to v5.34.7 2025-06-19 17:04:56 +00:00
ngn
323b76fe98 Merge pull request 'Update dependency svelte to v5.34.4' (#79) from renovate/svelte-5.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 42s
Reviewed-on: #79
2025-06-18 02:24:41 +03:00
f714bc6eaa Update dependency svelte to v5.34.4 2025-06-17 21:04:15 +00:00
ngn
8ad2778dd0 Merge pull request 'Update dependency @sveltejs/kit to v2.21.5' (#77) from renovate/sveltejs-kit-2.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 38s
Reviewed-on: #77
2025-06-14 20:19:29 +03:00
ngn
0d99a7da69 Merge pull request 'Update dependency svelte to v5.34.3' (#78) from renovate/svelte-5.x-lockfile into main
Some checks failed
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #78
2025-06-14 20:19:17 +03:00
c049f6c44a Update dependency svelte to v5.34.3 2025-06-14 17:05:11 +00:00
9acbe212ad Update dependency @sveltejs/kit to v2.21.5 2025-06-13 07:04:12 +00:00
ngn
aa29cca4f4 Merge pull request 'Update dependency svelte to v5.34.1' (#76) from renovate/svelte-5.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 48s
Reviewed-on: #76
2025-06-12 11:04:53 +03:00
afc0a30660 Update dependency svelte to v5.34.1 2025-06-12 02:03:58 +00:00
ngn
647634addc Merge pull request 'Update dependency @sveltejs/kit to v2.21.4' (#74) from renovate/sveltejs-kit-2.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 37s
Reviewed-on: #74
2025-06-10 20:37:43 +03:00
ngn
dba65911a6 Merge pull request 'Update dependency svelte to v5.33.19' (#75) from renovate/svelte-5.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 39s
Reviewed-on: #75
2025-06-10 20:36:40 +03:00
1067d0a3fd Update dependency svelte to v5.33.19 2025-06-10 17:04:40 +00:00
1ffd51b641 Update dependency @sveltejs/kit to v2.21.4 2025-06-10 17:04:36 +00:00
ngn
cd97df5f21 Merge pull request 'Update dependency svelte to v5.33.18' (#71) from renovate/svelte-5.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 43s
Reviewed-on: #71
2025-06-10 19:41:54 +03:00
ngn
12dedf3700 Merge pull request 'Update dependency @sveltejs/kit to v2.21.3' (#72) from renovate/sveltejs-kit-2.x-lockfile into main
Some checks failed
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #72
2025-06-10 19:41:42 +03:00
ngn
ade745cdc5 Merge pull request 'Update golang Docker tag to v1.24.4' (#73) from renovate/golang-1.x into main
All checks were successful
Build the docker image for the API / build (push) Successful in 2m54s
Reviewed-on: #73
2025-06-10 19:41:32 +03:00
ab5977eaae Update dependency @sveltejs/kit to v2.21.3 2025-06-09 02:03:38 +00:00
39813d246f Update dependency svelte to v5.33.18 2025-06-08 12:04:00 +00:00
975626fc5c Update golang Docker tag to v1.24.4 2025-06-05 21:04:27 +00:00
ngn
1f47d59760 Merge pull request 'Update dependency svelte to v5.33.13' (#70) from renovate/svelte-5.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 47s
Reviewed-on: #70
2025-06-02 16:13:18 +03:00
38abe411a8 Update dependency svelte to v5.33.13 2025-06-02 02:04:01 +00:00
ngn
e7216d51e3 Merge pull request 'Update dependency svelte to v5.33.12' (#69) from renovate/svelte-5.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 45s
Reviewed-on: #69
2025-06-02 00:56:30 +03:00
2b244645c6 Update dependency svelte to v5.33.12 2025-06-01 21:04:14 +00:00
ngn
43589f7ddc Merge pull request 'Update dependency svelte to v5.33.11' (#68) from renovate/svelte-5.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 58s
Reviewed-on: #68
2025-06-01 14:10:29 +03:00
b43a219fb3 Update dependency svelte to v5.33.11 2025-05-31 21:09:20 +00:00
ngn
7bf92f9049 Merge pull request 'Update dependency svelte to v5.33.6' (#67) from renovate/svelte-5.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 54s
Reviewed-on: #67
2025-05-29 10:40:39 +03:00
577195cb7f Update dependency svelte to v5.33.6 2025-05-29 07:04:18 +00:00
ngn
fc64b742bb Merge pull request 'Update dependency svelte to v5.33.1' (#66) from renovate/svelte-5.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 1m7s
Reviewed-on: #66
2025-05-23 16:50:39 +03:00
922eaeb124 Update dependency svelte to v5.33.1 2025-05-23 02:05:57 +00:00
ngn
65ec5a51aa Merge pull request 'Update dependency svelte to v5.33.0' (#63) from renovate/svelte-5.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 1m8s
Reviewed-on: #63
2025-05-22 23:34:57 +03:00
ngn
8036b0c1e1 Merge pull request 'Update module github.com/gofiber/fiber/v2 to v2.52.8' (#65) from renovate/github.com-gofiber-fiber-v2-2.x into main
All checks were successful
Build the docker image for the API / build (push) Successful in 2m53s
Reviewed-on: #65
2025-05-22 23:34:41 +03:00
ngn
c505698b0b fix the pages directory in doc server Dockerfile
All checks were successful
Build the docker image for the doc server / build (push) Successful in 1m1s
Signed-off-by: ngn <ngn@ngn.tf>
2025-05-22 20:15:03 +03:00
ngn
4e08375748 Merge branch 'main' of https://git.ngn.tf/ngn/website
Some checks failed
Build the docker image for the doc server / build (push) Failing after 15s
2025-05-22 20:12:41 +03:00
ngn
912bf616b6 update doc server to ctorm 1.8.1
Signed-off-by: ngn <ngn@ngn.tf>
2025-05-22 20:12:05 +03:00
d1ddbf2605 Update dependency svelte to v5.33.0 2025-05-22 17:07:27 +00:00
66b01bd58d Update module github.com/gofiber/fiber/v2 to v2.52.8 2025-05-22 07:06:48 +00:00
ngn
01468670e3 Merge pull request 'Update dependency marked to v15.0.12' (#62) from renovate/marked-15.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 1m11s
Reviewed-on: #62
2025-05-20 15:48:20 +03:00
ngn
1f09aae11e revert ce1ac7b09e
All checks were successful
Build the docker image for the doc server / build (push) Successful in 11s
revert Merge pull request 'Update ghcr.io/ngn13/ctorm Docker tag to v1.8' (#61) from renovate/ghcr.io-ngn13-ctorm-1.x into main

Reviewed-on: #61
2025-05-20 15:47:59 +03:00
ngn
ce1ac7b09e Merge pull request 'Update ghcr.io/ngn13/ctorm Docker tag to v1.8' (#61) from renovate/ghcr.io-ngn13-ctorm-1.x into main
Some checks failed
Build the docker image for the doc server / build (push) Has been cancelled
Reviewed-on: #61
2025-05-20 15:47:32 +03:00
f9faddf660 Update dependency marked to v15.0.12 2025-05-20 07:07:09 +00:00
945f1b84f2 Update ghcr.io/ngn13/ctorm Docker tag to v1.8 2025-05-20 02:07:44 +00:00
ngn
a9d2633107 Merge branch 'main' of https://git.ngn.tf/ngn/website 2025-05-20 01:17:34 +03:00
ngn
506d16e0c9 update the readme formatting, remove old assets
Signed-off-by: ngn <ngn@ngn.tf>
2025-05-20 01:17:04 +03:00
ngn
30c3b0c37b Merge pull request 'Update dependency dompurify to v3.2.6' (#59) from renovate/dompurify-3.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 1m8s
Reviewed-on: #59
2025-05-20 01:13:59 +03:00
ngn
2da4f81a6b Merge pull request 'Update dependency svelte to v5.31.1' (#60) from renovate/svelte-5.x-lockfile into main
Some checks failed
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #60
2025-05-20 01:13:49 +03:00
cc482fc0ad Update dependency svelte to v5.31.1 2025-05-19 21:21:28 +00:00
51ea22fbc5 Update dependency dompurify to v3.2.6 2025-05-19 17:05:37 +00:00
ngn
58595d5eb3 Merge pull request 'Update dependency @sveltejs/kit to v2.21.1' (#50) from renovate/sveltejs-kit-2.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 1m13s
Reviewed-on: #50
2025-05-19 16:26:10 +03:00
ngn
671d44017f Merge pull request 'Update github.com/ngn13/ortam digest to 8dea816' (#57) from renovate/github.com-ngn13-ortam-digest into main
All checks were successful
Build the docker image for the API / build (push) Successful in 2m40s
Reviewed-on: #57
2025-05-19 16:25:55 +03:00
ngn
95d24b734b Merge pull request 'Update dependency svelte to v5.30.2' (#49) from renovate/svelte-5.x-lockfile into main
Some checks failed
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #49
2025-05-19 16:25:33 +03:00
ngn
c2e31fb440 Merge pull request 'Update Node.js to v23.11.1' (#58) from renovate/node-23.x into main
Some checks failed
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #58
2025-05-19 16:25:22 +03:00
2d3dab5646 Update dependency @sveltejs/kit to v2.21.1 2025-05-18 21:09:17 +00:00
4b2e12b374 Update dependency svelte to v5.30.2 2025-05-18 12:09:46 +00:00
c6323cae38 Update Node.js to v23.11.1 2025-05-18 12:09:31 +00:00
e05d42a956 Update github.com/ngn13/ortam digest to 8dea816 2025-05-18 12:09:27 +00:00
ngn
426c474016 Merge pull request 'Update module github.com/mattn/go-sqlite3 to v1.14.28' (#51) from renovate/github.com-mattn-go-sqlite3-1.x into main
All checks were successful
Build the docker image for the API / build (push) Successful in 6m28s
Reviewed-on: #51
2025-05-18 14:18:43 +03:00
ngn
91ca908980 Merge pull request 'Update dependency marked to v15.0.11' (#52) from renovate/marked-15.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 3m31s
Reviewed-on: #52
2025-05-18 14:18:32 +03:00
ngn
5a61508359 Merge pull request 'Update dependency vite to v5.4.19' (#53) from renovate/vite-5.x-lockfile into main
Some checks failed
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #53
2025-05-18 14:18:22 +03:00
ngn
86c0e3cc97 Merge pull request 'Update golang Docker tag to v1.24.3' (#54) from renovate/golang-1.x into main
Some checks failed
Build the docker image for the API / build (push) Has been cancelled
Reviewed-on: #54
2025-05-18 14:18:10 +03:00
ngn
1afa33f28b Merge pull request 'Update dependency @sveltejs/adapter-auto to v6.0.1' (#55) from renovate/sveltejs-adapter-auto-6.x-lockfile into main
Some checks failed
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #55
2025-05-18 14:17:54 +03:00
ngn
a22b1e67c8 Merge pull request 'Update dependency prettier-plugin-svelte to v3.4.0' (#56) from renovate/prettier-plugin-svelte-3.x-lockfile into main
Some checks failed
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #56
2025-05-18 14:17:38 +03:00
f477b4525b Update dependency prettier-plugin-svelte to v3.4.0 2025-05-14 17:06:36 +00:00
462d35ea0d Update dependency @sveltejs/adapter-auto to v6.0.1 2025-05-12 21:03:05 +00:00
693c94274a Update golang Docker tag to v1.24.3 2025-05-06 21:03:55 +00:00
af2e5b49ae Update dependency vite to v5.4.19 2025-04-30 07:03:16 +00:00
33a31dac32 Update dependency marked to v15.0.11 2025-04-25 02:03:31 +00:00
00b7227d4d Update module github.com/mattn/go-sqlite3 to v1.14.28 2025-04-16 17:03:50 +00:00
ngn
2f9d38cd45 Merge pull request 'Update dependency @sveltejs/adapter-auto to v6' (#42) from renovate/sveltejs-adapter-auto-6.x into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 33s
Reviewed-on: #42
2025-04-13 01:12:44 +03:00
ngn
e68dd1d317 Merge pull request 'Update dependency svelte to v5.26.2' (#46) from renovate/svelte-5.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 37s
Reviewed-on: #46
2025-04-13 01:12:02 +03:00
ngn
fe834d2778 Merge pull request 'Update dependency @sveltejs/kit to v2.20.5' (#47) from renovate/sveltejs-kit-2.x-lockfile into main
Some checks failed
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #47
2025-04-13 01:10:46 +03:00
ngn
686f51dcde Merge pull request 'Update dependency vite to v5.4.18' (#48) from renovate/vite-5.x-lockfile into main
Some checks are pending
Build the docker image for the frontend application / build (push) Waiting to run
Reviewed-on: #48
2025-04-13 01:10:35 +03:00
ngn
dc43bdb3af remove the old config library files from the API
All checks were successful
Build the docker image for the API / build (push) Successful in 2m36s
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-13 01:07:20 +03:00
ngn
05185cb82a use the ortam config library in the API
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-13 01:06:37 +03:00
ee3fa10096 Update dependency svelte to v5.26.2 2025-04-12 17:02:55 +00:00
0e4457d5fb Update dependency vite to v5.4.18 2025-04-10 07:03:05 +00:00
b95c51193e Update dependency @sveltejs/kit to v2.20.5 2025-04-09 21:03:23 +00:00
ngn
322d8d5c39 Merge pull request 'Update dependency svelte to v5.25.8' (#45) from renovate/svelte-5.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 34s
Reviewed-on: #45
2025-04-08 05:04:24 +03:00
ngn
904e9f6d15 Merge pull request 'Update dependency marked to v15.0.8' (#44) from renovate/marked-15.x-lockfile into main
Some checks are pending
Build the docker image for the frontend application / build (push) Waiting to run
Reviewed-on: #44
2025-04-08 05:04:13 +03:00
cc4abc85fe Update dependency svelte to v5.25.8 2025-04-08 02:03:45 +00:00
73cfcea923 Update dependency marked to v15.0.8 2025-04-08 02:03:40 +00:00
ngn
90af3d0500 update the app_url config option
All checks were successful
Build the docker image for the API / build (push) Successful in 2m6s
Build the docker image for the frontend application / build (push) Successful in 37s
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-08 03:07:14 +03:00
ngn
8d16273540 add /api route to the API documentation
All checks were successful
Build the docker image for the doc server / build (push) Successful in 14s
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-08 03:00:02 +03:00
ngn
4fb78c0ab7 update frontend app version
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 35s
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-08 02:55:58 +03:00
ngn
a57a4955ba fix internal api url
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-08 02:54:23 +03:00
ngn
ccf0d8abf9 make stuff work without js
All checks were successful
Build the docker image for the API / build (push) Successful in 2m7s
Build the docker image for the frontend application / build (push) Successful in 42s
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-08 02:39:37 +03:00
ngn
b312e40204 use the correct package manager
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 36s
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-07 06:04:34 +03:00
ngn
a67bd74ced fix dockerfile by adding required build programs
Some checks failed
Build the docker image for the frontend application / build (push) Failing after 10s
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-07 06:03:00 +03:00
ngn
9a72d3f95d dump nerdfonts from the website to use locally
Some checks failed
Build the docker image for the frontend application / build (push) Failing after 20s
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-07 05:58:46 +03:00
ngn
66c96ae312 update the example compose file
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-07 05:16:56 +03:00
ngn
f779a03ae5 update app dockerfile to include new envs
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 35s
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-07 05:12:08 +03:00
ngn
bcb48d789b update the app URL env var name in API config
All checks were successful
Build the docker image for the API / build (push) Successful in 2m18s
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-07 04:57:29 +03:00
ngn
aa69525912 Merge branch 'main' of https://git.ngn.tf/ngn/website
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 44s
2025-04-07 04:49:00 +03:00
ngn
0134bd2ab1 add env vars for configuring onion and I2P urls
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-07 04:46:47 +03:00
ngn
84e477b23e Merge pull request 'Update dependency dompurify to v3.2.5' (#43) from renovate/dompurify-3.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 35s
Reviewed-on: #43
2025-04-06 00:05:18 +03:00
515dcf6a09 Update dependency dompurify to v3.2.5 2025-04-05 21:03:17 +00:00
ngn
a50cc1ff7f Merge pull request 'Update dependency @sveltejs/kit to v2.20.4' (#41) from renovate/sveltejs-kit-2.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 40s
Reviewed-on: #41
2025-04-05 23:50:09 +03:00
ngn
f2ec9479b1 Merge pull request 'Update module github.com/mattn/go-sqlite3 to v1.14.27' (#40) from renovate/github.com-mattn-go-sqlite3-1.x into main
All checks were successful
Build the docker image for the API / build (push) Successful in 2m18s
Reviewed-on: #40
2025-04-05 23:43:34 +03:00
ngn
e1eae3c05b Merge pull request 'Update dependency vite to v5.4.17' (#39) from renovate/vite-5.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 38s
Reviewed-on: #39
2025-04-05 23:38:39 +03:00
ngn
53ab201ab3 Merge pull request 'Update golang Docker tag to v1.24.2' (#36) from renovate/golang-1.x into main
All checks were successful
Build the docker image for the API / build (push) Successful in 3m4s
Reviewed-on: #36
2025-04-05 23:38:18 +03:00
ngn
c9792fb6be Merge pull request 'Update Node.js to v23.11.0' (#35) from renovate/node-23.x into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 40s
Reviewed-on: #35
2025-04-05 23:24:26 +03:00
ngn
68808a14f9 Merge pull request 'Update dependency prettier to v3.5.3' (#34) from renovate/prettier-3.x-lockfile into main
Some checks failed
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #34
2025-04-05 23:24:16 +03:00
ngn
4343192a2c Merge pull request 'Update dependency svelte to v5.25.7' (#28) from renovate/svelte-5.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 36s
Reviewed-on: #28
2025-04-05 23:22:31 +03:00
20fb3cd685 Update dependency svelte to v5.25.7 2025-04-05 17:03:22 +00:00
906ace188b Update dependency vite to v5.4.17 2025-04-05 17:03:16 +00:00
f2a1a21a8c Update dependency prettier to v3.5.3 2025-04-05 17:03:12 +00:00
9353631931 Update dependency @sveltejs/kit to v2.20.4 2025-04-05 17:03:08 +00:00
ngn
4eb5ad6ef3 add _version to easily access app/api version
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 57s
Build the docker image for the doc server / build (push) Successful in 10s
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-05 19:52:51 +03:00
ngn
bac570615e bump frontend version
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-05 19:40:29 +03:00
ngn
037a4405b9 fix service info, update images, add meta tags
Signed-off-by: ngn <ngn@ngn.tf>
2025-04-05 19:39:10 +03:00
389a9d3df4 Update module github.com/mattn/go-sqlite3 to v1.14.27 2025-04-02 17:04:40 +00:00
921fe4255d Update dependency @sveltejs/adapter-auto to v6 2025-04-02 07:03:12 +00:00
f7e4442de5 Update Node.js to v23.11.0 2025-04-01 21:03:03 +00:00
e4d1fc21fb Update golang Docker tag to v1.24.2 2025-04-01 21:03:00 +00:00
ngn
fe6b3512fd Merge pull request 'Update dependency @sveltejs/kit to v2.20.2' (#32) from renovate/sveltejs-kit-2.x-lockfile into main
All checks were successful
Build the docker image for the frontend application / build (push) Successful in 26s
Reviewed-on: #32
2025-03-25 00:11:28 +03:00
ngn
3dbc2e0510 Merge branch 'main' of https://git.ngn.tf/ngn/website 2025-03-25 00:09:30 +03:00
ngn
26362cf89d [skip ci] add path filters to workflows
Signed-off-by: ngn <ngn@ngn.tf>
2025-03-25 00:08:09 +03:00
ngn
16288c2bcb Merge pull request 'Update dependency vite to v5.4.15' (#38) from renovate/vite-5.x-lockfile into main
All checks were successful
Build the docker image for the API / build (push) Successful in 2m26s
Build the docker image for the frontend application / build (push) Successful in 51s
Build the docker image for the doc server / build (push) Successful in 8s
Reviewed-on: #38
2025-03-25 00:06:41 +03:00
177b546fb9 Update dependency vite to v5.4.15 2025-03-24 12:03:27 +00:00
311a37ae5c Update dependency @sveltejs/kit to v2.20.2 2025-03-21 12:02:44 +00:00
ngn
4380638982 Merge pull request 'Update golang Docker tag to v1.24.0' (#29) from renovate/golang-1.x into main
All checks were successful
Build the docker image for the API / build (push) Successful in 3m6s
Build the docker image for the frontend application / build (push) Successful in 1m12s
Build the docker image for the doc server / build (push) Successful in 11s
Reviewed-on: #29
2025-02-15 01:22:48 +03:00
ngn
32e0ddaa36 Merge branch 'main' into renovate/golang-1.x 2025-02-15 01:22:38 +03:00
ngn
27c58b1174 Merge pull request 'Update dependency prettier to v3.5.1' (#30) from renovate/prettier-3.x-lockfile into main
Some checks failed
Build the docker image for the doc server / build (push) Waiting to run
Build the docker image for the API / build (push) Successful in 14s
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #30
2025-02-15 01:22:28 +03:00
d5f1325b79 Update dependency prettier to v3.5.1 2025-02-13 17:03:53 +00:00
9e27aabdb9 Update golang Docker tag to v1.24.0 2025-02-12 21:03:55 +00:00
ngn
a1967141ae Merge pull request 'Update dependency marked to v15.0.7' (#26) from renovate/marked-15.x-lockfile into main
All checks were successful
Build the docker image for the API / build (push) Successful in 8s
Build the docker image for the frontend application / build (push) Successful in 56s
Build the docker image for the doc server / build (push) Successful in 9s
Reviewed-on: #26
2025-02-12 19:49:05 +03:00
ngn
a172262dbd Merge branch 'main' into renovate/marked-15.x-lockfile 2025-02-12 19:48:58 +03:00
ngn
caee8c2a90 Merge pull request 'Update dependency svelte to v5.19.10' (#27) from renovate/svelte-5.x-lockfile into main
Some checks failed
Build the docker image for the frontend application / build (push) Waiting to run
Build the docker image for the doc server / build (push) Waiting to run
Build the docker image for the API / build (push) Has been cancelled
Reviewed-on: #27
2025-02-12 19:48:53 +03:00
060c5f0c73 Update dependency svelte to v5.19.10 2025-02-11 17:04:42 +00:00
e2796b39b8 Update dependency marked to v15.0.7 2025-02-10 02:03:06 +00:00
ngn
191c54cc9a Merge pull request 'Update dependency svelte to v5.19.9' (#24) from renovate/svelte-5.x-lockfile into main
All checks were successful
Build the docker image for the API / build (push) Successful in 8s
Build the docker image for the frontend application / build (push) Successful in 47s
Build the docker image for the doc server / build (push) Successful in 7s
Reviewed-on: #24
2025-02-09 19:14:43 +03:00
ngn
87095a8c35 Merge branch 'main' into renovate/svelte-5.x-lockfile 2025-02-09 19:14:35 +03:00
ngn
a28a678693 Merge pull request 'Update dependency prettier to v3.5.0' (#25) from renovate/prettier-3.x-lockfile into main
Some checks failed
Build the docker image for the doc server / build (push) Waiting to run
Build the docker image for the API / build (push) Successful in 10s
Build the docker image for the frontend application / build (push) Has been cancelled
Reviewed-on: #25
2025-02-09 19:14:27 +03:00
d3fd1e479c Update dependency prettier to v3.5.0 2025-02-09 12:03:11 +00:00
7f97f5330e Update dependency svelte to v5.19.9 2025-02-07 02:03:20 +00:00
ngn
30286710d1 Merge pull request 'Update dependency @sveltejs/kit to v2.17.1' (#22) from renovate/sveltejs-kit-2.x-lockfile into main
All checks were successful
Build the docker image for the API / build (push) Successful in 2m43s
Build the docker image for the frontend application / build (push) Successful in 55s
Build the docker image for the doc server / build (push) Successful in 9s
Reviewed-on: #22
2025-02-05 19:09:56 +03:00
ngn
8de018bf5c Merge branch 'main' into renovate/sveltejs-kit-2.x-lockfile 2025-02-05 19:09:47 +03:00
ngn
5a73910331 Merge pull request 'Update golang Docker tag to v1.23.6' (#23) from renovate/golang-1.x into main
Some checks are pending
Build the docker image for the API / build (push) Waiting to run
Build the docker image for the frontend application / build (push) Waiting to run
Build the docker image for the doc server / build (push) Waiting to run
Reviewed-on: #23
2025-02-05 19:09:37 +03:00
53236fab61 Update golang Docker tag to v1.23.6 2025-02-04 22:03:52 +00:00
d5dfaaad96 Update dependency @sveltejs/kit to v2.17.1 2025-02-04 21:06:20 +00:00
ngn
c1e2974f1b Merge pull request 'Update dependency svelte to v5.19.7' (#20) from renovate/svelte-5.x-lockfile into main
All checks were successful
Build the docker image for the API / build (push) Successful in 3m40s
Build the docker image for the frontend application / build (push) Successful in 59s
Build the docker image for the doc server / build (push) Successful in 12s
Reviewed-on: #20
2025-02-04 23:24:47 +03:00
ngn
40cdbfba36 Merge branch 'main' into renovate/svelte-5.x-lockfile 2025-02-04 23:24:39 +03:00
ngn
be8777eb67 Merge pull request 'Update dependency dompurify to v3.2.4' (#21) from renovate/dompurify-3.x-lockfile into main
Some checks failed
Build the docker image for the frontend application / build (push) Waiting to run
Build the docker image for the doc server / build (push) Waiting to run
Build the docker image for the API / build (push) Has been cancelled
Reviewed-on: #21
2025-02-04 23:24:27 +03:00
ngn
b75bd4f3b2 Merge branch 'main' into renovate/dompurify-3.x-lockfile 2025-02-04 23:24:10 +03:00
ngn
c127c62492 Merge pull request 'Update Node.js to v23.7.0' (#18) from renovate/node-23.x into main
Some checks failed
Build the docker image for the frontend application / build (push) Waiting to run
Build the docker image for the doc server / build (push) Waiting to run
Build the docker image for the API / build (push) Has been cancelled
Reviewed-on: #18
2025-02-04 23:24:01 +03:00
1389233108 Update Node.js to v23.7.0 2025-01-31 20:04:10 +00:00
e2a4df8602 Update dependency dompurify to v3.2.4 2025-01-30 10:04:17 +00:00
117 changed files with 3077 additions and 3453 deletions

View File

@@ -3,6 +3,7 @@ name: Build the docker image for the API
on:
push:
branches: ["main"]
paths: ["api/**"]
env:
REGISTRY: git.ngn.tf

View File

@@ -3,6 +3,7 @@ name: Build the docker image for the frontend application
on:
push:
branches: ["main"]
paths: ["app/**"]
env:
REGISTRY: git.ngn.tf
@@ -27,8 +28,8 @@ jobs:
cd app
docker build --build-arg WEBSITE_REPORT_URL=https://git.ngn.tf/ngn/website/issues/new \
--build-arg WEBSITE_SOURCE_URL=https://git.ngn.tf/ngn/website \
--build-arg WEBSITE_APP_URL=https://ngn.tf \
--build-arg WEBSITE_API_URL=https://api.ngn.tf \
--build-arg WEBSITE_DOC_URL=http://doc:7003 \
--build-arg WEBSITE_API_URL=http://api:7002 \
--build-arg WEBSITE_API_PATH=/api \
--tag ${{env.REGISTRY}}/${{env.IMAGE}}:latest .
docker push ${{env.REGISTRY}}/${{env.IMAGE}}:latest

View File

@@ -3,6 +3,7 @@ name: Build the docker image for the doc server
on:
push:
branches: ["main"]
paths: ["doc/**"]
env:
REGISTRY: git.ngn.tf

14
.gitignore vendored
View File

@@ -1,11 +1,7 @@
# docker(-compose) stuff
data.db
*.yaml
*.yml
docker-compose.yaml
docker-compose.yml
compose.yaml
compose.yml
*.env
# don't ignore example deployment stuff
!deploy/*
!.github/*
!.gitea/*
!.github/*/*
!.gitea/*/*

141
README.md
View File

@@ -4,85 +4,112 @@
![](https://git.ngn.tf/ngn/website/actions/workflows/build-api.yml/badge.svg)
![](https://git.ngn.tf/ngn/website/actions/workflows/build-doc.yml/badge.svg)
This repo contains all the source code for my personal website, [ngn.tf](https://ngn.tf)
All code is licensed under AGPL version 3 (see [LICENSE.txt](LICENSE.txt))
This repo contains all the source code for my personal website,
[ngn.tf](https://ngn.tf) All code is licensed under AGPL version 3 (see
[LICENSE.txt](LICENSE.txt))
## Directory structure
### `app`
Contains frontend application, written with SvelteKit. It supports full SSR.
Contains modified CSS from [github-markdown-css](https://github.com/sindresorhus/github-markdown-css)
and fonts from [NerdFonts](https://www.nerdfonts.com/)
Contains modified CSS from
[github-markdown-css](https://github.com/sindresorhus/github-markdown-css) and
fonts from [NerdFonts](https://www.nerdfonts.com/)
### `api`
Contains the API server, written in Go. It uses the [Fiber](https://github.com/gofiber/fiber) web
framework which offers an [Express](https://expressjs.com/) like experience. I choose Fiber since I've used
worked with express a lot in the past. However previously the I was using [Gin](https://github.com/gin-gonic/gin)
(see history section).
API stores all the data in a local SQLite(3) database. Go doesn't support SQLite3 out of the box so
I'm using [mattn's sqlite3 driver](https://github.com/mattn/go-sqlite3).
Contains the API server, written in Go. It uses the
[Fiber](https://github.com/gofiber/fiber) web framework which offers an
[Express](https://expressjs.com/) like experience. I choose Fiber since I've
used worked with express a lot in the past. However previously the I was using
[Gin](https://github.com/gin-gonic/gin) (see history section).
API stores all the data in a local SQLite(3) database. Go doesn't support
SQLite3 out of the box so I'm using
[mattn's sqlite3 driver](https://github.com/mattn/go-sqlite3).
### `doc`
Contains the documentation server, written in C. It uses the [ctorm](https://github.com/ngn13/ctorm) web
framework, which is a framework that I myself wrote. Unlike the frontend application or the API server, it's not
accessable by public, the frontend application gets the documentation content from this server and renders it using
SSR. The reason I don't use the API for hosting the documentation content is that I want a separate server for hosting
Contains the documentation server, written in C. It uses the
[ctorm](https://github.com/ngn13/ctorm) web framework, which is a framework that
I myself wrote. Unlike the frontend application or the API server, it's not
accessable by public, the frontend application gets the documentation content
from this server and renders it using SSR. The reason I don't use the API for
hosting the documentation content is that I want a separate server for hosting
static content, API is only for hosting dynamic stuff.
### `admin`
The frontend application does not contain an admin interface, I do the administration stuff (such as adding news posts,
adding services etc.) using the python script in this directory. This script can be installed on to the PATH by running
the Makefile install script. After installation it can be used by running `admin_script`.
The frontend application does not contain an admin interface, I do the
administration stuff (such as adding news posts, adding services etc.) using the
python script in this directory. This script can be installed on to the PATH by
running the Makefile install script. After installation it can be used by
running `admin_script`.
## Deployment
Easiest way to deploy is to use docker. There is `compose.yml` and a `run.sh` script in the [deploy](deploy/) directory
that can be used to startup all the docker containers. Configuration options are passed during build time for the frontend
application, and for others it's passed with environment variables.
Easiest way to deploy is to use docker. There is `compose.yml` and a `run.sh`
script in the [deploy](deploy/) directory that can be used to startup all the
docker containers. Configuration options are passed during build time for the
frontend application, and for others it's passed with environment variables.
## History
Some nostalgic history/changelog stuff (just for the major version numbers):
- **v0.1 (late 2020 - early 2021)**: First ever version of my website, it was just a simple HTML/CSS page,
I never published any of the source code and I wiped the local copy on my USB drive in early 2022, I still
remember what it looked like though, it looked like I made entire website in microsoft paint... while blindfoled,
so yeah it was shit.
- **v0.1 (late 2020 - early 2021)**: First ever version of my website, it was
just a simple HTML/CSS page, I never published any of the source code and I
wiped the local copy on my USB drive in early 2022, I still remember what it
looked like though, it looked like I made entire website in microsoft paint...
while blindfoled, so yeah it was shit.
- **v1.0 (early 2021 - late 2022)**: This version was actualy hosted on my github.io page, and all the source code
was (and still is) avaliable, it was just a simple static site, [here is a screenshot](assets/githubio.png).
- **v1.0 (early 2021 - late 2022)**: This version was actualy hosted on my
github.io page, and all the source code was (and still is) avaliable, it was
just a simple static site, [here is a screenshot](assets/githubio.png).
- **vLOST (late 2022 - early 2023)**: As I learned more JS, I decided to rewrite (and rework) my website with one
of the fancy JS frameworks. I decided to go with Svelte. Not the kit version, at the time svelte did not support SSR.
I do not remember writting an API for it so I guess I just updated it everytime I wanted to add content? It was pretty
much like a static website and was hosted on `ngn13.fun` as at this point I had my own hosting. The source code for
this website was in a deleted github repository of mine, I looked for a local copy on my old hard drive but I wasn't able
to find it. I also do not remember how it looked like, sooo this version is pretty much lost :(
- **vLOST (late 2022 - early 2023)**: As I learned more JS, I decided to rewrite
(and rework) my website with one of the fancy JS frameworks. I decided to go
with Svelte. Not the kit version, at the time svelte did not support SSR. I do
not remember writting an API for it so I guess I just updated it everytime I
wanted to add content? It was pretty much like a static website and was hosted
on `ngn13.fun` as at this point I had my own hosting. The source code for this
website was in a deleted github repository of mine, I looked for a local copy
on my old hard drive but I wasn't able to find it. I also do not remember how
it looked like, sooo this version is pretty much lost :(
- **v2.0 (early 2023 - late 2023)**: After I discovered what SSR is, I decided to rewrite and rework my website one more
time in NuxtJS. I had really "fun" time using vue stuff. As NuxtJS supported server-side code, this website had its own
built in API. This website was also hosted on `ngn13.fun`. This also the first version that lives on this git repository.
- **v2.0 (early 2023 - late 2023)**: After I discovered what SSR is, I decided
to rewrite and rework my website one more time in NuxtJS. I had really "fun"
time using vue stuff. As NuxtJS supported server-side code, this website had
its own built in API. This website was also hosted on `ngn13.fun`. This also
the first version that lives on this git repository.
- **v3.0 (2023 august - 2023 november)**: In agust of 2023, I decided to rewrite and rework my website again, this time
I was going with SvelteKit as I haven't had the greatest experience with NuxtJS. SvelteKit was really fun to work with
and I got my new website done pretty quickly. (I don't wanna brag or something but I really imporeved the CSS/styling
stuff ya know). I also wrote a new API with Go and Gin. I did not publish the source code for the API, the code lived
on my local git server until I deleted it when I was done with 6.0. This website was hosted on `ngn13.fun` as well.
- **v3.0 (2023 august - 2023 november)**: In agust of 2023, I decided to rewrite
and rework my website again, this time I was going with SvelteKit as I haven't
had the greatest experience with NuxtJS. SvelteKit was really fun to work with
and I got my new website done pretty quickly. (I don't wanna brag or something
but I really imporeved the CSS/styling stuff ya know). I also wrote a new API
with Go and Gin. I did not publish the source code for the API, the code lived
on my local git server until I deleted it when I was done with 6.0. This
website was hosted on `ngn13.fun` as well.
- **v4.0 (2023 november - 2024 october)**: In this version the frontend was still similar to 3.0, the big changes are in
the API. I rewrote the API with Fiber. This version was the first version hosted on `ngn.tf` which is my new domain name.
Here is a [picture of the index](assets/4.0_index.png) and the [blog page](assets/4.0_blog.png).
- **v4.0 (2023 november - 2024 october)**: In this version the frontend was
still similar to 3.0, the big changes are in the API. I rewrote the API with
Fiber. This version was the first version hosted on `ngn.tf` which is my new
domain name. Here is a [picture of the index](assets/4.0_index.png) and the
[blog page](assets/4.0_blog.png).
- **v5.0 (2024 october - 2025 january)**: This version just had simple frontend UI changes compared to 4.0, at this
point I was thinking about doing a massive rework (which I did with 6.0), however I was working on some other shit at
the time, so I just did some small changes with the limited time I had for this project.
- **v5.0 (2024 october - 2025 january)**: This version just had simple frontend
UI changes compared to 4.0, at this point I was thinking about doing a massive
rework (which I did with 6.0), however I was working on some other shit at the
time, so I just did some small changes with the limited time I had for this
project.
- **v6.0 (2025 january - ...)**: The current major version of my website, frontend had a massive rework, API has been
cleaned up and extended to do status checking for the services I host. The `doc` server has been added to the mix
so I can host static documentation. The most important thing about this version is that it adds multi-language support,
so literally everything on the website (including the API and documentation content) is localized for both English
and Turkish, which was something I wanted to do for the longest time ever.
Damn it has been 4 years since I wrote that shit HTML page huh? Time flies...
## Screenshots (from v6.0)
![](assets/6.0_index.png)
![](assets/6.0_services.png)
- **v6.0 (2025 january - ...)**: The current major version of my website,
frontend had a massive rework, API has been cleaned up and extended to do
status checking for the services I host. The `doc` server has been added to
the mix so I can host static documentation. The most important thing about
this version is that it adds multi-language support, so literally everything
on the website (including the API and documentation content) is localized for
both English and Turkish, which was something I wanted to do for the longest
time ever.

View File

@@ -1,4 +1,4 @@
FROM golang:1.23.5
FROM golang:1.24.5
WORKDIR /api

View File

@@ -11,4 +11,7 @@ run:
format:
gofmt -s -w .
.PHONY: test format
clean:
rm -f *.elf
.PHONY: test format clean

View File

@@ -3,107 +3,47 @@ package config
import (
"fmt"
"net/url"
"os"
"github.com/ngn13/ortam"
)
type Type struct {
Options []Option
Count int
Debug bool // should display debug messgaes?
AppUrl *url.URL // frontend application URL for the website
Password string // admin password
Host string // host the server should listen on
IPHeader string // header that should be checked for obtaining the client IP
Interval string // service status check interval
Timeout string // timeout for the service status check
Limit string // if the service responds slower than this limit, it will be marked as "slow"
}
func (c *Type) Find(name string, typ uint8) (*Option, error) {
for i := 0; i < c.Count; i++ {
if c.Options[i].Name != name {
continue
}
if c.Options[i].Type != typ {
return nil, fmt.Errorf("bad option type")
}
return &c.Options[i], nil
func Load() (*Type, error) {
var conf = Type{
Debug: false,
Password: "",
Host: "0.0.0.0:7002",
IPHeader: "X-Real-IP",
Interval: "1h",
Timeout: "15s",
Limit: "5s",
}
return nil, fmt.Errorf("option not found")
}
func (c *Type) Load() (err error) {
var (
env_val string
env_name string
opt *Option
exists bool
)
// default options
c.Options = []Option{
{Name: "debug", Value: "false", Type: OPTION_TYPE_BOOL, Required: true}, // should display debug messgaes?
{Name: "app_url", Value: "http://localhost:7001/", Type: OPTION_TYPE_URL, Required: true}, // frontend application URL for the website
{Name: "password", Value: "", Type: OPTION_TYPE_STR, Required: true}, // admin password
{Name: "host", Value: "0.0.0.0:7002", Type: OPTION_TYPE_STR, Required: true}, // host the server should listen on
{Name: "ip_header", Value: "X-Real-IP", Type: OPTION_TYPE_STR, Required: false}, // header that should be checked for obtaining the client IP
{Name: "interval", Value: "1h", Type: OPTION_TYPE_STR, Required: false}, // service status check interval
{Name: "timeout", Value: "15s", Type: OPTION_TYPE_STR, Required: false}, // timeout for the service status check
{Name: "limit", Value: "5s", Type: OPTION_TYPE_STR, Required: false}, // if the service responds slower than this limit, it will be marked as "slow"
}
c.Count = len(c.Options)
for i := 0; i < c.Count; i++ {
opt = &c.Options[i]
env_name = opt.Env()
if env_val, exists = os.LookupEnv(env_name); exists {
opt.Value = env_val
}
if opt.Value == "" && opt.Required {
return fmt.Errorf("please specify a value for the config option \"%s\" (\"%s\")", opt.Name, env_name)
}
if err = opt.Load(); err != nil {
return fmt.Errorf("failed to load option \"%s\" (\"%s\"): %s", opt.Name, env_name, err.Error())
}
}
return nil
}
func (c *Type) GetStr(name string) string {
var (
opt *Option
err error
)
if opt, err = c.Find(name, OPTION_TYPE_STR); err != nil {
return ""
}
return opt.TypeValue.Str
}
func (c *Type) GetBool(name string) bool {
var (
opt *Option
err error
)
if opt, err = c.Find(name, OPTION_TYPE_BOOL); err != nil {
return false
}
return opt.TypeValue.Bool
}
func (c *Type) GetURL(name string) *url.URL {
var (
opt *Option
err error
)
if opt, err = c.Find(name, OPTION_TYPE_URL); err != nil {
return nil
}
return opt.TypeValue.URL
if err := ortam.Load(&conf, "WEBSITE"); err != nil {
return nil, err
}
if conf.AppUrl == nil {
conf.AppUrl, _ = url.Parse("http://localhost:7001/")
}
if conf.Password == "" {
return nil, fmt.Errorf("password is not specified")
}
if conf.Host == "" {
return nil, fmt.Errorf("host address is not specified")
}
return &conf, nil
}

View File

@@ -1,49 +0,0 @@
package config
import (
"fmt"
"net/url"
"strings"
)
const (
OPTION_TYPE_STR = 0
OPTION_TYPE_BOOL = 1
OPTION_TYPE_URL = 2
)
type Option struct {
Name string
Value string
Required bool
Type uint8
TypeValue struct {
URL *url.URL
Str string
Bool bool
}
}
func (o *Option) Env() string {
return strings.ToUpper(fmt.Sprintf("WEBSITE_%s", o.Name))
}
func (o *Option) Load() (err error) {
err = nil
switch o.Type {
case OPTION_TYPE_STR:
o.TypeValue.Str = o.Value
case OPTION_TYPE_BOOL:
o.TypeValue.Bool = "1" == o.Value || "true" == strings.ToLower(o.Value)
case OPTION_TYPE_URL:
o.TypeValue.URL, err = url.Parse(o.Value)
default:
return fmt.Errorf("invalid option type")
}
return err
}

View File

@@ -1,10 +1,11 @@
module github.com/ngn13/website/api
go 1.21.3
go 1.24.5
require (
github.com/gofiber/fiber/v2 v2.52.6
github.com/mattn/go-sqlite3 v1.14.24
github.com/gofiber/fiber/v2 v2.52.8
github.com/mattn/go-sqlite3 v1.14.28
github.com/ngn13/ortam v0.0.0-20250421004351-8dea81680817
)
require (

View File

@@ -1,17 +1,9 @@
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gofiber/fiber/v2 v2.52.8 h1:xl4jJQ0BV5EJTA2aWiKw/VddRpHrKeZLF0QPUxqn0x4=
github.com/gofiber/fiber/v2 v2.52.8/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@@ -19,12 +11,12 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
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/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/ngn13/ortam v0.0.0-20250421004351-8dea81680817 h1:WkHM4w51N5jCsWcDVcPsXz3zhi/kCfNp/VGh2uPjwsk=
github.com/ngn13/ortam v0.0.0-20250421004351-8dea81680817/go.mod h1:MSJZ4ZstrLvVEvivbp9hhup+iL8rvtpgKcYaF3DSOKk=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
@@ -35,7 +27,5 @@ github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVS
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

View File

@@ -36,18 +36,18 @@ func main() {
app *fiber.App
stat status.Type
conf config.Type
conf *config.Type
db database.Type
err error
)
if err = conf.Load(); err != nil {
if conf, err = config.Load(); err != nil {
util.Fail("failed to load the configuration: %s", err.Error())
return
}
if !conf.GetBool("debug") {
if !conf.Debug {
util.Debg = func(m string, v ...any) {}
}
@@ -56,7 +56,7 @@ func main() {
return
}
if err = stat.Setup(&conf, &db); err != nil {
if err = stat.Setup(conf, &db); err != nil {
util.Fail("failed to setup the status checker: %s", err.Error())
return
}
@@ -75,7 +75,7 @@ func main() {
c.Set("Access-Control-Allow-Methods", "PUT, DELETE, GET") // POST can be sent from HTML forms, so I prefer PUT for API endpoints
c.Locals("status", &stat)
c.Locals("config", &conf)
c.Locals("config", conf)
c.Locals("database", &db)
return c.Next()
@@ -121,9 +121,9 @@ func main() {
}
// start the app
util.Info("starting web server on %s", conf.GetStr("host"))
util.Info("starting web server on %s", conf.Host)
if err = app.Listen(conf.GetStr("host")); err != nil {
if err = app.Listen(conf.Host); err != nil {
util.Fail("failed to start the web server: %s", err.Error())
}

View File

@@ -21,7 +21,7 @@ func admin_log(c *fiber.Ctx, m string) error {
func AuthMiddleware(c *fiber.Ctx) error {
conf := c.Locals("config").(*config.Type)
if c.Get("Authorization") != conf.GetStr("password") {
if c.Get("Authorization") != conf.Password {
return util.ErrAuth(c)
}

View File

@@ -7,8 +7,7 @@ import (
func GET_Index(c *fiber.Ctx) error {
conf := c.Locals("config").(*config.Type)
app := conf.GetURL("app_url")
// redirect to the API documentation
return c.Redirect(app.JoinPath("/doc/api").String())
return c.Redirect(conf.AppUrl.JoinPath("/doc/api").String())
}

View File

@@ -40,7 +40,6 @@ func GET_News(c *fiber.Ctx) error {
db := c.Locals("database").(*database.Type)
conf := c.Locals("config").(*config.Type)
app := conf.GetURL("app_url")
lang := c.Params("lang")
if lang == "" || len(lang) != 2 {
@@ -63,10 +62,10 @@ func GET_News(c *fiber.Ctx) error {
})
if feed, err = util.Render("views/news.xml", fiber.Map{
"app_url": conf.AppUrl,
"updated": time.Now().Format(time.RFC3339),
"entries": entries,
"lang": lang,
"app": app,
}); err != nil {
return util.ErrInternal(c, err)
}

View File

@@ -66,29 +66,24 @@ func (s *Type) loop() {
func (s *Type) Setup(conf *config.Type, db *database.Type) error {
var (
dur time.Duration
iv, to, lm string
err error
dur time.Duration
err error
)
iv = conf.GetStr("interval")
to = conf.GetStr("timeout")
lm = conf.GetStr("limit")
if iv == "" || to == "" || lm == "" {
if conf.Interval == "" || conf.Timeout == "" || conf.Limit == "" {
s.disabled = true
return nil
}
if dur, err = util.GetDuration(iv); err != nil {
if dur, err = util.GetDuration(conf.Interval); err != nil {
return err
}
if s.timeout, err = util.GetDuration(iv); err != nil {
if s.timeout, err = util.GetDuration(conf.Timeout); err != nil {
return err
}
if s.limit, err = util.GetDuration(iv); err != nil {
if s.limit, err = util.GetDuration(conf.Limit); err != nil {
return err
}

View File

@@ -10,10 +10,9 @@ import (
func IP(c *fiber.Ctx) string {
conf := c.Locals("config").(*config.Type)
ip_header := conf.GetStr("ip_header")
if ip_header != "" && c.Get(ip_header) != "" {
return strings.Clone(c.Get(ip_header))
if conf.IPHeader != "" && c.Get(conf.IPHeader) != "" {
return strings.Clone(c.Get(conf.IPHeader))
}
return c.IP()

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">
<title>{{.app.Host}} news</title>
<title>{{.app_url.Host}} news</title>
<updated>{{.updated}}</updated>
<subtitle>News and updates about my projects and self-hosted services</subtitle>
<link href="{{.app.JoinPath "/news"}}"></link>
<link href="{{.app_url.JoinPath "/news"}}"></link>
{{ range .entries }}
<entry>
<title>{{.Title}}</title>

11
app/.gitignore vendored
View File

@@ -1,10 +1,9 @@
.DS_Store
.svelte-kit
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
package
build
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

View File

@@ -1,2 +0,0 @@
engine-strict=true
resolution-mode=highest

View File

@@ -3,7 +3,8 @@
"tabWidth": 2,
"singleQuote": false,
"trailingComma": "es5",
"printWidth": 100,
"printWidth": 80,
"arrowParens": "avoid",
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View File

@@ -1,22 +1,23 @@
# build the application with node
FROM node:23.6.0 AS build
FROM node:24.4.1 AS build
ARG WEBSITE_REPORT_URL
ARG WEBSITE_SOURCE_URL
ARG WEBSITE_APP_URL
ARG WEBSITE_API_URL
ARG WEBSITE_DOC_URL
ARG WEBSITE_API_URL
ARG WEBSITE_API_PATH
ENV WEBSITE_REPORT_URL=$WEBSITE_REPORT_URL
ENV WEBSITE_SOURCE_URL=$WEBSITE_SOURCE_URL
ENV WEBSITE_APP_URL=$WEBSITE_APP_URL
ENV WEBSITE_API_URL=$WEBSITE_API_URL
ENV WEBSITE_DOC_URL=$WEBSITE_DOC_URL
ENV WEBSITE_API_URL=$WEBSITE_API_URL
ENV WEBSITE_API_PATH=$WEBSITE_API_PATH
WORKDIR /app
COPY . /app
RUN npm install && npm run build
RUN npm install
RUN make
# run it with bun (a lot faster)
FROM oven/bun:latest AS main

View File

@@ -1,10 +1,13 @@
all:
npm run build
format:
npm run format
run:
npm run dev
.PHONY: format
format:
npm run format
clean:
rm -rf build
.PHONY: format run clean

2237
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "website",
"version": "6.0",
"version": "6.5",
"private": true,
"scripts": {
"dev": "vite dev",
@@ -10,19 +10,18 @@
"format": "prettier --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^4.0.0",
"@sveltejs/adapter-node": "^5.2.11",
"@sveltejs/kit": "^2.15.1",
"@sveltejs/vite-plugin-svelte": "^4.0.3",
"@sveltejs/adapter-node": "^5.2.13",
"@sveltejs/kit": "^2.25.1",
"@sveltejs/vite-plugin-svelte": "^6.1.0",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.2",
"svelte": "^5.16.0",
"vite": "^5.4.11"
"prettier-plugin-svelte": "^3.4.0",
"svelte": "^5.36.10",
"vite": "^7.0.0"
},
"type": "module",
"dependencies": {
"dompurify": "^3.2.3",
"marked": "^15.0.6",
"svelte-i18n": "^4.0.1"
"js-yaml": "^4.1.0",
"marked": "^16.0.0"
}
}

View File

@@ -0,0 +1,57 @@
<script>
let { title = "", id = "", children } = $props();
</script>
<main {id}>
{#if title === ""}
<div>{@render children()}</div>
{:else}
<h1>{title}</h1>
<div class="padded">{@render children()}</div>
{/if}
</main>
<style>
main {
display: flex;
flex-direction: column;
height: min-content;
}
main h1 {
font-family: var(--monospace);
font-size: var(--size-6);
color: var(--white-1);
background: var(--black-1);
position: relative;
top: 20px;
right: 7px;
width: min-content;
padding-right: 10px;
}
main h1::before {
color: var(--white-3);
content: "#";
margin: 0 10px 0 0;
}
main div {
color: var(--white-2);
font-size: var(--size-3);
line-height: 1.5em;
word-wrap: break-word;
flex: 1;
align-items: center;
}
main .padded {
padding: 25px 20px 18px 20px;
border: solid 1px var(--black-3);
}
</style>

View File

@@ -0,0 +1,94 @@
<script>
import { _ } from "$lib/locale.js";
import { date, date_from_ts } from "$lib/util.js";
import api from "$lib/api.js";
import { onMount } from "svelte";
let data = $state(null);
onMount(async () => {
data = await api.metrics(fetch);
});
</script>
<footer>
<ul>
<li>
<a href={import.meta.env.WEBSITE_SOURCE_URL}>{$_("footer.source")}</a>
</li>
<li>
<a href="/doc/license">{$_("footer.license")}</a>
</li>
<li>
<a href="/doc/privacy">{$_("footer.privacy")}</a>
</li>
</ul>
{#if data === null}
<span>
{$_("footer.render", {
time: date(new Date()),
})}
</span>
{:else}
<span>
{$_("footer.number", {
total: data.total,
since: date_from_ts(data.since),
})}
{#if data.total % 1000 === 0}
<span class="wow">({$_("footer.wow")})</span>
{/if}
</span>
{/if}
</footer>
<style>
footer {
background: var(--glass);
border-top: solid 2px var(--color);
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
padding: 15px 40px;
}
footer ul {
list-style: none;
display: flex;
flex-direction: row;
gap: 5px;
font-size: var(--size-2);
text-align: left;
}
footer ul li a {
text-decoration: none;
font-weight: 200;
}
footer ul li a:hover {
color: var(--color);
}
footer ul li:not(:last-of-type)::after {
content: " / ";
color: var(--white-3);
font-weight: 900;
}
footer span {
color: var(--white-2);
font-size: 15px;
}
footer .wow {
color: var(--color);
}
</style>

View File

@@ -0,0 +1,32 @@
<script>
import { locale } from "$lib/locale.js";
import api from "$lib/api.js";
let { desc, title } = $props();
</script>
<svelte:head>
<title>ngn.tf | {title}</title>
<!-- standart metadata tags -->
<meta name="description" content={desc} />
<meta name="author" content="ngn" />
<meta name="keywords" content="ngn,ngn13,ngn.tf" />
<meta name="color-scheme" content="only dark" />
<meta name="theme-color" content="#000000" />
<!-- open graph meta tags -->
<meta property="og:title" content="ngn.tf | {title}" />
<meta property="og:description" content={desc} />
<!-- atom feed for the service updates -->
<link
rel="alternate"
type="application/atom+xml"
href={api.join("/news/" + $locale.code)}
title="Service news and updates"
/>
</svelte:head>

View File

@@ -0,0 +1,72 @@
<script>
import { _ } from "$lib/locale.js";
let { picture, title } = $props();
</script>
<header>
<div style="--length: {title.length + 1}">
<h1>{title}<span>_</span></h1>
</div>
<img src="/assets/{picture}.png" alt="" />
</header>
<style>
header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: end;
background: var(--transparent);
height: 250px;
}
header div {
padding: 0 40px 10px 40px;
color: var(--color);
font-size: var(--size-6);
font-family:
Consolas,
Monaco,
Lucida Console,
Liberation Mono,
DejaVu Sans Mono,
Bitstream Vera Sans Mono,
Courier New,
monospace;
white-space: nowrap;
}
header div h1 {
text-shadow: var(--text-shadow);
overflow: hidden;
white-space: nowrap;
animation: typing 0.8s steps(var(--length), end);
}
header div span {
content: "_";
display: inline-block;
animation: blink 1.5s steps(2) infinite;
}
header img {
width: 220px;
padding: 0 50px;
}
@media only screen and (max-width: 900px) {
header {
height: 180px;
justify-content: center;
align-items: center;
}
header div {
padding: 30px 20px 20px 20px;
}
header img {
display: none;
}
}
</style>

View File

@@ -0,0 +1,31 @@
<script>
import { localizer, next } from "$lib/locale.js";
/* if we have javascript, no need to send the GET request to the server to
* change the language, we can just switch it on the browser */
function onsubmit(e) {
e.preventDefault();
localizer.switch();
}
</script>
<form method="get" {onsubmit}>
<input type="hidden" name="l" value={$next.code} />
<button type="submit">{$next.icon}</button>
</form>
<style>
button {
cursor: pointer;
background: none;
color: var(--white-1);
font-size: var(--size-4);
outline: none;
border: none;
transition: 0.4s;
}
button:hover {
background: var(--black-1);
}
</style>

View File

@@ -0,0 +1,46 @@
<script>
import Page from "$components/page.svelte";
import Language from "$components/language.svelte";
import { _ } from "$lib/locale.js";
</script>
<nav>
<h3>ngn.tf</h3>
<div>
<Page link="/">{$_("navbar.home")}</Page>
<Page link="/services">{$_("navbar.services")}</Page>
<Page link="/donate">{$_("navbar.donate")}</Page>
<Language />
</div>
</nav>
<style>
nav {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
background: var(--glass);
box-shadow: var(--box-shadow);
border-bottom: solid 2px var(--color);
padding: 15px 40px;
}
nav div {
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: right;
gap: 15px;
}
nav h3 {
color: var(--color);
font-family: var(--monospace);
font-size: var(--size-4);
font-weight: 900;
}
</style>

View File

@@ -0,0 +1,35 @@
<script>
import { click } from "$lib/util.js";
import { page } from "$app/stores";
let { link, children } = $props();
</script>
<a
class={$page.url.pathname === link ? "active" : "inactive"}
data-sveltekit-preload-data
onclick={click}
href={link}
>
{@render children()}
</a>
<style>
a {
font-weight: 600;
font-size: var(--size-3);
color: var(--white-1);
text-decoration: none;
text-decoration-color: var(--color);
}
a:hover {
color: var(--color);
text-decoration: underline;
}
.active {
color: var(--color);
}
</style>

View File

@@ -0,0 +1,141 @@
<script>
import { time_from_ts } from "$lib/util.js";
import { _, locale } from "$lib/locale.js";
let { service } = $props();
</script>
<main>
<div class="info">
<div>
<h1>{service.name}</h1>
<p>{service.desc[$locale.code]}</p>
</div>
<ul>
<li><a href={service.clear}> Clear</a></li>
<span>|</span>
<li><a href={service.onion}>TOR</a></li>
<span>|</span>
<li><a href={service.i2p}> I2P</a></li>
</ul>
</div>
<div class="check">
<p>
{$_("services.last", {
time: time_from_ts(service.check_time),
})}
</p>
{#if service.check_res == 0}
<span style="background: var(--white-2)">
{$_("services.status.down")}
</span>
{:else if service.check_res == 1}
<span style="background: var(--color)">
{$_("services.status.up")}
</span>
{:else if service.check_res == 2}
<span style="background: var(--color); filter: brightness(50%);">
{$_("services.status.slow")}
</span>
{/if}
</div>
</main>
<style>
main {
display: flex;
flex-direction: column;
background: var(--black-2);
border: solid 1px var(--black-3);
text-align: left;
flex: 1;
}
main .info {
display: flex;
flex-direction: row;
align-items: start;
justify-content: space-between;
flex: 1;
color: var(--white-1);
padding: 15px 18px;
}
main .info div h1 {
font-size: var(--size-4);
font-weight: 900;
}
main .info div p {
font-size: var(--size-2);
color: var(--white-2);
font-weight: 100;
}
main .info ul {
display: flex;
flex-direction: row;
gap: 5px;
text-align: right;
list-style: none;
}
main .info li a {
color: var(--color);
text-decoration: none;
font-size: var(--size-2);
font-weight: 100;
}
main .info li a:hover {
text-decoration: underline;
}
main .info li a[href=""] {
color: var(--white-3);
cursor: not-allowed;
}
main .info li a[href=""]:hover {
text-decoration: none;
text-shadow: none;
}
main .info ul span {
color: var(--white-3);
}
main .check {
border-top: solid 1px var(--black-3);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
color: var(--white-1);
}
main .check p {
font-size: var(--size-2);
font-weight: 100;
color: var(--white-2);
padding: 5px 18px;
word-break: keep-all;
white-space: nowrap;
}
main .check span {
font-size: var(--size-4);
font-weight: 1000;
color: var(--black-1);
padding: 5px 18px;
text-transform: uppercase;
}
</style>

14
app/src/hooks.server.js Normal file
View File

@@ -0,0 +1,14 @@
import { redirect } from "@sveltejs/kit";
/** @type {import('@sveltejs/kit').HandleServerError} */
export async function handleError({ error, status }) {
// for unknown routes, just redirect to /
if (status === 404) {
return redirect(303, "/");
}
// for other errors, pass the message which will be used by the error page
return {
message: `${error}`,
};
}

View File

@@ -1,37 +1,59 @@
import { browser } from "$app/environment";
import { urljoin } from "$lib/util.js";
const api_version = "v1";
const api_url = urljoin(import.meta.env.WEBSITE_API_URL, api_version);
class API {
constructor() {
this.version = "v1";
}
function api_urljoin(path = null, query = {}) {
return urljoin(api_url, path, query);
// join given path and queries into an API URL
join(path = null, query = {}) {
let base = "";
if (browser) {
base = urljoin(import.meta.env.WEBSITE_API_PATH, this.version);
} else {
base = urljoin(import.meta.env.WEBSITE_API_URL, this.version);
}
return urljoin(base, path, query);
}
// check given JSON body for errors
check_err(json) {
if (!("error" in json))
throw new Error('API response is missing the "error" key');
if (json["error"] != "")
throw new Error(`API returned an error: ${json["error"]}`);
if (!("result" in json))
throw new Error('API response is missing the "result" key');
}
// make a HTTP GET request to the given URL
async GET(fetch, url) {
const res = await fetch(url);
const json = await res.json();
this.check_err(json);
return json["result"];
}
// get visitor metrics
async metrics(fetch) {
return await this.GET(fetch, this.join("/metrics"));
}
// get service list
async services(fetch) {
return await this.GET(fetch, this.join("/services"));
}
// get projects list
async projects(fetch) {
return await this.GET(fetch, this.join("/projects"));
}
}
function api_check_err(json) {
if (!("error" in json)) throw new Error('API response is missing the "error" key');
if (json["error"] != "") throw new Error(`API returned an error: ${json["error"]}`);
if (!("result" in json)) throw new Error('API response is missing the "result" key');
}
async function api_http_get(fetch, url) {
const res = await fetch(url);
const json = await res.json();
api_check_err(json);
return json["result"];
}
async function api_get_metrics(fetch) {
return await api_http_get(fetch, api_urljoin("/metrics"));
}
async function api_get_services(fetch) {
return await api_http_get(fetch, api_urljoin("/services"));
}
async function api_get_projects(fetch) {
return await api_http_get(fetch, api_urljoin("/projects"));
}
export { api_version, api_urljoin, api_get_metrics, api_get_services, api_get_projects };
const api = new API();
export default api;

View File

@@ -1,50 +0,0 @@
<script>
export let title;
</script>
<main>
<h1 class="title">{title}</h1>
<div>
<slot></slot>
</div>
</main>
<style>
main {
flex: 1;
flex-basis: 30%;
display: flex;
flex-direction: column;
}
main .title {
font-family:
Consolas,
Monaco,
Lucida Console,
Liberation Mono,
DejaVu Sans Mono,
Bitstream Vera Sans Mono,
Courier New,
monospace;
color: var(--white-1);
}
main .title::before {
content: "#";
margin: 0 10px 0 0;
color: var(--white-3);
}
main div {
border-left: solid 1px var(--black-4);
padding: 25px 25px 10px 25px;
font-size: var(--size-4);
color: var(--white-1);
word-wrap: break-word;
align-items: center;
margin-left: 7px;
flex: 1;
}
</style>

View File

@@ -1,28 +1,36 @@
import { urljoin } from "$lib/util.js";
function doc_urljoin(path = null, query = {}) {
return urljoin(import.meta.env.WEBSITE_DOC_URL, path, query);
class Doc {
// join given path and queries with a document server URL
join(path = null, query = {}) {
return urljoin(import.meta.env.WEBSITE_DOC_URL, path, query);
}
// check JSON response and throw an error if it contains one
check_err(json) {
if ("error" in json)
throw new Error(`Doc server returned an error: ${json["error"]}`);
}
// send a HTTP request to the documentation server
async GET(fetch, url) {
const res = await fetch(url);
const json = await res.json();
this.check_err(json);
return json;
}
// get a list of all the documentations
async list(fetch) {
return await this.GET(fetch, this.join("/list"));
}
// get a documentation
async get(fetch, name) {
let url = this.join(`/get/${name}`);
return await this.GET(fetch, url);
}
}
function doc_check_err(json) {
if ("error" in json) throw new Error(`Documentation server returned an error: ${json["error"]}`);
}
async function doc_http_get(fetch, url) {
const res = await fetch(url);
const json = await res.json();
doc_check_err(json);
return json;
}
async function doc_get_list(fetch) {
return await doc_http_get(fetch, doc_urljoin("/list"));
}
async function doc_get(fetch, name) {
let url = doc_urljoin("/get");
url = urljoin(url, name);
return await doc_http_get(fetch, url);
}
export { doc_urljoin, doc_get, doc_get_list };
const doc = new Doc();
export default doc;

View File

@@ -1,62 +0,0 @@
<script>
import Link from "$lib/link.svelte";
import { color } from "$lib/util.js";
import { _ } from "svelte-i18n";
export let error = "";
</script>
<main>
<h1 style="color: var(--{color()})">{$_("error.title")}</h1>
<code>
{#if error === ""}
Unknown error
{:else}
{error}
{/if}
</code>
<Link link={import.meta.env.WEBSITE_REPORT_URL}>
{$_("error.report")}
</Link>
<img src="/profile/sad.png" alt="" />
</main>
<style>
main {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
display: flex;
flex-direction: column;
justify-content: end;
align-items: flex-start;
gap: 10px;
padding: 50px;
font-size: var(--size-4);
background: var(--background);
background-size: 50%;
}
main h1 {
font-size: var(--size-6);
}
main code {
font-size: var(--size-4);
color: var(--white-2);
}
main img {
width: var(--profile-size);
position: absolute;
right: 0;
bottom: 0;
}
</style>

View File

@@ -1,89 +0,0 @@
<script>
import { urljoin, color, date_from_ts } from "$lib/util.js";
import { api_get_metrics } from "$lib/api.js";
import Link from "$lib/link.svelte";
import { onMount } from "svelte";
import { _ } from "svelte-i18n";
let data = {};
onMount(async () => {
data = await api_get_metrics(fetch);
});
</script>
<footer style="border-top: solid 2px var(--{color()});">
<div class="info">
<div class="links">
<span>
<Link link={import.meta.env.WEBSITE_SOURCE_URL} bold={true}>{$_("footer.source")}</Link>
</span>
<span>/</span>
<span>
<Link link={urljoin(import.meta.env.WEBSITE_APP_URL, "doc/license")} bold={true}
>{$_("footer.license")}</Link
>
</span>
<span>/</span>
<span>
<Link link={urljoin(import.meta.env.WEBSITE_APP_URL, "doc/privacy")} bold={true}
>{$_("footer.privacy")}</Link
>
</span>
</div>
<span>
{$_("footer.powered")}
</span>
</div>
<div class="useless">
<span>
{$_("footer.number", {
values: {
total: data.total,
since: date_from_ts(data.since),
},
})}
{#if data.number % 1000 == 0}
<span style="color: var(--{color()})">({$_("footer.wow")})</span>
{/if}
</span>
<span>
{$_("footer.version", { values: { api_version: "v1", frontend_version: pkg.version } })}
</span>
</div>
</footer>
<style>
footer {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
background: var(--black-1);
}
div {
display: flex;
color: var(--white-2);
font-size: var(--size-2);
flex-direction: column;
gap: 5px;
}
.useless {
margin: 25px 50px 25px 0;
text-align: right;
}
.info {
margin: 25px 0 25px 50px;
text-align: left;
}
.info .links {
display: flex;
flex-direction: row;
gap: 5px;
}
</style>

View File

@@ -1,22 +0,0 @@
<script>
import { api_urljoin } from "$lib/api.js";
import { app_url } from "$lib/util.js";
export let desc, title;
</script>
<svelte:head>
<title>[ngn.tf] | {title}</title>
<meta content="[ngn.tf] | {title}" property="og:title" />
<meta content={desc} property="og:description" />
<meta content={app_url()} property="og:url" />
<meta content="#000000" data-react-helmet="true" name="theme-color" />
<link
rel="alternate"
type="application/atom+xml"
href={api_urljoin("/news/en")}
title="Atom Feed"
/>
</svelte:head>

View File

@@ -1,80 +0,0 @@
<script>
import { color } from "$lib/util.js";
import { _ } from "svelte-i18n";
export let picture = "";
export let title = "";
</script>
<header>
<div>
<h1 class="title" style="color: var(--{color()})">{title.toLowerCase()}</h1>
<h1 class="cursor" style="color: var(--{color()})">_</h1>
</div>
<img src="/profile/{picture}.png" alt="" />
</header>
<style>
header {
background: var(--background);
background-size: 50%;
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: end;
}
header div {
display: flex;
flex-direction: row;
align-items: end;
padding: 50px 50px 30px 50px;
font-size: var(--size-6);
font-family:
Consolas,
Monaco,
Lucida Console,
Liberation Mono,
DejaVu Sans Mono,
Bitstream Vera Sans Mono,
Courier New,
monospace;
white-space: nowrap;
justify-content: start;
width: min-content;
}
header div .title {
text-shadow: var(--text-shadow);
overflow: hidden;
width: 0;
animation: typing 1s steps(20, end) forwards;
animation-delay: 0.3s;
}
header div .cursor {
content: "_";
display: inline-block;
animation: blink 1.5s steps(2) infinite;
}
header img {
padding: 50px 50px 0 50px;
width: var(--profile-size);
bottom: 0;
left: 0;
}
@media only screen and (max-width: 900px) {
header {
display: block;
}
header img {
display: none;
}
}
</style>

View File

@@ -1,6 +0,0 @@
<script>
import { color } from "$lib/util.js";
export let icon = "";
</script>
<i style="color: var(--{color()});" class="nf {icon}"></i>

View File

@@ -1,37 +0,0 @@
<script>
import Icon from "$lib/icon.svelte";
import { color } from "$lib/util.js";
const default_color = "white-1";
export let active = false;
export let highlight = true;
export let link = "";
export let icon = "";
let style = "";
if (highlight) style = `text-decoration-color: var(--${color()});`;
if (active) style += `color: var(--${color()});`;
else style += `color: var(--${default_color});`;
</script>
{#if icon != ""}
<Icon {icon} />
{/if}
{#if highlight}
<a data-sveltekit-preload-data {style} href={link}>
<slot></slot>
</a>
{:else}
<a data-sveltekit-preload-data {style} class="no-highlight" href={link}>
<slot></slot>
</a>
{/if}
<style>
.no-highlight:hover {
text-decoration: none;
}
</style>

View File

@@ -1,66 +1,225 @@
import { init, locale, register, waitLocale } from "svelte-i18n";
import { derived, get, writable } from "svelte/store";
import { browser } from "$app/environment";
import { get, writable } from "svelte/store";
import yaml from "js-yaml";
const locale_default = "en";
let locale_index = writable(0);
let locale_list = [];
// defines a single locale
class Locale {
constructor(code, icon) {
// regex for "render"ing the locale
this.ref_regex = /\[[0-9]*:.*?\]/gm;
this.link_regex = /\[[^\]]*]\([^ ]*\)/gm;
this.bold_regex = /\*\*.*?\*\*/gm;
function locale_setup() {
// english
register("en", () => import("../locales/en.json"));
locale_list.push({ code: "en", name: "English", icon: "🇬🇧" });
// turkish
register("tr", () => import("../locales/tr.json"));
locale_list.push({ code: "tr", name: "Turkish", icon: "🇹🇷" });
init({
fallbackLocale: locale_default,
initialLocale: get(locale),
});
}
function locale_from_browser() {
if (browser) return window.navigator.language.slice(0, 2).toLowerCase();
else return locale_default;
}
function locale_select(l = null) {
if (l === null) {
if (browser && null !== (l = localStorage.getItem("locale"))) locale_select(l);
else locale_select(locale_from_browser());
return;
this.code = code; // BCP 47 language tag
this.icon = icon; // icon for the locale
this.all = {}; // all the locales
}
l = l.slice(0, 2);
for (let i = 0; i < locale_list.length; i++) {
if (l !== locale_list[i].code) continue;
if (browser) localStorage.setItem("locale", l);
locale.set(l);
locale_index.set(i);
return;
// load the locale
async load() {
const text = await import(`../locales/${this.code}.yaml?raw`);
this.all = yaml.load(text.default);
}
locale.set(locale_default);
locale_index.set(0);
// renders a given locale using given values and links
render(locale, values, links) {
// get rid of newlines and remove trailing/repeating spaces and stuff
locale = locale.replaceAll("\r", "").replaceAll("\n", " ");
locale = locale.trim();
// find and replace all the values
for (let name in values) {
// values cannot be objects
if (typeof values[name] === "object") {
continue;
}
// "{name}" will be replaced by the values
locale = locale.replaceAll(`{${name}}`, values[name]);
}
// find all the references
const refs = [...locale.matchAll(this.ref_regex)];
// and repalce them
for (let i = 0; i < refs.length; i++) {
let ref = refs[i][0];
let name = ref.replaceAll(/(^\[|\]$)/g, "");
let indx = parseInt(name.charAt(0)) - 1;
name = name.substring(2);
// check the index
if (indx >= links.length) {
continue;
}
// replace the reference with a link
locale = locale.replaceAll(ref, `<a href="${links[indx]}">${name}</a>`);
}
// look for []() patterns, which are used adding links, kinda like markdown
// but this is shittier
links = [...locale.matchAll(this.link_regex)];
// replace the found links
for (let i = 0; i < links.length; i++) {
let link = links[i][0];
let name = link.match(/(?<=\[).*?(?=])/g);
let url = link.match(/(?<=\]\()[^ ]*(?=\))/g);
// if we fail to extract the link name and/or URL, skip this match
if (null === name || null === url) continue;
locale = locale.replaceAll(link, `<a href="${url[0]}">${name[0]}</a>`);
}
// look for double stars which is used for bold text
const bolds = [...locale.matchAll(this.bold_regex)];
// replace bold text with actual bold text
for (let i = 0; i < bolds.length; i++) {
let bold = bolds[i][0];
let text = bold.match(/(?<=\*\*).*?(?=\*\*)/g);
// if we fail to extract the text content, skip this match
if (null === text) continue;
locale = locale.replaceAll(bold, `<b>${text}</b>`);
}
return locale;
}
// resolve the given locale
resolve(key, vars = {}) {
let cur = this.all;
let keys = key.split(".");
for (let i = 0; i < keys.length; i++) {
cur = cur[keys[i]];
if (cur === undefined) {
return cur;
}
}
// locale needs to be a string
if (typeof cur !== "string") {
return undefined;
}
// extract links from the vars
let links = vars.links;
delete vars.links;
return this.render(cur, vars, links);
}
}
async function locale_wait() {
await waitLocale();
// localizer stores and defines all the locales
class Localizer {
constructor() {
// list of supported locales
this.list = [
new Locale("en", "🇬🇧󠁧󠁢󠁥󠁮󠁧󠁿"), // English
new Locale("tr", "🇹🇷"), // Turkish
];
this.current = writable(this.list[0]); // current locale
this.next = writable(this.list[1]); // next locale
this.fallback = this.list[0]; // fallback locale
}
// get the name of a language using the current locale
name(code) {
return get(this.current).name(code);
}
// get the current browser locale tag
browser() {
if (browser) {
window.navigator.language.slice(0, 2).toLowerCase();
} else {
return this.fallback.code;
}
}
/* load all the locales, attempt to set the provided locale as the current
* locale, if the provided locale is not available just use the default */
async setup(code) {
for (let i = 0; i < this.list.length; i++) {
await this.list[i].load();
}
// find the locale index by it's code
let indx = this.list.findIndex(locale => {
return locale.code === code;
});
// check the index to see if we found the locale
if (indx < 0) {
return false;
}
// if we actually found it, set it as the current locale
this.current.set(this.list[indx]);
// set the next locale
if (++indx >= this.list.length) {
this.next.set(this.list[0]);
} else {
this.next.set(this.list[indx]);
}
}
// switch to the next locale
switch() {
// find the next locale's index
let indx = this.list.findIndex(locale => {
return locale === get(this.next);
});
// set next locale as the new current locale
this.current.set(get(this.next));
document.cookie = `locale=${get(this.next).code};`;
// get the next locale based on the index
if (indx === this.list.length - 1) {
this.next.set(this.list[(indx = 0)]);
} else {
this.next.set(this.list[++indx]);
}
}
resolve(key, vars = {}) {
// attempt to resolve the given key
let res = get(this.current).resolve(key, vars);
if (res !== undefined) {
return res;
}
// if we fail to resolve the key, try to resolve it using the fallback
// locale, if that fails too then we are kinda fucked so yeah we just throw
// an error
if (get(this.current) === this.fallback) {
throw new Error(`missing key: ${key}`);
} else {
return this.fallback.resolve(key, vars);
}
}
}
export {
export const localizer = new Localizer(); // global localizer
export const locale = localizer.current; // current locale
export const next = localizer.next; // next locale
// resolve a given locale by it's key
export const _ = derived(
locale,
locale_list,
locale_index,
locale_default,
locale_setup,
locale_wait,
locale_select,
locale_from_browser,
};
() =>
(key, vars = {}) =>
localizer.resolve(key, vars)
);
export default localizer;

View File

@@ -1,42 +0,0 @@
<script>
import NavbarLink from "./navbar_link.svelte";
import NavbarSwitch from "./navbar_switch.svelte";
import { color } from "$lib/util.js";
import { _ } from "svelte-i18n";
</script>
<nav style="border-bottom: solid 2px var(--{color()});">
<h3 style="color: var(--{color()})">[ngn.tf]</h3>
<div>
<NavbarLink link="/">{$_("navbar.home")}</NavbarLink>
<NavbarLink link="/services">{$_("navbar.services")}</NavbarLink>
<NavbarLink link="/donate">{$_("navbar.donate")}</NavbarLink>
<NavbarSwitch />
</div>
</nav>
<style>
nav {
box-shadow: var(--box-shadow-1);
background: var(--black-1);
padding: 20px 30px 20px 20px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
div {
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: right;
gap: 15px;
}
h3 {
font-weight: 900;
font-size: var(--size-4);
}
</style>

View File

@@ -1,27 +0,0 @@
<script>
import { color, click } from "$lib/util.js";
import { page } from "$app/stores";
export let link;
function is_active() {
return $page.url.pathname == link;
}
</script>
<a
style="text-decoration-color: var(--{color()}); {is_active() ? `color: var(--${color()})` : ''}"
data-sveltekit-preload-data
on:click={click}
href={link}
>
<slot></slot>
</a>
<style>
a {
font-weight: 900;
font-size: var(--size-4);
color: var(--white-1);
}
</style>

View File

@@ -1,37 +0,0 @@
<script>
import { locale_list, locale_select, locale_index } from "$lib/locale.js";
let len = locale_list.length;
function get_next(indx) {
let new_indx = 0;
if (indx + 1 >= len) indx = 0;
else new_indx = indx + 1;
return locale_list[new_indx];
}
function next() {
locale_select(get_next($locale_index).code);
}
</script>
<button on:click={next}>
{get_next($locale_index).icon}
</button>
<style>
button {
background: var(--black-2);
color: var(--white-1);
font-size: var(--size-4);
outline: none;
border: none;
transition: 0.4s;
}
button:hover {
background: var(--black-1);
}
</style>

View File

@@ -1,114 +0,0 @@
<script>
import Icon from "$lib/icon.svelte";
import Link from "$lib/link.svelte";
import { color, time_from_ts } from "$lib/util.js";
import { locale, _ } from "svelte-i18n";
export let service = {};
</script>
<main>
<div class="info">
<div class="title">
<h1>{service.name}</h1>
<p>{service.desc[$locale]}</p>
</div>
<div class="links">
<Link highlight={false} link={service.clear}><Icon icon="nf-oct-link" /></Link>
{#if service.onion != ""}
<Link highlight={false} link={service.onion}><Icon icon="nf-linux-tor" /></Link>
{/if}
{#if service.i2p != ""}
<Link highlight={false} link={service.i2p}
><span style="color: var(--{color()})">I2P</span></Link
>
{/if}
</div>
</div>
<div class="check">
<h1>
{$_("services.last", {
values: { time: time_from_ts(service.check_time) },
})}
</h1>
{#if service.check_res == 0}
<span style="background: var(--white-2)">
{$_("services.status.down")}
</span>
{:else if service.check_res == 1}
<span style="background: var(--{color()})">
{$_("services.status.up")}
</span>
{:else if service.check_res == 2}
<span style="background: var(--{color()}); filter: brightness(50%);">
{$_("services.status.slow")}
</span>
{/if}
</div>
</main>
<style>
main {
display: flex;
flex-direction: column;
background: var(--black-3);
border: solid 1px var(--black-4);
text-align: left;
flex: 1;
flex-basis: 40%;
}
main .info {
padding: 25px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
color: var(--white-1);
flex: 1;
}
main .info .title h1 {
font-size: var(--size-5);
margin-bottom: 8px;
font-weight: 900;
}
main .info .title p {
font-size: var(--size-4);
color: var(--white-2);
font-weight: 100;
}
main .info .links {
display: flex;
flex-direction: row;
gap: 10px;
font-size: var(--size-6);
}
main .check {
border-top: solid 1px var(--black-4);
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
color: var(--white-1);
}
main .check h1 {
padding: 15px 25px 15px 25px;
font-size: var(--size-4);
font-weight: 100;
}
main .check span {
padding: 15px 25px 15px 25px;
font-size: var(--size-5);
text-transform: uppercase;
color: var(--black-1);
font-weight: 1000;
}
</style>

View File

@@ -1,71 +1,60 @@
import { locale_from_browser } from "$lib/locale.js";
import { localizer } from "$lib/locale.js";
const colors = [
"yellow",
"cyan",
"green",
"pinkish",
"red",
// "blue" (looks kinda ass)
];
// colors defined in static/css/global.css
const colors = ["yellow", "cyan", "green", "pinkish", "red", "blue"];
let colors_pos = -1;
function color() {
if (colors_pos < 0) colors_pos = Math.floor(Math.random() * colors.length);
else if (colors_pos >= colors.length) colors_pos = 0;
return colors[colors_pos];
// randomly select a color
export function color() {
return colors[Math.floor(Math.random() * colors.length)];
}
function click() {
let audio = new Audio("/click.wav");
// play a click sound
export function click() {
let audio = new Audio("/assets/click.wav");
audio.play();
}
function urljoin(url, path = null, query = {}) {
if (undefined === url || null === url) return;
// join a given path to the URL
export function urljoin(url, path = null) {
if (null === path || path.length === 0) {
return url;
}
let url_len = url.length;
if (url[url.length - 1] != "/") {
url += "/";
}
if (url[url_len - 1] != "/") url += "/";
if (path[0] === "/") {
path = path.slice(1);
}
if (null === path || "" === path) url = new URL(url);
else if (path[0] === "/") url = new URL(path.slice(1), url);
else url = new URL(path, url);
for (let k in query) url.searchParams.append(k, query[k]);
return url.href;
return url + path;
}
function app_url(path = null, query = {}) {
return urljoin(import.meta.env.WEBSITE_APP_URL, path, query);
}
function time_from_ts(ts) {
if (ts === 0 || ts === undefined) return;
let ts_date = new Date(ts * 1000);
let ts_zone = ts_date.toString().match(/([A-Z]+[\+-][0-9]+)/)[1];
return (
new Intl.DateTimeFormat(locale_from_browser(), {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}).format(ts_date) + ` (${ts_zone})`
);
}
function date_from_ts(ts) {
if (ts === 0 || ts === undefined) return;
return new Intl.DateTimeFormat(locale_from_browser(), {
// convert Date() to readable date
export function date(date) {
return new Intl.DateTimeFormat(localizer.browser(), {
month: "2-digit",
year: "2-digit",
day: "2-digit",
}).format(new Date(ts * 1000));
}).format(date);
}
export { color, click, urljoin, app_url, time_from_ts, date_from_ts };
// convert timestamp to readable time
export function time_from_ts(ts) {
const date = new Date(ts * 1000);
const zone = date.toString().match(/([A-Z]+[\+-][0-9]+)/)[1];
return (
new Intl.DateTimeFormat(localizer.browser(), {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}).format(date) + ` (${zone})`
);
}
// convert timestamp to readable date
export function date_from_ts(ts) {
return date(new Date(ts * 1000));
}

View File

@@ -1,83 +0,0 @@
{
"navbar": {
"home": "home",
"services": "services",
"donate": "donate"
},
"home": {
"title": "Hello world!",
"welcome": {
"title": "about",
"desc": "Welcome to my website, I'm ngn",
"whoami": "I'm a security, privacy and freedom advocate high-schooler from Turkey",
"interest": "I'm interested in system security and software development",
"support": "I love and support Free/Libre and Open Source Software (FLOSS)"
},
"work": {
"title": "work",
"desc": "I don't currently have a job, so I spend most of my time...",
"build": "building stupid shit",
"fix": "fixing stupid shit",
"ctf": "solving CTF challenges",
"contribute": "contributing to random projects",
"wiki": "expanding my wiki"
},
"links": {
"title": "contact",
"desc": "Here are some useful links if you want to get in contact with me",
"prefer": "I highly prefer email, you can send encrypted emails using my PGP key"
},
"services": {
"title": "services",
"desc": "A part from working on stupid shit, I host free (as in freedom, and price) services available for all",
"speed": "All of these services are available over a 600 Mbit/s interface",
"security": "All use SSL encrypted connection and they are all privacy-respecting",
"privacy": "Accessible from clearnet, TOR and I2P, no region or network blocks",
"bullshit": "No CDNs, no cloudflare, no CAPTCHA, no analytics, no bullshit",
"link": "See all the services!"
},
"projects": {
"title": "projects",
"desc": "I mostly work on free software projects, here are some of projects that you might find interesting"
}
},
"services": {
"title": "Service Status",
"none": "No services found",
"search": "Search for a service",
"feed": "News and updates",
"last": "Last checked at {time}",
"status": {
"up": "Up",
"down": "Down",
"slow": "Slow"
}
},
"donate": {
"title": "Donate Money!",
"info": "I spend a lot of time and money on different projects and maintaining different services.",
"price": "I mostly pay for hosting and electricity. Which when added up costs around 550₺ per month (~$15 at the time of writing).",
"details": "So even a small donation would be useful. And it would help me keep everything up and running.",
"thanks": "Also huge thanks to all of you who have donated so far!",
"table": {
"platform": "Platform",
"address": "Adress/Link"
}
},
"doc": {
"title": "Documentation"
},
"error": {
"title": "Something went wrong!",
"report": "Report this issue"
},
"footer": {
"source": "Source",
"license": "License",
"privacy": "Privacy",
"powered": "Powered by Svelte, Go, SQLite and donations",
"number": "Visited {total} times since {since}",
"wow": "wow!!",
"version": "Using API version {api_version}, frontend version {frontend_version}"
}
}

98
app/src/locales/en.yaml Normal file
View File

@@ -0,0 +1,98 @@
navbar:
home: home
services: services
donate: donate
home:
title: hello world!
welcome:
title: about
desc: |
Welcome to my personal website, I'm ngn. You might also know me as ngn13.
I am a security, privacy and freedom advocate high-school student from
Turkey. I'm mainly interesed in software development and system security,
so I usually spend my free time writing [Free/Libre
software](https://www.gnu.org/philosophy/free-sw.html), solving
[CTF](https://en.wikipedia.org/wiki/Capture_the_flag_(cybersecurity))s and
learning new things.
thanks: |
I have few personal software projects and I host web based services. If
you would like to check them out, see the sections below. Thank you for
visiting my website and I hope you have a good rest of your day!
projects:
title: projects
desc: |
I develop and work on Free/Libre software. I don't work on massive
projects and I'm not a professional but I do enjoy it. Here are some of my
more popular projects that you might find interesting
services:
title: services
desc: |
I host web based services. Most of these services provide front-end or
alternatives to already existing popular web services. Here are all the
details
speed: All of these services are available over an 1 Gbit interface
security: All use SSL encrypted connection and they respect your privacy and freedom
blocks: |
Accessible from clearnet, [TOR](https://www.torproject.org/) and
[I2P](https://geti2p.net/en/) - no region or network blocks
bullshit: No CDNs, no cloudflare, no CAPTCHA, no analytics, no bullshit
link: See all the services!
contact:
title: contact
desc: |
If you have question or a suggestion, or if you simply want to chat, feel
free to send me an email at **ngn [at] ngn.tf**. You can use my [PGP
key](https://keyoxide.org/F9E70878C2FB389AEC2BA34CA3654DF5AD9F641D) to
send me an encrypted email, which I would highly prefer. My PGP key also
includes identity claims, which you can use to verify my public profiles.
mastodon: |
I'm also on [mastodon](https://defcon.social/@ngn) where I mainly post
Turkish stuff.
services:
title: service status
desc: |
Here you can find a list of all the publicly accessible services I host. I
have periodic status check for clearnet addresses: A request is sent to the
clearnet address the status of the service is reported based on the
response. The latest reported status is displayed next to the time of the
report. If you experience any issues with a service even though the status
is displayed as up, please [contact me](/#contact) to report this issue.
feed: |
I also have an [1:Atom feed] that you can follow to get the latest news and
updates about the changes made to the different services.
last: Last checked at {time}
status:
up: Up
down: Down
slow: Slow
donate:
title: donate!
desc: |
If you like my work or if you are feeling generous, you can financially
support me. I spend around $15 every month to maintain everything, so even a
small donation would be highly appreciated and it would help me keep
everything up and running.
info: |
Currently I only accept donations with [Monero
(XMR)](https://www.getmonero.org/). Here is my monero address
thanks: |
Also I would like to thank everyone who has donated so far. I really
appreciate it!
error:
title: something went wrong!
report: Report this issue
footer:
source: Source
license: License
privacy: Privacy
number: Visited {total} times since {since}
wow: wow!!
render: Rendered on {time} just for you

View File

@@ -1,84 +0,0 @@
{
"navbar": {
"home": "anasayfa",
"services": "servisler",
"donate": "bağış"
},
"home": {
"title": "Merhaba Dünya!",
"welcome": {
"title": "hakkımda",
"desc": "Websiteme hoşgeldiniz, ben ngn",
"whoami": "Türkiye'den, güvenlik, gizlik ve özgürlük savunucusu bir liseliyim",
"interest": "Sistem güvenliği ve yazılım geliştirmek ile ilgileniyorum",
"support": "Özgür/Libre ve Açık Kaynaklı Yazılımı (FLOSS) seviyorum ve destekliyorum"
},
"work": {
"title": "iş",
"desc": "Şuan bir işim yok, o yüzden zamanımın çoğunu şunlarla geçiriyorum:",
"build": "salak şeyler inşa etmek",
"fix": "salak şeyleri düzeltmek",
"ctf": "CTF challenge'ları çözmek",
"contribute": "rastgele projelere katkıda bulunmak",
"wiki": "wikimi genişletmek"
},
"links": {
"title": "iletişim",
"desc": "Eğer benim ile iletişime geçmek istiyorsanız, işte bazı faydalı linkler",
"prefer": "Email'i fazlasıyla tercih ediyorum, PGP anahtarım ile şifreli email'ler gönderebilirsiniz"
},
"services": {
"title": "servisler",
"desc": "Salak şeyler inşa etmenin yanı sıra, herkes için kullanıma açık özgür ve ücretsiz servisler host ediyorum",
"speed": "Tüm servisler 600 Mbit/s ağ arayüzü üzerinden erişilebilir",
"security": "Hepsi SSL şifreli bağlantı kullanıyor ve hepsi gizliğinize önem veriyor",
"privacy": "Accessible from clearnet, TOR and I2P, no region or network blocks",
"privacy": "Açık ağdan, TOR ve I2P'den erişilebilirler, bölge ya da ağ blokları yok",
"bullshit": "CDN yok, cloudflare yok, CAPTCHA yok, analitikler yok, boktan saçmalıklar yok",
"link": "Tüm servisleri incele!"
},
"projects": {
"title": "projeler",
"desc": "Çoğunlukla özgür yazılım projeleri üzerinde çalışıyorum, işte ilginç bulabileceğiniz bazı projelerim"
}
},
"services": {
"title": "Servis Durumu",
"none": "Servis bulunamadı",
"search": "Bir servisi ara",
"feed": "Yenilikler ve güncellemeler",
"last": "Son kontrol zamanı {time}",
"status": {
"up": "Çalışıyor",
"down": "Kapalı",
"slow": "Yavaş"
}
},
"donate": {
"title": "Para Bağışla!",
"info": "Farklı projeler ve farklı servisleri yönetmek için oldukça zaman ve para harcıyorum.",
"price": "Çoğunlukla hosting ve elektrik için ödeme yapıyorum. Bunlar eklendiği zaman aylık 550₺ civarı bir miktar oluyor (yazdığım sırada ~15$).",
"details": "Bu sebepten küçük bir bağış bile oldukça faydalı olacaktır. Ve herşeyi açık ve çalışmakta tutmama yardımcı olacaktır.",
"thanks": "Ayrıca şuana kadar bağışta bulunan herkese çok teşekkür ederim!",
"table": {
"platform": "Platform",
"address": "Adres/Bağlantı"
}
},
"doc": {
"title": "Dökümantasyon"
},
"error": {
"title": "Birşeyler yanlış gitti!",
"report": "Bu sorunu raporlayın"
},
"footer": {
"source": "Kaynak",
"license": "Lisans",
"privacy": "Gizlilik",
"powered": "Svelte, Go, SQLite ve bağışlar tarafından destekleniyor",
"number": "{since} tarihinden beri {total} kez ziyaret edildi",
"wow": "vay be!!",
"version": "Kullan API versiyonu {api_version}, arayüz versiyonu {frontend_version}"
}
}

103
app/src/locales/tr.yaml Normal file
View File

@@ -0,0 +1,103 @@
navbar:
home: anasayfa
services: servisler
donate: bağış
home:
title: merhaba dünya!
welcome:
title: hakkımda
desc: |
Kişisel websiteme hoşgeldiniz, ben ngn. Beni ngn13 olarak da
bilebilirsiniz. Türkiye'den güvenlik, gizlilik ve özgürlük savunucusu bir
liseliyim. Genel olarak yazılım geliştirme ve sistem güvenliği ile
ilgileniyorum, o yüzden boş zamanımın çoğunu
[Özgür yazılım](https://www.gnu.org/philosophy/free-sw.tr.html) yazarak,
[CTF](https://wikipedia.org/wiki/Capture_the_flag_(cybersecurity)) çözerek
ve yeni şeyler öğrenerek harcıyorum.
thanks: |
Birkaç kişisel yazılım projem var ve web tabanlı servisler barındırıyorum.
Eğer bunları incelemek isterseniz, aşağıdaki bölümlere bakın. Websitemi
ziyaret ettiğiniz için teşekkür ederim ve size iyi günler dilerim!
projects:
title: projeler
desc: |
Özgür yazılım geliştiriyorum ve üzerinde çalışıyorum. Devasa projelerde
çalışmıyorum ve profesyonel değilim ama hoşuma gidiyor. İşte biraz daha
popüler olan ve ilginç bulabileceğiniz bazı projelerim
services:
title: servisler
desc: |
Web tabanlı servisler host ediyorum. Bu servislerin çoğu zaten mevcut
popüler web servislerine arayüz ya da alternatifler sağlıyor. İşte tüm
detaylar
speed: Tüm servisler 1 Gbit ağ arayüzü üzerinden erişilebilir
security: |
Hepsi SSL şifreli bağlantı kullanıyor ve mahremiyetinize ve özgürlüğünüze
saygı gösteriyor
blocks: |
ık ağdan, [TOR](https://www.torproject.org/tr/) ve
[I2P](https://geti2p.net/tr/)'den erişilebilirler, bölge ya da ağ blokları
yok
bullshit: CDN yok, cloudflare yok, CAPTCHA yok, analitikler ve diğer saçmalıklar yok
link: Tüm servisleri görüntüle!
contact:
title: contact
desc: |
Eğer bir sorunuz ya da öneriniz varsa, ya da basitçe muhabbet etmek
istiyorsanız, bana **ngn [at] ngn.tf** adresinden bir e-posta göndermekten
çekinmeyin. [PGP anahtarımı
](https://keyoxide.org/F9E70878C2FB389AEC2BA34CA3654DF5AD9F641D) bana
tercihim olan şifreli e-postalar göndermek için kullanabilirsiniz. PGP
anahtarım aynı zamanda identity claim'leri içeriyor, bunları internete
ık profillerimi doğrulamak için kullanabilirisniz.
mastodon: |
Aynı zamanda [mastodon'dayım](https://defcon.social/@ngn),
çoğunlukla Türkçe olan paylaşımlar yapıyorum.
services:
title: servis durumu
desc: |
Burada barındırdığım internete açık tüm servislerin bir listesini
bulabilirsiniz. Açık ağ adresleri için periyodik bir durum kontrolü mevcut:
ık ağ adresine bir istek gönderiliyor ve servisin durumu verilen cevaba
göre raporlanıyor. En son raporlanan durum raporun zamanının yanında
görüntüleniyor. Bir servisin durumu çalışıyor olarak görünmesine rağmen
servisle alakalı sorunlar yaşıyorsanız, lütfen bu sorunları raporlamak için
[bana ulaşın](/#contact).
feed: |
Farklı servislere yapılan değişiklikler hakkında güncellemeleri ve
haberleri görmek adına takip edebileceğiniz bir [1:Atom beslemesi] de
mevcut.
last: Son kontrol zamanı {time}
status:
up: Çalışıyor
down: Kapalı
slow: Yavaş
donate:
title: bağış yap!
desc: |
Eğer emeğimi seviyorsanız ya da kendinizi cömert hissediyorsanız, beni
finansal olarak destekleyebilirsiniz. Herşeyi yönetmek adına her yaklaşık
$15 harcıyorum, o yüzden küçük bir bağış bile oldukça faydalı olacaktır ve
herşeyi ayakta tutmama yardım eder.
info: |
Şuan sadece [Monero (XMR)](https://www.getmonero.org/tr/) ile bağış kabul
ediyorum. İşte monero adresim
thanks: Ayrıca şuana kadar bağış yapan herkese çok teşekkür ederim.
error:
title: birşeyler yanlış gitti!
report: Bu hatayı raporla
footer:
source: Kaynak
license: Lisans
privacy: Gizlilik
number: "{since} tarihinden beri {total} kez ziyaret edildi"
wow: vay be!
render: Sizin için {time} tarihinde işlendi

View File

@@ -1,8 +1,51 @@
<script>
import { onMount } from "svelte";
import { goto } from "$app/navigation";
onMount(() => {
goto("/");
});
import { page } from "$app/state";
import { _ } from "$lib/locale.js";
</script>
<main>
<h1>{$_("error.title")}</h1>
<code>{page.error.message}</code>
<a href={import.meta.env.WEBSITE_REPORT_URL}>{$_("error.report")}</a>
<img src="/assets/sad.png" alt="" />
</main>
<style>
main {
background: var(--transparent);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
display: flex;
flex-direction: column;
justify-content: end;
align-items: flex-start;
gap: 10px;
padding: 50px;
font-size: var(--size-4);
}
main h1 {
font-size: var(--size-6);
text-shadow: var(--text-shadow);
color: var(--color);
}
main code {
font-size: var(--size-4);
color: var(--white-2);
}
main img {
width: 250px;
position: absolute;
right: 50px;
bottom: 0;
}
</style>

View File

@@ -1,6 +1,6 @@
import { locale_setup, locale_wait } from "$lib/locale.js";
import localizer from "$lib/locale.js";
export async function load() {
locale_setup();
await locale_wait();
export async function load({ data }) {
await localizer.setup(data.locale);
return data;
}

View File

@@ -0,0 +1,36 @@
import { redirect } from "@sveltejs/kit";
import { color } from "../lib/util.js";
// set the locale cookie
function set_locale(cookies, locale) {
cookies.set("locale", locale, { path: "/", secure: false, httpOnly: false });
}
export async function load({ cookies, request, url }) {
// if the locale param is specified, use the specified locale
let locale = url.searchParams.get("l");
if (locale) {
set_locale(cookies, locale);
return redirect(307, url.pathname);
}
// attempt get the preferred locale from cookies
locale = cookies.get("locale");
/* if that doesn't work, try the accept-language header, and update the
* cookies respectively */
if (!locale) {
locale = request.headers.get("accept-language")?.split(",")[0];
set_locale(cookies, locale);
}
return {
// the locale we detected
locale: locale,
/* color is randomly picked on the server and passed via data to the client
* so both the client and the server will have the same color */
color: color(),
};
}

View File

@@ -1,36 +1,30 @@
<script>
import Navbar from "$lib/navbar.svelte";
import Footer from "$lib/footer.svelte";
import { browser } from "$app/environment";
import { locale_select } from "$lib/locale.js";
import { onMount } from "svelte";
import { color } from "$lib/util.js";
import api from "$lib/api.js";
let { children } = $props();
let { data, children } = $props();
onMount(() => {
locale_select();
});
if (browser) {
// set the current app and the API version
window._version = {
app: pkg.version,
api: api.version,
};
}
</script>
<main>
<Navbar />
<div class="content">
{@render children()}
</div>
<Footer />
<main style="--color: var(--{data.color})">
{@render children()}
</main>
<style>
@import "/global.css";
@import "/css/global.css";
main {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.content {
background: var(--black-1);
flex-grow: 1;
}
</style>

View File

@@ -1,15 +1,9 @@
import { api_get_projects } from "$lib/api.js";
import api from "$lib/api.js";
export async function load({ fetch }) {
try {
let projects = await api_get_projects(fetch);
return {
projects: null === projects ? [] : projects,
error: "",
};
} catch (err) {
return {
error: err.toString(),
};
}
let projects = await api.projects(fetch);
return {
projects: null === projects ? [] : projects,
};
}

View File

@@ -1,121 +1,94 @@
<script>
import Header from "$lib/header.svelte";
import Error from "$lib/error.svelte";
import Head from "$lib/head.svelte";
import Card from "$lib/card.svelte";
import Link from "$lib/link.svelte";
import Navbar from "$components/navbar.svelte";
import Footer from "$components/footer.svelte";
import Header from "$components/header.svelte";
import Head from "$components/head.svelte";
import Card from "$components/card.svelte";
import { _, locale } from "svelte-i18n";
import { color } from "$lib/util.js";
import { locale, _ } from "$lib/locale.js";
let { data } = $props();
// return list of projects that have decriptions for the given locale
function projects() {
return data.projects.filter(p => {
return (
p.desc[$locale.code] !== "" &&
p.desc[$locale.code] !== null &&
p.desc[$locale.code] !== undefined
);
});
}
</script>
<Head title="home" desc="home page of my personal website" />
<Navbar />
<Header picture="tired" title={$_("home.title")} />
<main>
<!-- welcome -->
<Card>
<p>{@html $_("home.welcome.desc")}</p>
<br />
<p>{$_("home.welcome.thanks")}</p>
</Card>
{#if data.error.length !== 0}
<Error error={data.error} />
{:else}
<main>
<Card title={$_("home.welcome.title")}>
<span> 👋 {$_("home.welcome.desc")}</span>
<ul>
<li>🇹🇷 {$_("home.welcome.whoami")}</li>
<li>🖥️ {$_("home.welcome.interest")}</li>
<li>❤️ {$_("home.welcome.support")}</li>
</ul>
</Card>
<Card title={$_("home.work.title")}>
<span>{$_("home.work.desc")}</span>
<ul>
<li>⌨️ {$_("home.work.build")}</li>
<li>🤦 {$_("home.work.fix")}</li>
<li>🚩 {$_("home.work.ctf")}</li>
<li>👥 {$_("home.work.contribute")}</li>
<li>📑 {$_("home.work.wiki")}</li>
</ul>
</Card>
<Card title={$_("home.links.title")}>
<span>{$_("home.links.desc")}:</span>
<ul>
<li>
<Link
icon="nf-fa-key"
link="https://keyoxide.org/F9E70878C2FB389AEC2BA34CA3654DF5AD9F641D"
>
PGP
</Link>
<!-- projects -->
<Card title={$_("home.projects.title")} id="projects">
<p>{$_("home.projects.desc")}:</p>
<br />
<ul>
{#each projects() as project}
<li class="project">
<a href={project.url}>{project.name}</a>: {project.desc[$locale.code]}
</li>
<li>
<Link icon="nf-md-email" link="mailto:ngn@ngn.tf">Email</Link>
</li>
<li>
<Link icon="nf-md-mastodon" link="https://defcon.social/@ngn">Mastodon</Link>
</li>
</ul>
<span>
{$_("home.links.prefer")}
</span>
</Card>
<Card title={$_("home.services.title")}>
<span>
{$_("home.services.desc")}:
</span>
<ul>
<li>
<i style="color: var(--{color()});" class="nf nf-md-speedometer_slow"></i>
{$_("home.services.speed")}
</li>
<li>
<i style="color: var(--{color()});" class="nf nf-fa-lock"></i>
{$_("home.services.security")}
</li>
<li>
<i style="color: var(--{color()});" class="nf nf-fa-network_wired"></i>
{$_("home.services.privacy")}
</li>
<li>
<i style="color: var(--{color()});" class="nf nf-md-eye_off"></i>
{$_("home.services.bullshit")}
</li>
</ul>
<Link link="/services">{$_("home.services.link")}</Link>
</Card>
<Card title={$_("home.projects.title")}>
<span>
{$_("home.projects.desc")}:
</span>
{#if data.error.length === 0}
<ul>
{#each data.projects.filter((p) => {
return p.desc[$locale] !== "" && p.desc[$locale] !== null && p.desc[$locale] !== undefined;
}) as project}
<li>
<Link active={true} link={project.url}>{project.name}</Link>:
{project.desc[$locale]}
</li>
{/each}
</ul>
{/if}
</Card>
</main>
{/if}
{/each}
</ul>
</Card>
<!-- services -->
<Card title={$_("home.services.title")} id="services">
<p>{$_("home.services.desc")}:</p>
<br />
<ul>
<li>{@html $_("home.services.speed")}</li>
<li>{@html $_("home.services.security")}</li>
<li>{@html $_("home.services.blocks")}</li>
<li>{@html $_("home.services.bullshit")}</li>
</ul>
<br />
<a class="services" href="/services" data-sveltekit-preload-data>
{$_("home.services.link")}
</a>
</Card>
<!-- contact -->
<Card title={$_("home.contact.title")} id="contact">
<p>{@html $_("home.contact.desc")}</p>
<br />
<p>{@html $_("home.contact.mastodon")}</p>
</Card>
</main>
<Footer />
<style>
main {
background: var(--black-1);
flex: 1;
display: flex;
flex-wrap: wrap;
justify-content: center;
flex-direction: column;
align-items: stretch;
padding: 50px;
gap: 28px;
padding: 40px;
}
@media only screen and (max-width: 900px) {
main {
flex-direction: column;
}
.services {
color: var(--color);
text-decoration-color: var(--color);
}
.project a {
color: var(--color);
text-decoration-color: var(--color);
}
</style>

View File

@@ -1,13 +1,7 @@
import { doc_get_list, doc_get } from "$lib/doc";
import doc from "$lib/doc";
export async function load({ fetch, params }) {
try {
return {
docs: await doc_get_list(fetch),
doc: await doc_get(fetch, params.name),
error: "",
};
} catch (err) {
return { error: err.toString() };
}
return {
doc: await doc.get(fetch, params.name),
};
}

View File

@@ -1,123 +1,41 @@
<script>
import Header from "$lib/header.svelte";
import Error from "$lib/error.svelte";
import Head from "$lib/head.svelte";
import Navbar from "$components/navbar.svelte";
import Footer from "$components/footer.svelte";
import Header from "$components/header.svelte";
import Head from "$components/head.svelte";
import { locale, _ } from "$lib/locale.js";
import { locale, _ } from "svelte-i18n";
import { goto } from "$app/navigation";
import { color } from "$lib/util.js";
import DOMPurify from "dompurify";
import { onMount } from "svelte";
import { marked } from "marked";
import { onMount } from "svelte";
let { data } = $props();
marked.use({ breaks: true });
marked.use();
onMount(async () => {
for (let key in data.doc)
onMount(() => {
for (let key in data.doc) {
data.doc[key]["content"] = DOMPurify.sanitize(data.doc[key]["content"]);
if (undefined !== data.error && data.error.includes("not found")) goto("/");
}
});
</script>
<Head title="documentation" desc="website and API documentation" />
<Header picture="reader" title={$_("doc.title")} />
{#if data.error.length !== 0}
{#if !data.error.includes("not found")}
<Error error={data.error} />
{/if}
{:else}
<main>
{#if data.doc !== undefined}
<div class="markdown-body" style="--link-color: var(--{color()})">
{@html marked.parse(data.doc[$locale].content)}
</div>
<div class="docs">
{#each data.docs[$locale] as doc}
{#if doc.title == data.doc[$locale].title}
<a href="/doc/{doc.name}" style="border-color: var(--{color()})">
<h1>{doc.title}</h1>
<h3>{doc.desc}</h3>
</a>
{:else}
<a href="/doc/{doc.name}" style="border-color: var(--white-3)">
<h1>{doc.title}</h1>
<h3>{doc.desc}</h3>
</a>
{/if}
{/each}
</div>
{/if}
</main>
{/if}
<Navbar />
<Header picture="reader" title={data.doc[$locale.code].title} />
<main>
<div class="markdown-body">
{@html marked.parse(data.doc[$locale.code].content)}
</div>
</main>
<Footer />
<style>
@import "/markdown.css";
@import "/css/markdown.css";
main {
padding: 50px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: start;
background: var(--black-1);
padding: 40px;
gap: 30px;
}
main .docs {
display: flex;
flex-direction: column;
align-items: end;
gap: 6px;
}
main .docs a {
display: flex;
flex-direction: column;
background: var(--black-3);
text-decoration: none;
box-sizing: border-box;
border-right-style: solid;
padding: 15px;
width: 100%;
gap: 4px;
}
main .docs a:hover {
box-shadow: var(--box-shadow-2);
}
main .docs a h1 {
font-size: var(--size-3);
color: var(--white-1);
font-weight: 900;
}
main .docs a h3 {
font-size: var(--size-2);
color: var(--white-3);
font-weight: 100;
text-decoration: none;
}
main .markdown-body :global(a) {
color: var(--link-color);
}
@media only screen and (max-width: 900px) {
main {
flex-direction: column-reverse;
}
main .docs {
width: 100%;
}
main .docs a {
border-right-style: none;
border-left-style: solid;
width: 100%;
}
}
</style>

View File

@@ -1,93 +1,43 @@
<script>
import Header from "$lib/header.svelte";
import Head from "$lib/head.svelte";
import Icon from "$lib/icon.svelte";
import Navbar from "$components/navbar.svelte";
import Footer from "$components/footer.svelte";
import Header from "$components/header.svelte";
import Head from "$components/head.svelte";
import Card from "$components/card.svelte";
import { color } from "$lib/util.js";
import { _ } from "svelte-i18n";
import { _ } from "$lib/locale.js";
</script>
<Head title="donate" desc="give me all of your life savings" />
<Navbar />
<Header picture="money" title={$_("donate.title")} />
<main>
<span> </span>
<span>
{$_("donate.info")}
{$_("donate.price")}
</span>
<br />
<br />
<span>
{$_("donate.details")}
</span>
<table>
<thead>
<tr>
<th style="color: var(--{color()})">{$_("donate.table.platform")}</th>
<th style="color: var(--{color()})">{$_("donate.table.address")}</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<Icon icon="nf-fa-monero" />
Monero (XMR)
</td>
<td>
<code>
46q7G7u7cmASvJm7AmrhmNg6ctS77mYMmDAy1QxpDn5w57xV3GUY5za4ZPZHAjqaXdfS5YRWm4AVj5UArLDA1retRkJp47F
</code>
</td>
</tr>
</tbody>
</table>
<span>
{$_("donate.thanks")}
</span>
<Card>
<p>{@html $_("donate.desc")}</p>
<br />
<p>{@html $_("donate.info")}:</p>
<div>
<code>
46q7G7u7cmASvJm7AmrhmNg6ctS77mYMmDAy1QxpDn5w57xV3GUY5za4ZPZHAjqaXdfS5YRWm4AVj5UArLDA1retRkJp47F
</code>
</div>
<p>{$_("donate.thanks")}</p>
</Card>
</main>
<Footer />
<style>
main {
padding: 50px;
background: var(--black-1);
flex: 1;
padding: 40px;
}
main span {
font-size: var(--size-4);
color: var(--white-1);
}
table {
box-shadow: var(--box-shadow);
background: var(--black-3);
border-collapse: collapse;
font-size: var(--size-3);
margin: 30px 0 30px 0;
width: 100%;
}
tr,
th,
td {
color: white;
background: var(--dark-two);
text-align: left;
}
td,
th {
font-size: var(--size-4);
border: solid 1px var(--black-4);
padding: 16px;
}
th {
font-weight: 1000;
}
td {
color: var(--white-2);
font-weight: 400;
main div {
background: var(--black-2);
border: solid 1px var(--black-3);
padding: 6px;
margin: 15px 0;
}
code {

View File

@@ -1,15 +1,9 @@
import { api_get_services } from "$lib/api.js";
import api from "$lib/api.js";
export async function load({ fetch }) {
try {
let services = await api_get_services(fetch);
return {
services: null === services ? [] : services,
error: "",
};
} catch (err) {
return {
error: err.toString(),
};
}
let services = await api.services(fetch);
return {
services: null === services ? [] : services,
};
}

View File

@@ -1,92 +1,68 @@
<script>
import Service from "$lib/service.svelte";
import Header from "$lib/header.svelte";
import Error from "$lib/error.svelte";
import Link from "$lib/link.svelte";
import Head from "$lib/head.svelte";
import { api_urljoin } from "$lib/api.js";
import { locale, _ } from "svelte-i18n";
import Navbar from "$components/navbar.svelte";
import Footer from "$components/footer.svelte";
import Header from "$components/header.svelte";
import Service from "$components/service.svelte";
import Head from "$components/head.svelte";
import Card from "$components/card.svelte";
import { locale, _ } from "$lib/locale.js";
import api from "$lib/api.js";
let { data } = $props();
let services = $state(data.services);
function change(input) {
let value = input.target.value.toLowerCase();
services = [];
if (value === "") {
services = data.services;
return;
}
data.services.forEach((s) => {
if (s.name.toLowerCase().includes(value)) services.push(s);
else if (s.desc[$locale].toLowerCase().includes(value)) services.push(s);
});
}
function get_services() {
return services.filter((s) => {
return s.desc[$locale] !== "" && s.desc[$locale] !== null && s.desc[$locale] !== undefined;
});
}
// filtered list of services that have descriptions for the current locale
let services = $derived(
data.services.filter(
s =>
s.desc[$locale.code] !== "" &&
s.desc[$locale.code] !== null &&
s.desc[$locale.code] !== undefined
)
);
</script>
<Head title="services" desc="my self-hosted services and projects" />
<Navbar />
<Header picture="cool" title={$_("services.title")} />
{#if data.error.length !== 0}
<Error error={data.error} />
{:else}
<main>
<div class="title">
<input oninput={change} type="text" placeholder={$_("services.search")} />
<div>
<Link icon="nf-fa-feed" link={api_urljoin("/news/" + $locale)}>{$_("services.feed")}</Link>
</div>
</div>
<div class="services">
{#if get_services().length == 0}
<h3 class="none">{$_("services.none")}</h3>
{:else}
{#each get_services() as service}
<Service {service} />
{/each}
{/if}
</div>
</main>
{/if}
<main>
<Card>
<p>{@html $_("services.desc")}</p>
<br />
<p>
{@html $_("services.feed", {
links: [api.join("/news/" + $locale.code)],
})}
</p>
</Card>
<div class="services">
{#if services.length === 0}
<h3>{$_("services.none")}</h3>
{:else}
{#each services as service}
<Service {service} />
{/each}
{/if}
</div>
</main>
<Footer />
<style>
main {
padding: 50px;
text-align: right;
}
main .title {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
main .none {
color: var(--white-3);
background: var(--black-1);
flex: 1;
padding: 40px;
}
main .services {
margin-top: 20px;
border-top: solid 1px var(--white-3);
padding-top: 20px;
margin-top: 15px;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: stretch;
gap: 28px;
}
@media only screen and (max-width: 1200px) {
main .services {
flex-direction: column;
}
color: var(--white-3);
gap: 15px;
}
</style>

View File

@@ -1,21 +0,0 @@
@keyframes blink {
0% {
opacity: 0;
}
}
@keyframes cursor {
to {
border-color: transparent;
}
}
@keyframes typing {
from {
width: 0%;
}
to {
width: 100%;
}
}

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
app/static/assets/cool.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

BIN
app/static/assets/sad.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
app/static/assets/tired.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

126
app/static/css/global.css Normal file
View File

@@ -0,0 +1,126 @@
/* global CSS file, imported in every page, defines commonly used color, effects
* etc. also defines styles for commonly used elements */
:root {
/* randomly selected colors */
--yellow: #d3b910;
--cyan: #0dd2e8;
--green: #06e00a;
--pinkish: #d506e0;
--red: #e8180d;
--blue: #3768fc;
/* white tones */
--white-1: #ffffff;
--white-2: #bfbfbf;
--white-3: #5f5f5f;
/* black tones */
--black-1: #000000;
--black-2: #111;
--black-3: #3a3b3c;
/* different sizes */
--size-1: 8px;
--size-2: 16px;
--size-3: 18px;
--size-4: 20px;
--size-5: 24px;
--size-6: 30px;
/* shadows */
--box-shadow: rgb(38, 57, 77) 0px 20px 30px -10px;
--text-shadow: 0 1px 5px rgba(255, 255, 255, 0.15);
/* backgrounds */
--transparent: linear-gradient(rgba(11, 11, 11, 0.808), rgba(1, 1, 1, 0.96));
--glass: rgba(1, 1, 1, 0.93);
/* fonts */
--monospace:
ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono,
monospace;
--sans-serif:
-apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica,
Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
--emoji: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
/* cursor blink animation */
@keyframes blink {
0% {
opacity: 0;
}
}
/* typing animation */
@keyframes typing {
from {
width: 0;
}
to {
width: 100%;
}
}
* {
padding: 0;
margin: 0;
}
html {
box-sizing: border-box;
}
body {
font-family: var(--sans-serif);
background-image: url("/assets/banner.png");
overflow-x: hidden;
height: 100vh;
width: 100vw;
line-height: 1.5;
font-size: var(--size-3);
}
::selection {
background: rgba(100, 100, 100, 0.5);
}
::-webkit-scrollbar {
border-radius: 5px;
width: 1px;
}
::-webkit-scrollbar-track {
border-radius: 10px;
background: var(--black-1);
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: var(--black-2);
}
a {
color: var(--white-2);
font-weight: 600;
cursor: pointer;
text-decoration: underline;
text-decoration-color: var(--white-2);
}
a:hover {
color: var(--white-1);
text-shadow: var(--text-shadow);
}
ul {
list-style: inside;
}
br {
display: block;
margin: 5px 0;
}

View File

@@ -1,15 +1,21 @@
/* modified version of github-markdown-css, the dark version
* licensed under MIT, see: https://github.com/sindresorhus/github-markdown-css
*/
.markdown-body {
color-scheme: dark;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
margin: 0;
color: #c9d1d9;
background-color: #000;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial,
sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
font-size: 16px;
line-height: 1.5;
word-wrap: break-word;
color: var(--white-2);
}
.markdown-body br {
margin: 0;
}
.markdown-body .octicon {
@@ -60,10 +66,6 @@
display: none !important;
}
.markdown-body a {
text-decoration: none;
}
.markdown-body abbr[title] {
border-bottom: none;
text-decoration: underline dotted;
@@ -81,9 +83,9 @@
.markdown-body h1 {
margin: 0.67em 0;
font-weight: 600;
padding-bottom: 0.3em;
padding-bottom: 5px;
font-size: 2em;
border-bottom: 1px solid #21262d;
border-bottom: 1px solid var(--black-3);
}
.markdown-body mark {
@@ -286,17 +288,18 @@
.markdown-body h4,
.markdown-body h5,
.markdown-body h6 {
margin-top: 24px;
margin-bottom: 16px;
color: var(--white-1);
margin-top: 30px;
margin-bottom: 10px;
font-weight: 600;
line-height: 1.25;
}
.markdown-body h2 {
font-weight: 600;
padding-bottom: 0.3em;
padding-bottom: 5px;
font-size: 1.5em;
border-bottom: 1px solid #21262d;
border-bottom: 1px solid var(--black-3);
}
.markdown-body h3 {
@@ -358,28 +361,14 @@
.markdown-body tt,
.markdown-body code,
.markdown-body samp {
font-family:
ui-monospace,
SFMono-Regular,
SF Mono,
Menlo,
Consolas,
Liberation Mono,
monospace;
font-family: var(--monospace);
font-size: 12px;
}
.markdown-body pre {
margin-top: 0;
margin-bottom: 0;
font-family:
ui-monospace,
SFMono-Regular,
SF Mono,
Menlo,
Consolas,
Liberation Mono,
monospace;
font-family: var(--monospace);
font-size: 12px;
word-wrap: normal;
}
@@ -720,7 +709,7 @@
margin: 0;
font-size: 85%;
white-space: break-spaces;
background: var(--black-3);
background: var(--black-2);
border-radius: 6px;
}
@@ -761,11 +750,13 @@
.markdown-body .highlight pre,
.markdown-body pre {
padding: 16px;
padding: 10px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: var(--black-3);
background-color: var(--black-2);
border: solid 1px var(--black-3);
}
.markdown-body pre code,
@@ -979,7 +970,7 @@
.markdown-body g-emoji {
display: inline-block;
min-width: 1ch;
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-family: var(--emoji);
font-size: 1em;
font-style: normal !important;
font-weight: 400;
@@ -1026,7 +1017,9 @@
}
.markdown-body .contains-task-list:hover .task-list-item-convert-container,
.markdown-body .contains-task-list:focus-within .task-list-item-convert-container {
.markdown-body
.contains-task-list:focus-within
.task-list-item-convert-container {
display: block;
width: auto;
height: 24px;

View File

@@ -1,10 +0,0 @@
/* im using nerd fonts btw */
@import "https://www.nerdfonts.com/assets/css/webfont.css";
@font-face {
font-family: "Ubuntu";
font-style: normal;
font-weight: 300;
font-display: swap;
src: url("/ubuntu.woff2") format("woff2");
}

View File

@@ -1,107 +0,0 @@
@import "./animations.css";
@import "./font.css";
:root {
--yellow: #d3b910;
--cyan: #0dd2e8;
--green: #06e00a;
--pinkish: #d506e0;
--red: #e8180d;
--blue: #2036f9;
--white-1: #ffffff;
--white-2: #bfbfbf;
--white-3: #5f5f5f;
--white-4: #0f0f0f;
--black-1: #000000;
--black-2: #050505;
--black-3: #111111;
--black-4: #3a3b3c;
--size-1: 8px;
--size-2: 16px;
--size-3: 18px;
--size-4: 20px;
--size-5: 24px;
--size-6: 30px;
--box-shadow-1: rgba(20, 20, 20, 0.19) 0px 10px 20px, rgba(30, 30, 30, 0.23) 0px 6px 6px;
--box-shadow-2: rgba(0, 0, 0, 0.35) 0px 30px 60px -12px inset,
rgba(20, 20, 20, 0.3) 0px 18px 36px -18px inset;
--text-shadow: 3px 2px 8px rgba(50, 50, 50, 0.8);
--background: linear-gradient(rgba(11, 11, 11, 0.808), rgba(1, 1, 1, 0.96)), url("/banner.png");
--profile-size: 220px;
}
* {
padding: 0;
margin: 0;
}
html {
box-sizing: border-box;
}
body {
background: var(--black-1);
font-family: "Ubuntu", sans-serif;
overflow-x: hidden;
height: 100vh;
width: 100vw;
}
::selection {
background: rgba(100, 100, 100, 0.5);
}
::-webkit-scrollbar {
border-radius: 5px;
width: 1px;
}
::-webkit-scrollbar-track {
border-radius: 10px;
background: var(--black-1);
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: var(--black-2);
}
a {
font-weight: 900;
color: var(--white-1);
text-decoration: none;
cursor: pointer;
}
a:hover {
text-decoration: underline;
text-shadow: var(--text-shadow);
}
i .nf {
font-weight: 900;
}
ul {
list-style: inside;
margin: 12px 0 12px 0;
}
li + li {
margin-top: 10px;
}
input {
background: var(--black-3);
border: none;
outline: none;
font-size: var(--size-4);
padding: 10px;
border: solid 1px var(--black-4);
color: var(--white-1);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

3
app/static/robots.txt Normal file
View File

@@ -0,0 +1,3 @@
User-Agent: *
Disallow: /doc/
Disallow: /api/

Binary file not shown.

View File

@@ -1,14 +1,13 @@
//import adapter from '@sveltejs/adapter-auto';
import adapter from "@sveltejs/adapter-node";
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter(),
alias: { $components: "src/components" },
},
onwarn: (warning, handler) => {
if (warning.code === "a11y-click-events-have-key-events") return;
handler(warning);
compilerOptions: {
runes: true,
},
};

View File

@@ -3,21 +3,38 @@ import { defineConfig } from "vite";
import { fileURLToPath } from "url";
import { readFileSync } from "fs";
function env_from(prefix, object) {
for (const [key, value] of Object.entries(object)) {
let type = typeof value;
let name = prefix + "_" + key.toUpperCase();
switch (type) {
case "object":
env_from(name, value);
break;
case "string":
if (process.env[name] === undefined) process.env[name] = value;
break;
}
}
}
const default_env = {
REPORT_URL: "https://github.com/ngn13/website/issues",
SOURCE_URL: "https://github.com/ngn13/website",
APP_URL: "http://localhost:7001",
API_URL: "http://localhost:7002",
DOC_URL: "http://localhost:7003",
source_url: "https://git.ngn.tf/ngn/website",
report_url: "mailto:ngn@ngn.tf",
doc_url: "http://localhost:7003",
api: {
url: "http://localhost:7002",
path: "http://localhost:7002",
},
};
const file = fileURLToPath(new URL("package.json", import.meta.url));
const json = readFileSync(file, "utf8");
const pkg = JSON.parse(json);
const package_file = fileURLToPath(new URL("package.json", import.meta.url));
const package_json = readFileSync(package_file, "utf8");
const package_data = JSON.parse(package_json);
for (let env in default_env) {
if (process.env["WEBSITE_" + env] === undefined) process.env["WEBSITE_" + env] = default_env[env];
}
env_from("WEBSITE", default_env);
export default defineConfig({
plugins: [sveltekit()],
@@ -31,6 +48,6 @@ export default defineConfig({
strictPort: true,
},
define: {
pkg: pkg,
pkg: package_data,
},
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

View File

@@ -7,19 +7,20 @@ services:
args:
WEBSITE_SOURCE_URL: "http://github.com/ngn13/website"
WEBSITE_REPORT_URL: "http://github.com/ngn13/website/issues"
WEBSITE_APP_URL: "http://localhost:7001"
WEBSITE_API_URL: "http://localhost:7002"
WEBSITE_DOC_URL: "http://doc:7003"
WEBSITE_API_URL: "http://api:7002"
WEBSITE_API_PATH: "http://localhost:7002"
security_opt:
- "no-new-privileges:true"
cap_drop:
- ALL
ports:
- "127.0.0.1:7001:7001"
restart: unless-stopped
depends_on:
- api
- doc
read_only: true
restart: unless-stopped
api:
container_name: "website_api"
@@ -34,28 +35,28 @@ services:
- "127.0.0.1:7002:7002"
volumes:
- ./data.db:/api/data.db:rw
restart: unless-stopped
environment:
WEBSITE_DEBUG: "false"
WEBSITE_APP_URL: "http://localhost:7001/"
WEBSITE_APP_URL: "http://localhost:7001"
WEBSITE_PASSWORD: "change_me"
WEBSITE_HOST: "0.0.0.0:7002"
WEBSITE_IP_HEADER: "X-Real-IP"
WEBSITE_INTERVAL: "1h"
WEBSITE_TIMEOUT: "15s"
WEBSITE_LIMIT: "5s"
restart: unless-stopped
doc:
container_name: "website_doc"
image: website_doc
read_only: true
build:
context: ./doc
security_opt:
- "no-new-privileges:true"
cap_drop:
- ALL
restart: unless-stopped
environment:
WEBSITE_HOST: "0.0.0.0:7003"
WEBSITE_DOCS_DIR: "./docs"
read_only: true
restart: unless-stopped

View File

@@ -81,7 +81,7 @@ BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
BreakStringLiterals: true
ColumnLimit: 120
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4

35
doc/.clang-tidy Normal file
View File

@@ -0,0 +1,35 @@
---
# "gnu-zero-variadic-macro-arguments" ignored because we are using GNU99
# standart
# "clang-diagnostic-language-extension-token" is ignored because we need the
# asm() extension token
# "DeprecatedOrUnsafeBufferHandling" ignored because C11 "_s" functions are not
# secure either
Checks: >-
clang-diagnostic-*,
-clang-diagnostic-gnu-zero-variadic-macro-arguments,
-clang-diagnostic-language-extension-token,
clang-analyzer-*,
-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,
portability-*,
performance-*,
WarningsAsErrors: '*'
HeaderFileExtensions:
- ''
- h
- hh
- hpp
- hxx
ImplementationFileExtensions:
- c
- cc
- cpp
- cxx
HeaderFilterRegex: '.*'
ExcludeHeaderFilterRegex: ''
FormatStyle: file
SystemHeaders: false
...

View File

@@ -1,9 +1,9 @@
FROM ghcr.io/ngn13/ctorm:1.7
FROM ghcr.io/ngn13/ctorm:1.8.1
WORKDIR /doc
COPY Makefile ./
COPY docs ./docs
COPY pages ./pages
COPY inc ./inc
COPY src ./src

View File

@@ -26,10 +26,14 @@ $(DISTDIR)/%.o: src/%.c
format:
clang-format -i -style=file $(CSRCS) $(HSRCS)
lint:
clang-tidy --warnings-as-errors --config= $(CSRCS) $(HSRCS)
clean:
rm -f *.elf
rm -rf $(DISTDIR)
run:
./doc.elf
.PHONY: format clean run
.PHONY: format lint clean run

View File

@@ -1,4 +0,0 @@
{
"title": "Privacy",
"desc": "Learn how I respect your privacy"
}

View File

@@ -1,41 +0,0 @@
As a privacy advocate myself I do my best to respect your privacy while simultaneously keeping my server safe. I also think it's
important to be transparent about this kind of stuff so I wrote this document to tell you how exactly I process or store your
information.
## DNS & SSL
Currently I use cloudflare's name servers, however cloudflare doesn't own my domain (I didn't purchase the domain from cloudflare)
nor it proxies any of the traffic. All of my records use the *DNS only*" mode, meaning they are just DNS records and they point
to my server, not cloudflare. This also means cloudflare doesn't control any of the SSL certificates. All the certificates are
stored on my server and they are created with Let's Encrypt. So I own the certificates and it's not possible for cloudlfare to
suddenly switch DNS records (it would break SSL).
## Usage metrics
I don't have any kind of request or traffic monitoring on my server. So no, your HTTP(S) requests or other network
connections are not processed to generate colorful graphs, pie charts and stuff.
You may have realized that in the bottom of this page there is total visitor number tracker. It is the only
usage/visitor metric tracking I have, which is implemented by website's API, which is free (as in freedom) so you can
go audit it's code yourself.
I want to make it clear that this metric tracker does not store any information about you or your HTTP(S) requests to
a database. It temporarily stores your IP address' SHA1 hash, in memory, so it doesn't recount the same visitor again
when they refresh the website or visit it multiple times in a short period of time. After a certain amount of requests,
your IP address' SHA1 hash will be removed from the memory and it will be replaced with a new visitor's SHA1 hash instead.
## Logs
All the HTTP(S) services are proxied with nginx, and nginx logs all of them to a file on the disk. This file (`access.log`)
is only readable by the root user, and it is contents are deleted every 4 hours (with `shred` to make sure it doesn't leave
anything on the disk). The logs include *only* the following information:
- Request time
- Requested host
- Requested path
- HTTP request method
- HTTP response code
This is the minimal information I need to trace any issues if something goes wrong, which is the main reason why I use
logging at all, to make it easier to find any issues.
## Data removal
If you want to remove any of your data from my server, [you can send me an email](mailto:ngn@ngn.tf). And yes this includes
removing usage metrics and logs.

View File

@@ -1,4 +0,0 @@
{
"title": "Gizlilik",
"desc": "Gizliliğinize nasıl önem verdiğimi öğrenin"
}

View File

@@ -1,40 +0,0 @@
Kişisel olarak ben de bir gizlik savunucusu olduğumdan, bir yandan sunucumu güvende tutarken bir yandan da gizliliğinize önem
göstermek için elimden geleni yapıyorum. Aynı zamanda bu tarz şeyler hakkında şeffaf ve açık olmanın önemli olduğunu düşünüyorum,
o yüzden verilerinizi nasıl işlediğimi ya da depoladığımı anlamanız için bu dökümanı yazmaya karar verdim.
## DNS & SSL
Şuan cloudflare'in isim sunucularını kullanıyorum, ancak cloudflare alan adıma sahip değil (alan adımı cloudflare'den almadım)
ve aynı şekilde herhangi bir trafiğe vekillik etmiyor. Tüm DNS kayıtlarım *Sadece DNS* modunu kullanıyor, yani sadece
DNS kayıtlarından ibaretler ve benim sunucuma işaret ediyorlar, cloudflare'e değil. Bu aynı zamanda cloudflare SSL sertifikalarımı
kontrol etmiyor demek. Tüm sertifikalar benim sunucumda tutuluyor ve Let's Encrypt ile oluşturuldular. Yani sertifikalar bana ait
ve cloudflare'in aniden DNS kayıtlarını değiştirmesi mümkün değil (bu SSL'in bozulmasına sebep olur).
## Kullanım metrikleri
Sunucumda herhangi bir istek ya da trafik monitörlermesi yok. Yani hayır, HTTP(S) istekleriniz ya da diğer ağ
bağlantılarınız renki grafikler, pasta grafikleri gibi şeyler üretmek için işlenmiyor.
Bu sayfanın altında bir ziyaretçi sayısı takipçisi olduğunu farketmiş olabilirsiniz. Bu kullandığım tek kullanım/ziyaretçi
metrik takibi ve websitemin, özgür olan, bu yüzden kodunu kendiniz denetleyebileceğiniz API'ı ile implemente edildi.
Bu metrik takipçisinin, HTTP(S) istekleriniz hakkında herhangi bir veriyi bir veri tabanına kaydetmediğini belirtmek isterim.
Bu takipçi geçici olarak IP adresinizin SHA1 hash'ini bellekte tutuyor, bunun amacı aynı ziyaretçiyi sayfayı yenilediği zaman
ya da kısa bir süre için websitesini birden fazla kez ziyaret ettiği zaman tekrar saymayı önlemek. Belirli bir miktar istekten
sonra, IP adresinizin SHA1 hash'i bellekten kaldırılacaktır ve yeni bir ziyaretçinin SHA1'i onun yerine geçicektir.
## Kayıtlar
Tüm HTTP(S) servisleri nginx ile vekilleniyor, ve nginx hepsini disk üzerindeki bir dosyaya kaydediyor. Bu dosya (`access.log`)
sadece root kullanıcısı tarafından okunabilir, ve içerği her 4 saatde bir siliniyor (diskde veri kalmadığından emin olmak için
shred komutu ile). Kayıtlar *sadece* aşağıdaki bilgileri içeriyor:
- İstek zamanı
- İstenilen host
- İstenilen yol
- HTTP istek yöntemi
- HTTP cevap kodu
Bu birşeyler yanlış giderse sorunları bulmak için ihtiyacım olan en az bilgi, kayıt tutmamın ana sebeplerinden bir tanesi
zaten bu, sorunları bulmayı kolaylaştırmak.
## Veri silimi
Sunucumdan herhangi bir verinizi kaldırmak isterseniz, [bana bir email gönderebilirsiniz](mailto:ngn@ngn.tf). Ve evet buna
kullanım metrikleri ve kayıtlar dahil.

View File

@@ -3,13 +3,13 @@
#include <stdbool.h>
#include <dirent.h>
#include "util.h"
#include "file.h"
typedef struct {
DIR *dir;
util_file_t *file;
char name[NAME_MAX + 1];
char *lang;
DIR *dir;
file_t *file;
char name[NAME_MAX + 1];
char *lang;
} docs_t;
bool docs_init(docs_t *docs, char *dir);

10
doc/inc/file.h Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
#include <stdint.h>
typedef struct {
char *content;
int64_t size;
} file_t;
file_t *file_load(int dirfd, char *path);
void file_free(file_t *file);

View File

@@ -4,16 +4,8 @@
#include <stdint.h>
#include <ctype.h>
typedef struct {
char *content;
uint64_t size;
} util_file_t;
#define util_toupper(str) \
for (char *c = str; *c != 0; c++) \
#define util_toupper(str) \
for (char *c = str; *c != 0; c++) \
*c = toupper(*c)
uint64_t util_endswith(char *str, char *suf);
void util_send(ctorm_res_t *res, uint16_t code, cJSON *json);
util_file_t *util_file_load(int dirfd, char *path);
void util_file_free(util_file_t *file);
bool util_parse_doc_name(char *name, char **lang, const char *ext);
uint64_t util_endswith(char *str, char *suf);
void util_send(ctorm_res_t *res, uint16_t code, cJSON *json);

View File

@@ -1,4 +1,4 @@
{
"title": "API documentation",
"title": "API",
"desc": "Website's API documentation"
}

View File

@@ -1,51 +1,64 @@
My website's API stores information about my self-hosted services, it also allows me to
publish news and updates about these services using an Atom feed and it keeps track of
visitor metrics. The API itself is written in Go and uses SQLite for storage.
My website's API, stores information about my self-hosted services, it also
allows me to publish news and updates about these services using an Atom feed
and it keeps track of visitor metrics.
This documentation contains information about all the available API endpoints.
All the endpoints can be accessed using the `/api` route.
## Version 1 Endpoints
Each version 1 endpoint, can be accessed using the `/v1` route.
All the endpoints return JSON formatted data.
### Errors
If any error occurs, you will get a non-200 response. And the JSON data will have an
`error` key, which will contain information about the error that occured, in the
string format. This is the only JSON key that will be set in non-200 responses.
If any error occurs, you will get a non-200 response. And the JSON data will
have an `error` key, which will contain information about the error that
occured, in the string format. This is the only JSON key that will be set in
non-200 responses.
### Results
If no error occurs, `error` key will be set to an emtpy string (""). If the endpoint
returns any data, this will be stored using the `result` key. The `result` have a
different expected type and a format for each endpoint.
If no error occurs, `error` key will be set to an emtpy string (""). If the
endpoint returns any data, this will be stored using the `result` key. The
`result` have a different expected type and a format for each endpoint.
### Multilang
Some `result` formats may use a structure called "Multilang". This is a simple JSON
structure that includes one key for each supported language. The key is named after
the language it represents. Currently only supported languages are:
Some `result` formats may use a structure called "Multilang". This is a simple
JSON structure that includes one key for each supported language. The key is
named after the language it represents. Currently only supported languages are:
- English (`en`)
- Turkish (`tr`)
So each multilang structure, will have **at least** one of these keys.
Here is an example multilang structure:
```
{
"en": "Hello, world!",
"tr": "Merhaba, dünya!"
}
```
If a `result` field is using a multilang structure, it will be specified as "Multilang"
in the rest of the documentation.
If a `result` field is using a multilang structure, it will be specified as
"Multilang" in the rest of the documentation.
### Administrator routes
The endpoints under the `/v1/admin` route, are administrator-only routes. To access
these routes you'll need to specfiy a password using the `Authorization` header. If
the password you specify, matches with the password specified using the `API_PASSWORD`
environment variable, you will be able to access the route.
The endpoints under the `/v1/admin` route, are administrator-only routes. To
access these routes you'll need to specfiy a password using the `Authorization`
header. If the password you specify, matches with the password specified using
the `API_PASSWORD` environment variable, you will be able to access the route.
### GET /v1/services
Returns a list of available services. Each service has the following JSON format:
Returns a list of available services. Each service has the following JSON
format:
```
{
"name": "Test Service",
@@ -61,73 +74,91 @@ Returns a list of available services. Each service has the following JSON format
"i2p": ""
}
```
Where:
- `name`: Service name (string)
- `desc`: Service description (Multilang)
- `check_time`: Last time status check time for the service, set 0 if status checking is
not supported for this service/status checking is disabled (integer, UNIX timestamp)
- `check_time`: Last time status check time for the service, set 0 if status
checking is not supported for this service/status checking is disabled
(integer, UNIX timestamp)
- `check_res`: Last service status check result (integer)
* 0 if the service is down
* 1 if the service is up
* 2 if the service is up, but slow
* 3 if the service doesn't support status checking/status checking is disabled
- 0 if the service is down
- 1 if the service is up
- 2 if the service is up, but slow
- 3 if the service doesn't support status checking/status checking is disabled
- `check_url`: URL used for service's status check (string, empty if none)
- `clear`: Clearnet URL for the service (string, empty string if none)
- `onion`: Onion (TOR) URL for the service (string, empty string if none)
- `i2p`: I2P URL for the service (string, empty string if none)
You can also get information about a specific service by specifying it's name using
a URL query named `name`.
You can also get information about a specific service by specifying it's name
using a URL query named `name`.
### GET /v1/news/:language
Returns a Atom feed of news for the given language. Supports languages that are supported
by Multilang.
Returns a Atom feed of news for the given language. Supports languages that are
supported by Multilang.
### GET /v1/metrics
Returns metrics about the API usage. The metric data has the following format:
```
{
"since":1736294400,
"total":8
"since": 1736294400,
"total": 8
}
```
Where:
- `since`: Metric collection start date (integer, UNIX timestamp)
- `total`: Total number of visitors (integer)
### GET /v1/admin/logs
Returns a list of administrator logs. Each log has the following JSON format:
```
{
"action": "Added service \"Test Service\"",
"time": 1735861794
}
```
Where:
- `action`: Action that the administrator performed (string)
- `time`: Time when the administrator action was performed (integer, UNIX timestamp)
- `time`: Time when the administrator action was performed (integer, UNIX
timestamp)
### PUT /v1/admin/service/add
Creates a new service. The request body needs to contain JSON data, and it needs to
have the JSON format used to represent a service. See `/v1/services/all` route to
see this format.
Creates a new service. The request body needs to contain JSON data, and it needs
to have the JSON format used to represent a service. See `/v1/services/all`
route to see this format.
Returns no data on success.
### DELETE /v1/admin/service/del
Deletes a service. The client needs to specify the name of the service to delete, by
setting the URL query `name`.
Deletes a service. The client needs to specify the name of the service to
delete, by setting the URL query `name`.
Returns no data on success.
### GET /v1/admin/service/check
Forces a status check for all the services.
Returns no data on success.
### PUT /v1/admin/news/add
Creates a news post. The request body needs to contain JSON data, and it needs
to use the following JSON format:
```
{
"id": "test_news",
@@ -142,7 +173,9 @@ to use the following JSON format:
}
}
```
Where:
- `id`: Unique ID for the news post (string)
- `title`: Title for the news post (Multilang)
- `author`: Author of the news post (string)
@@ -151,7 +184,8 @@ Where:
Returns no data on success.
### DELETE /v1/admin/news/del
Deletes a news post. The client needs to specify the ID of the news post to delete,
by setting the URL query `id`.
Deletes a news post. The client needs to specify the ID of the news post to
delete, by setting the URL query `id`.
Returns no data on success.

View File

@@ -1,4 +1,4 @@
{
"title": "API dökümantasyonu",
"title": "API",
"desc": "Websitesinin API dökümantasyonu"
}

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