Compare commits
154 Commits
renovate/v
...
ab5977eaae
Author | SHA1 | Date | |
---|---|---|---|
ab5977eaae | |||
1f47d59760 | |||
38abe411a8 | |||
e7216d51e3 | |||
2b244645c6 | |||
43589f7ddc | |||
b43a219fb3 | |||
7bf92f9049 | |||
577195cb7f | |||
fc64b742bb | |||
922eaeb124 | |||
65ec5a51aa | |||
8036b0c1e1 | |||
c505698b0b
|
|||
4e08375748
|
|||
912bf616b6
|
|||
d1ddbf2605 | |||
66b01bd58d | |||
01468670e3 | |||
1f09aae11e | |||
ce1ac7b09e | |||
f9faddf660 | |||
945f1b84f2 | |||
a9d2633107
|
|||
506d16e0c9
|
|||
30c3b0c37b | |||
2da4f81a6b | |||
cc482fc0ad | |||
51ea22fbc5 | |||
58595d5eb3 | |||
671d44017f | |||
95d24b734b | |||
c2e31fb440 | |||
2d3dab5646 | |||
4b2e12b374 | |||
c6323cae38 | |||
e05d42a956 | |||
426c474016 | |||
91ca908980 | |||
5a61508359 | |||
86c0e3cc97 | |||
1afa33f28b | |||
a22b1e67c8 | |||
f477b4525b | |||
462d35ea0d | |||
693c94274a | |||
af2e5b49ae | |||
33a31dac32 | |||
00b7227d4d | |||
2f9d38cd45 | |||
e68dd1d317 | |||
fe834d2778 | |||
686f51dcde | |||
dc43bdb3af
|
|||
05185cb82a
|
|||
ee3fa10096 | |||
0e4457d5fb | |||
b95c51193e | |||
322d8d5c39 | |||
904e9f6d15 | |||
cc4abc85fe | |||
73cfcea923 | |||
90af3d0500
|
|||
8d16273540
|
|||
4fb78c0ab7
|
|||
a57a4955ba
|
|||
ccf0d8abf9
|
|||
b312e40204
|
|||
a67bd74ced
|
|||
9a72d3f95d
|
|||
66c96ae312
|
|||
f779a03ae5
|
|||
bcb48d789b
|
|||
aa69525912
|
|||
0134bd2ab1
|
|||
84e477b23e | |||
515dcf6a09 | |||
a50cc1ff7f | |||
f2ec9479b1 | |||
e1eae3c05b | |||
53ab201ab3 | |||
c9792fb6be | |||
68808a14f9 | |||
4343192a2c | |||
20fb3cd685 | |||
906ace188b | |||
f2a1a21a8c | |||
9353631931 | |||
4eb5ad6ef3
|
|||
bac570615e
|
|||
037a4405b9
|
|||
389a9d3df4 | |||
921fe4255d | |||
f7e4442de5 | |||
e4d1fc21fb | |||
fe6b3512fd | |||
3dbc2e0510
|
|||
26362cf89d
|
|||
16288c2bcb | |||
177b546fb9 | |||
311a37ae5c | |||
4380638982 | |||
32e0ddaa36 | |||
27c58b1174 | |||
d5f1325b79 | |||
9e27aabdb9 | |||
a1967141ae | |||
a172262dbd | |||
caee8c2a90 | |||
060c5f0c73 | |||
e2796b39b8 | |||
191c54cc9a | |||
87095a8c35 | |||
a28a678693 | |||
d3fd1e479c | |||
7f97f5330e | |||
30286710d1 | |||
8de018bf5c | |||
5a73910331 | |||
53236fab61 | |||
d5dfaaad96 | |||
c1e2974f1b | |||
40cdbfba36 | |||
be8777eb67 | |||
b75bd4f3b2 | |||
c127c62492 | |||
6a01631d22 | |||
1389233108 | |||
e2a4df8602 | |||
b4d8ab7606 | |||
478834fe2f | |||
ba441d5192
|
|||
4556452f69 | |||
6217103c5a | |||
eee6c2c2d2 | |||
db7d062662 | |||
c8a4067596
|
|||
437404dfca | |||
1b24596abf | |||
1759261332 | |||
3a9685511b | |||
7eac5d8bb3 | |||
68bc30d98b | |||
bc9b92cdd2 | |||
e49b30b2fb | |||
01a6cf34bc | |||
3ab85605c6 | |||
2bfc4886c8 | |||
e861bb4030 | |||
002044232f | |||
7facbbdeb2 | |||
55038a9745 | |||
665fb2e34c | |||
ce40a002b7 |
@ -3,6 +3,7 @@ name: Build the docker image for the API
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
|
paths: ["api/**"]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: git.ngn.tf
|
REGISTRY: git.ngn.tf
|
||||||
|
@ -3,6 +3,7 @@ name: Build the docker image for the frontend application
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
|
paths: ["app/**"]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: git.ngn.tf
|
REGISTRY: git.ngn.tf
|
||||||
@ -27,8 +28,8 @@ jobs:
|
|||||||
cd app
|
cd app
|
||||||
docker build --build-arg WEBSITE_REPORT_URL=https://git.ngn.tf/ngn/website/issues/new \
|
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_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_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 .
|
--tag ${{env.REGISTRY}}/${{env.IMAGE}}:latest .
|
||||||
docker push ${{env.REGISTRY}}/${{env.IMAGE}}:latest
|
docker push ${{env.REGISTRY}}/${{env.IMAGE}}:latest
|
||||||
|
@ -3,6 +3,7 @@ name: Build the docker image for the doc server
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
|
paths: ["doc/**"]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: git.ngn.tf
|
REGISTRY: git.ngn.tf
|
||||||
|
141
README.md
@ -4,85 +4,112 @@
|
|||||||

|

|
||||||

|

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

|
|
||||||

|
|
||||||
|
@ -405,4 +405,4 @@ class AdminScript:
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
script = AdminScript()
|
script = AdminScript()
|
||||||
exit(script.run() if 1 else 0)
|
exit(0 if script.run() else 1)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.23.5
|
FROM golang:1.24.3
|
||||||
|
|
||||||
WORKDIR /api
|
WORKDIR /api
|
||||||
|
|
||||||
|
@ -3,107 +3,47 @@ package config
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
|
"github.com/ngn13/ortam"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Type struct {
|
type Type struct {
|
||||||
Options []Option
|
Debug bool // should display debug messgaes?
|
||||||
Count int
|
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) {
|
func Load() (*Type, error) {
|
||||||
for i := 0; i < c.Count; i++ {
|
var conf = Type{
|
||||||
if c.Options[i].Name != name {
|
Debug: false,
|
||||||
continue
|
Password: "",
|
||||||
}
|
Host: "0.0.0.0:7002",
|
||||||
|
IPHeader: "X-Real-IP",
|
||||||
if c.Options[i].Type != typ {
|
Interval: "1h",
|
||||||
return nil, fmt.Errorf("bad option type")
|
Timeout: "15s",
|
||||||
}
|
Limit: "5s",
|
||||||
|
|
||||||
return &c.Options[i], nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("option not found")
|
if err := ortam.Load(&conf, "WEBSITE"); err != nil {
|
||||||
}
|
return nil, err
|
||||||
|
}
|
||||||
func (c *Type) Load() (err error) {
|
|
||||||
var (
|
if conf.AppUrl == nil {
|
||||||
env_val string
|
conf.AppUrl, _ = url.Parse("http://localhost:7001/")
|
||||||
env_name string
|
}
|
||||||
opt *Option
|
|
||||||
exists bool
|
if conf.Password == "" {
|
||||||
)
|
return nil, fmt.Errorf("password is not specified")
|
||||||
|
}
|
||||||
// default options
|
|
||||||
c.Options = []Option{
|
if conf.Host == "" {
|
||||||
{Name: "debug", Value: "false", Type: OPTION_TYPE_BOOL, Required: true}, // should display debug messgaes?
|
return nil, fmt.Errorf("host address is not specified")
|
||||||
{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
|
return &conf, nil
|
||||||
{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
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -1,10 +1,11 @@
|
|||||||
module github.com/ngn13/website/api
|
module github.com/ngn13/website/api
|
||||||
|
|
||||||
go 1.21.3
|
go 1.24.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gofiber/fiber/v2 v2.52.6
|
github.com/gofiber/fiber/v2 v2.52.8
|
||||||
github.com/mattn/go-sqlite3 v1.14.24
|
github.com/mattn/go-sqlite3 v1.14.28
|
||||||
|
github.com/ngn13/ortam v0.0.0-20250421004351-8dea81680817
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
24
api/go.sum
@ -1,17 +1,11 @@
|
|||||||
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 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
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 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
||||||
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
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/gofiber/fiber/v2 v2.52.8 h1:xl4jJQ0BV5EJTA2aWiKw/VddRpHrKeZLF0QPUxqn0x4=
|
||||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
@ -19,12 +13,16 @@ 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.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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
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 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
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.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.27/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-20250412195317-e76e62a7a305 h1:1YxtSMwR14PklXNlZxIqcmfpiq2+G98YNmhSuz7GKCQ=
|
||||||
|
github.com/ngn13/ortam v0.0.0-20250412195317-e76e62a7a305/go.mod h1:MSJZ4ZstrLvVEvivbp9hhup+iL8rvtpgKcYaF3DSOKk=
|
||||||
|
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 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
@ -35,7 +33,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=
|
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.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.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 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
14
api/main.go
@ -36,18 +36,18 @@ func main() {
|
|||||||
app *fiber.App
|
app *fiber.App
|
||||||
stat status.Type
|
stat status.Type
|
||||||
|
|
||||||
conf config.Type
|
conf *config.Type
|
||||||
db database.Type
|
db database.Type
|
||||||
|
|
||||||
err error
|
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())
|
util.Fail("failed to load the configuration: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !conf.GetBool("debug") {
|
if !conf.Debug {
|
||||||
util.Debg = func(m string, v ...any) {}
|
util.Debg = func(m string, v ...any) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ func main() {
|
|||||||
return
|
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())
|
util.Fail("failed to setup the status checker: %s", err.Error())
|
||||||
return
|
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.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("status", &stat)
|
||||||
c.Locals("config", &conf)
|
c.Locals("config", conf)
|
||||||
c.Locals("database", &db)
|
c.Locals("database", &db)
|
||||||
|
|
||||||
return c.Next()
|
return c.Next()
|
||||||
@ -121,9 +121,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// start the app
|
// 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())
|
util.Fail("failed to start the web server: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ func admin_log(c *fiber.Ctx, m string) error {
|
|||||||
func AuthMiddleware(c *fiber.Ctx) error {
|
func AuthMiddleware(c *fiber.Ctx) error {
|
||||||
conf := c.Locals("config").(*config.Type)
|
conf := c.Locals("config").(*config.Type)
|
||||||
|
|
||||||
if c.Get("Authorization") != conf.GetStr("password") {
|
if c.Get("Authorization") != conf.Password {
|
||||||
return util.ErrAuth(c)
|
return util.ErrAuth(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,7 @@ import (
|
|||||||
|
|
||||||
func GET_Index(c *fiber.Ctx) error {
|
func GET_Index(c *fiber.Ctx) error {
|
||||||
conf := c.Locals("config").(*config.Type)
|
conf := c.Locals("config").(*config.Type)
|
||||||
app := conf.GetURL("app_url")
|
|
||||||
|
|
||||||
// redirect to the API documentation
|
// redirect to the API documentation
|
||||||
return c.Redirect(app.JoinPath("/doc/api").String())
|
return c.Redirect(conf.AppUrl.JoinPath("/doc/api").String())
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,6 @@ func GET_News(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
db := c.Locals("database").(*database.Type)
|
db := c.Locals("database").(*database.Type)
|
||||||
conf := c.Locals("config").(*config.Type)
|
conf := c.Locals("config").(*config.Type)
|
||||||
app := conf.GetURL("app_url")
|
|
||||||
lang := c.Params("lang")
|
lang := c.Params("lang")
|
||||||
|
|
||||||
if lang == "" || len(lang) != 2 {
|
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{
|
if feed, err = util.Render("views/news.xml", fiber.Map{
|
||||||
|
"app_url": conf.AppUrl,
|
||||||
"updated": time.Now().Format(time.RFC3339),
|
"updated": time.Now().Format(time.RFC3339),
|
||||||
"entries": entries,
|
"entries": entries,
|
||||||
"lang": lang,
|
"lang": lang,
|
||||||
"app": app,
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return util.ErrInternal(c, err)
|
return util.ErrInternal(c, err)
|
||||||
}
|
}
|
||||||
|
@ -66,29 +66,24 @@ func (s *Type) loop() {
|
|||||||
|
|
||||||
func (s *Type) Setup(conf *config.Type, db *database.Type) error {
|
func (s *Type) Setup(conf *config.Type, db *database.Type) error {
|
||||||
var (
|
var (
|
||||||
dur time.Duration
|
dur time.Duration
|
||||||
iv, to, lm string
|
err error
|
||||||
err error
|
|
||||||
)
|
)
|
||||||
|
|
||||||
iv = conf.GetStr("interval")
|
if conf.Interval == "" || conf.Timeout == "" || conf.Limit == "" {
|
||||||
to = conf.GetStr("timeout")
|
|
||||||
lm = conf.GetStr("limit")
|
|
||||||
|
|
||||||
if iv == "" || to == "" || lm == "" {
|
|
||||||
s.disabled = true
|
s.disabled = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if dur, err = util.GetDuration(iv); err != nil {
|
if dur, err = util.GetDuration(conf.Interval); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.timeout, err = util.GetDuration(iv); err != nil {
|
if s.timeout, err = util.GetDuration(conf.Timeout); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.limit, err = util.GetDuration(iv); err != nil {
|
if s.limit, err = util.GetDuration(conf.Limit); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,10 +10,9 @@ import (
|
|||||||
|
|
||||||
func IP(c *fiber.Ctx) string {
|
func IP(c *fiber.Ctx) string {
|
||||||
conf := c.Locals("config").(*config.Type)
|
conf := c.Locals("config").(*config.Type)
|
||||||
ip_header := conf.GetStr("ip_header")
|
|
||||||
|
|
||||||
if ip_header != "" && c.Get(ip_header) != "" {
|
if conf.IPHeader != "" && c.Get(conf.IPHeader) != "" {
|
||||||
return strings.Clone(c.Get(ip_header))
|
return strings.Clone(c.Get(conf.IPHeader))
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.IP()
|
return c.IP()
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">
|
<?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>
|
<updated>{{.updated}}</updated>
|
||||||
<subtitle>News and updates about my projects and self-hosted services</subtitle>
|
<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 }}
|
{{ range .entries }}
|
||||||
<entry>
|
<entry>
|
||||||
<title>{{.Title}}</title>
|
<title>{{.Title}}</title>
|
||||||
|
1
app/.gitignore
vendored
@ -8,3 +8,4 @@ node_modules
|
|||||||
!.env.example
|
!.env.example
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
|
nerdfonts.*
|
||||||
|
@ -1,22 +1,24 @@
|
|||||||
# build the application with node
|
# build the application with node
|
||||||
FROM node:23.6.0 AS build
|
FROM node:23.11.1 AS build
|
||||||
|
|
||||||
ARG WEBSITE_REPORT_URL
|
ARG WEBSITE_REPORT_URL
|
||||||
ARG WEBSITE_SOURCE_URL
|
ARG WEBSITE_SOURCE_URL
|
||||||
ARG WEBSITE_APP_URL
|
|
||||||
ARG WEBSITE_API_URL
|
|
||||||
ARG WEBSITE_DOC_URL
|
ARG WEBSITE_DOC_URL
|
||||||
|
ARG WEBSITE_API_URL
|
||||||
|
ARG WEBSITE_API_PATH
|
||||||
|
|
||||||
ENV WEBSITE_REPORT_URL=$WEBSITE_REPORT_URL
|
ENV WEBSITE_REPORT_URL=$WEBSITE_REPORT_URL
|
||||||
ENV WEBSITE_SOURCE_URL=$WEBSITE_SOURCE_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_DOC_URL=$WEBSITE_DOC_URL
|
||||||
|
ENV WEBSITE_API_URL=$WEBSITE_API_URL
|
||||||
|
ENV WEBSITE_API_PATH=$WEBSITE_API_PATH
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . /app
|
COPY . /app
|
||||||
|
|
||||||
RUN npm install && npm run build
|
RUN apt install -y make sed wget
|
||||||
|
RUN npm install
|
||||||
|
RUN make
|
||||||
|
|
||||||
# run it with bun (a lot faster)
|
# run it with bun (a lot faster)
|
||||||
FROM oven/bun:latest AS main
|
FROM oven/bun:latest AS main
|
||||||
|
23
app/Makefile
@ -1,10 +1,25 @@
|
|||||||
all:
|
NF_CSS = static/css/nerdfonts.css
|
||||||
|
NF_WOFF = static/assets/nerdfonts.woff2
|
||||||
|
|
||||||
|
all: $(NF_CSS)
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
|
$(NF_CSS): $(NF_WOFF)
|
||||||
|
wget "https://www.nerdfonts.com/assets/css/webfont.css" -O $@
|
||||||
|
sed 's/\.\.\/fonts\/Symbols-2048-em Nerd Font Complete\.woff2/\/assets\/nerdfonts\.woff2/g' -i $@
|
||||||
|
|
||||||
|
$(NF_WOFF):
|
||||||
|
wget "https://www.nerdfonts.com/assets/fonts/Symbols-2048-em%20Nerd%20Font%20Complete.woff2" -O $@
|
||||||
|
|
||||||
|
run: $(NF_CSS)
|
||||||
|
npm run dev
|
||||||
|
|
||||||
format:
|
format:
|
||||||
npm run format
|
npm run format
|
||||||
|
|
||||||
run:
|
clean:
|
||||||
npm run dev
|
rm -rf build
|
||||||
|
rm $(NF_CSS)
|
||||||
|
rm $(NF_WOFF)
|
||||||
|
|
||||||
.PHONY: format
|
.PHONY: format run clean
|
||||||
|
135
app/package-lock.json
generated
@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "website",
|
"name": "website",
|
||||||
"version": "6.0",
|
"version": "6.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "website",
|
"name": "website",
|
||||||
"version": "6.0",
|
"version": "6.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dompurify": "^3.2.3",
|
"dompurify": "^3.2.3",
|
||||||
"marked": "^15.0.6",
|
"marked": "^15.0.6",
|
||||||
"svelte-i18n": "^4.0.1"
|
"svelte-i18n": "^4.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "^3.3.1",
|
"@sveltejs/adapter-auto": "^6.0.0",
|
||||||
"@sveltejs/adapter-node": "^5.2.11",
|
"@sveltejs/adapter-node": "^5.2.11",
|
||||||
"@sveltejs/kit": "^2.15.1",
|
"@sveltejs/kit": "^2.15.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.3",
|
"@sveltejs/vite-plugin-svelte": "^4.0.3",
|
||||||
@ -831,14 +831,21 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@sveltejs/acorn-typescript": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"acorn": "^8.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@sveltejs/adapter-auto": {
|
"node_modules/@sveltejs/adapter-auto": {
|
||||||
"version": "3.3.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-6.0.1.tgz",
|
||||||
"integrity": "sha512-5Sc7WAxYdL6q9j/+D0jJKjGREGlfIevDyHSQ2eNETHcB1TKlQWHcAo8AS8H1QdjNvSXpvOwNjykDUHPEAyGgdQ==",
|
"integrity": "sha512-mcWud3pYGPWM2Pphdj8G9Qiq24nZ8L4LB7coCUckUEy5Y7wOWGJ/enaZ4AtJTcSm5dNK1rIkBRoqt+ae4zlxcQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"license": "MIT",
|
||||||
"import-meta-resolve": "^4.1.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@sveltejs/kit": "^2.0.0"
|
"@sveltejs/kit": "^2.0.0"
|
||||||
}
|
}
|
||||||
@ -860,24 +867,25 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sveltejs/kit": {
|
"node_modules/@sveltejs/kit": {
|
||||||
"version": "2.15.1",
|
"version": "2.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.15.1.tgz",
|
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.21.3.tgz",
|
||||||
"integrity": "sha512-8t7D3hQHbUDMiaQ2RVnjJJ/+Ur4Fn/tkeySJCsHtX346Q9cp3LAnav8xXdfuqYNJwpUGX0x3BqF1uvbmXQw93A==",
|
"integrity": "sha512-Bd05srNOaqP05qnytjg/KkWNlkcwEpE76s0xGSlgzL4I8pLyrK3c9+a7zMCquoiEEIZF2ecGTn6Fj/lELjaa8A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@sveltejs/acorn-typescript": "^1.0.5",
|
||||||
"@types/cookie": "^0.6.0",
|
"@types/cookie": "^0.6.0",
|
||||||
|
"acorn": "^8.14.1",
|
||||||
"cookie": "^0.6.0",
|
"cookie": "^0.6.0",
|
||||||
"devalue": "^5.1.0",
|
"devalue": "^5.1.0",
|
||||||
"esm-env": "^1.2.1",
|
"esm-env": "^1.2.2",
|
||||||
"import-meta-resolve": "^4.1.0",
|
|
||||||
"kleur": "^4.1.5",
|
"kleur": "^4.1.5",
|
||||||
"magic-string": "^0.30.5",
|
"magic-string": "^0.30.5",
|
||||||
"mrmime": "^2.0.0",
|
"mrmime": "^2.0.0",
|
||||||
"sade": "^1.8.1",
|
"sade": "^1.8.1",
|
||||||
"set-cookie-parser": "^2.6.0",
|
"set-cookie-parser": "^2.6.0",
|
||||||
"sirv": "^3.0.0",
|
"sirv": "^3.0.0",
|
||||||
"tiny-glob": "^0.2.9"
|
"vitefu": "^1.0.6"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"svelte-kit": "svelte-kit.js"
|
"svelte-kit": "svelte-kit.js"
|
||||||
@ -957,9 +965,10 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.14.0",
|
"version": "8.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
|
||||||
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
||||||
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@ -967,14 +976,6 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/acorn-typescript": {
|
|
||||||
"version": "1.4.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz",
|
|
||||||
"integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"acorn": ">=8.9.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/aria-query": {
|
"node_modules/aria-query": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
|
||||||
@ -1083,9 +1084,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/dompurify": {
|
"node_modules/dompurify": {
|
||||||
"version": "3.2.3",
|
"version": "3.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz",
|
||||||
"integrity": "sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==",
|
"integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==",
|
||||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@types/trusted-types": "^2.0.7"
|
"@types/trusted-types": "^2.0.7"
|
||||||
@ -1182,9 +1183,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esm-env": {
|
"node_modules/esm-env": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
||||||
"integrity": "sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng=="
|
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/esniff": {
|
"node_modules/esniff": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
@ -1202,9 +1204,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/esrap": {
|
"node_modules/esrap": {
|
||||||
"version": "1.3.2",
|
"version": "1.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/esrap/-/esrap-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.6.tgz",
|
||||||
"integrity": "sha512-C4PXusxYhFT98GjLSmb20k9PREuUdporer50dhzGuJu9IJXktbMddVCMLAERl5dAHyAi73GWWCE4FVHGP1794g==",
|
"integrity": "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||||
}
|
}
|
||||||
@ -1296,17 +1299,6 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/import-meta-resolve": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/wooorm"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/intl-messageformat": {
|
"node_modules/intl-messageformat": {
|
||||||
"version": "10.7.11",
|
"version": "10.7.11",
|
||||||
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.11.tgz",
|
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.11.tgz",
|
||||||
@ -1393,9 +1385,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/marked": {
|
"node_modules/marked": {
|
||||||
"version": "15.0.6",
|
"version": "15.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/marked/-/marked-15.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
|
||||||
"integrity": "sha512-Y07CUOE+HQXbVDCGl3LXggqJDbXDP2pArc2C1N1RRMN0ONiShoSsIInMd5Gsxupe7fKLpgimTV+HOJ9r7bA+pg==",
|
"integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"marked": "bin/marked.js"
|
"marked": "bin/marked.js"
|
||||||
@ -1524,9 +1516,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "3.4.2",
|
"version": "3.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
|
||||||
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
|
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -1540,9 +1532,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier-plugin-svelte": {
|
"node_modules/prettier-plugin-svelte": {
|
||||||
"version": "3.3.3",
|
"version": "3.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.4.0.tgz",
|
||||||
"integrity": "sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==",
|
"integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@ -1658,20 +1650,21 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/svelte": {
|
"node_modules/svelte": {
|
||||||
"version": "5.16.0",
|
"version": "5.33.13",
|
||||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.33.13.tgz",
|
||||||
"integrity": "sha512-Ygqsiac6UogVED2ruKclU+pOeMThxWtp9LG+li7BXeDKC2paVIsRTMkNmcON4Zejerd1s5sZHWx6ZtU85xklVg==",
|
"integrity": "sha512-uT3BAPpHGaJqpOgdwJwIK7P4JkBkSS0vylbaRXxQjt1gr+DZ9BiPkhmbZw3ql8LJofUyz5XyrzzQDgQQdfP86Q==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.3.0",
|
"@ampproject/remapping": "^2.3.0",
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
|
"@sveltejs/acorn-typescript": "^1.0.5",
|
||||||
"@types/estree": "^1.0.5",
|
"@types/estree": "^1.0.5",
|
||||||
"acorn": "^8.12.1",
|
"acorn": "^8.12.1",
|
||||||
"acorn-typescript": "^1.4.13",
|
|
||||||
"aria-query": "^5.3.1",
|
"aria-query": "^5.3.1",
|
||||||
"axobject-query": "^4.1.0",
|
"axobject-query": "^4.1.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"esm-env": "^1.2.1",
|
"esm-env": "^1.2.1",
|
||||||
"esrap": "^1.3.2",
|
"esrap": "^1.4.6",
|
||||||
"is-reference": "^3.0.3",
|
"is-reference": "^3.0.3",
|
||||||
"locate-character": "^3.0.0",
|
"locate-character": "^3.0.0",
|
||||||
"magic-string": "^0.30.11",
|
"magic-string": "^0.30.11",
|
||||||
@ -2156,10 +2149,11 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.4.11",
|
"version": "5.4.19",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
|
||||||
"integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
|
"integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.21.3",
|
"esbuild": "^0.21.3",
|
||||||
"postcss": "^8.4.43",
|
"postcss": "^8.4.43",
|
||||||
@ -2215,12 +2209,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vitefu": {
|
"node_modules/vitefu": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.6.tgz",
|
||||||
"integrity": "sha512-iKKfOMBHob2WxEJbqbJjHAkmYgvFDPhuqrO82om83S8RLk+17FtyMBfcyeH8GqD0ihShtkMW/zzJgiA51hCNCQ==",
|
"integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"tests/deps/*",
|
||||||
|
"tests/projects/*"
|
||||||
|
],
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0-beta.0"
|
"vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"vite": {
|
"vite": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "website",
|
"name": "website",
|
||||||
"version": "6.0",
|
"version": "6.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
@ -10,14 +10,14 @@
|
|||||||
"format": "prettier --write ."
|
"format": "prettier --write ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "^3.3.1",
|
"@sveltejs/adapter-auto": "^6.0.0",
|
||||||
"@sveltejs/adapter-node": "^5.2.11",
|
"@sveltejs/adapter-node": "^5.2.11",
|
||||||
"@sveltejs/kit": "^2.15.1",
|
"@sveltejs/kit": "^2.15.1",
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.3",
|
"@sveltejs/vite-plugin-svelte": "^4.0.3",
|
||||||
"prettier": "^3.4.2",
|
"prettier": "^3.4.2",
|
||||||
"prettier-plugin-svelte": "^3.3.2",
|
"prettier-plugin-svelte": "^3.3.2",
|
||||||
"svelte": "^5.16.0",
|
"svelte": "^5.16.0",
|
||||||
"vite": "^6.0.0"
|
"vite": "^5.4.11"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
|
import { browser } from "$app/environment";
|
||||||
import { urljoin } from "$lib/util.js";
|
import { urljoin } from "$lib/util.js";
|
||||||
|
|
||||||
const api_version = "v1";
|
const api_version = "v1";
|
||||||
const api_url = urljoin(import.meta.env.WEBSITE_API_URL, api_version);
|
|
||||||
|
|
||||||
function api_urljoin(path = null, query = {}) {
|
function api_urljoin(path = null, query = {}) {
|
||||||
|
let api_url = "";
|
||||||
|
|
||||||
|
if (browser) api_url = urljoin(import.meta.env.WEBSITE_API_PATH, api_version);
|
||||||
|
else api_url = urljoin(import.meta.env.WEBSITE_API_URL, api_version);
|
||||||
|
|
||||||
return urljoin(api_url, path, query);
|
return urljoin(api_url, path, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,43 +1,36 @@
|
|||||||
<script>
|
<script>
|
||||||
import { urljoin, color, date_from_ts } from "$lib/util.js";
|
import { color, date_from_ts } from "$lib/util.js";
|
||||||
import { api_get_metrics } from "$lib/api.js";
|
import { api_get_metrics } from "$lib/api.js";
|
||||||
import Link from "$lib/link.svelte";
|
import Link from "$lib/link.svelte";
|
||||||
|
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
|
|
||||||
let data = {};
|
let show_counter = false,
|
||||||
|
data = {};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
show_counter = true;
|
||||||
data = await api_get_metrics(fetch);
|
data = await api_get_metrics(fetch);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<footer style="border-top: solid 2px var(--{color()});">
|
<footer style="border-top: solid 2px var(--{color()});">
|
||||||
<div class="info">
|
<div class="links">
|
||||||
<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>
|
<span>
|
||||||
{$_("footer.powered")}
|
<Link link={import.meta.env.WEBSITE_SOURCE_URL} bold={true}>{$_("footer.source")}</Link>
|
||||||
|
</span>
|
||||||
|
<span>/</span>
|
||||||
|
<span>
|
||||||
|
<Link link="/doc/license" bold={true}>{$_("footer.license")}</Link>
|
||||||
|
</span>
|
||||||
|
<span>/</span>
|
||||||
|
<span>
|
||||||
|
<Link link="/doc/privacy" bold={true}>{$_("footer.privacy")}</Link>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="useless">
|
{#if show_counter}
|
||||||
<span>
|
<span class="counter">
|
||||||
{$_("footer.number", {
|
{$_("footer.number", {
|
||||||
values: {
|
values: {
|
||||||
total: data.total,
|
total: data.total,
|
||||||
@ -48,10 +41,9 @@
|
|||||||
<span style="color: var(--{color()})">({$_("footer.wow")})</span>
|
<span style="color: var(--{color()})">({$_("footer.wow")})</span>
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
{:else}
|
||||||
{$_("footer.version", { values: { api_version: "v1", frontend_version: pkg.version } })}
|
<span class="counter">{$_("footer.js")}</span>
|
||||||
</span>
|
{/if}
|
||||||
</div>
|
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@ -61,27 +53,29 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: var(--black-1);
|
background: var(--black-1);
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 20px 50px 20px 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div {
|
div {
|
||||||
display: flex;
|
display: flex;
|
||||||
color: var(--white-2);
|
|
||||||
font-size: var(--size-2);
|
font-size: var(--size-2);
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.useless {
|
span {
|
||||||
margin: 25px 50px 25px 0;
|
color: var(--white-2);
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counter {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.links {
|
||||||
margin: 25px 0 25px 50px;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
|
||||||
|
|
||||||
.info .links {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
|
@ -1,22 +1,24 @@
|
|||||||
<script>
|
<script>
|
||||||
import { api_urljoin } from "$lib/api.js";
|
import { api_urljoin } from "$lib/api.js";
|
||||||
import { app_url } from "$lib/util.js";
|
|
||||||
|
|
||||||
export let desc, title;
|
export let desc, title;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>[ngn.tf] | {title}</title>
|
<title>[ngn.tf] | {title}</title>
|
||||||
|
|
||||||
<meta content="[ngn.tf] | {title}" property="og:title" />
|
<meta name="description" content={desc} />
|
||||||
<meta content={desc} property="og:description" />
|
<meta name="author" content="ngn" />
|
||||||
<meta content={app_url()} property="og:url" />
|
<meta name="keywords" content="ngn,ngn13,ngn1,ngn.tf" />
|
||||||
<meta content="#000000" data-react-helmet="true" name="theme-color" />
|
<meta name="color-scheme" content="only dark" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
|
||||||
|
<meta property="og:title" content="[ngn.tf] | {title}" />
|
||||||
|
<meta property="og:description" content={desc} />
|
||||||
|
|
||||||
<link
|
<link
|
||||||
rel="alternate"
|
rel="alternate"
|
||||||
type="application/atom+xml"
|
type="application/atom+xml"
|
||||||
href={api_urljoin("/news/en")}
|
href={api_urljoin("/news/en")}
|
||||||
title="Atom Feed"
|
title="Service news and updates"
|
||||||
/>
|
/>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
@ -1,15 +1,46 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { browser } from "$app/environment";
|
||||||
import { color } from "$lib/util.js";
|
import { color } from "$lib/util.js";
|
||||||
|
import { onMount } from "svelte";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
|
|
||||||
export let picture = "";
|
export let picture = "";
|
||||||
export let title = "";
|
export let title = "";
|
||||||
|
|
||||||
|
let title_cur = title;
|
||||||
|
let show_animation = false;
|
||||||
|
|
||||||
|
function animate(title) {
|
||||||
|
if (!browser) return;
|
||||||
|
|
||||||
|
let id = window.setTimeout(function () {}, 0);
|
||||||
|
|
||||||
|
while (id--) clearTimeout(id);
|
||||||
|
|
||||||
|
title_cur = "";
|
||||||
|
|
||||||
|
for (let i = 0; i < title.length; i++) {
|
||||||
|
setTimeout(() => {
|
||||||
|
title_cur += title[i];
|
||||||
|
}, i * 70);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
show_animation = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
$: animate(title);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="title" style="color: var(--{color()})">{title.toLowerCase()}</h1>
|
{#if show_animation}
|
||||||
<h1 class="cursor" style="color: var(--{color()})">_</h1>
|
<h1 class="title" style="color: var(--{color()})">{title_cur}</h1>
|
||||||
|
<h1 class="cursor" style="color: var(--{color()})">_</h1>
|
||||||
|
{:else}
|
||||||
|
<h1 class="title" style="color: var(--{color()})">{title}</h1>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<img src="/profile/{picture}.png" alt="" />
|
<img src="/profile/{picture}.png" alt="" />
|
||||||
</header>
|
</header>
|
||||||
@ -50,9 +81,6 @@
|
|||||||
header div .title {
|
header div .title {
|
||||||
text-shadow: var(--text-shadow);
|
text-shadow: var(--text-shadow);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 0;
|
|
||||||
animation: typing 1s steps(20, end) forwards;
|
|
||||||
animation-delay: 0.3s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
header div .cursor {
|
header div .cursor {
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import { locale_list, locale_select, locale_index } from "$lib/locale.js";
|
import { locale_list, locale_select, locale_index } from "$lib/locale.js";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
let len = locale_list.length;
|
let len = locale_list.length;
|
||||||
|
let show = false;
|
||||||
|
|
||||||
function get_next(indx) {
|
function get_next(indx) {
|
||||||
let new_indx = 0;
|
let new_indx = 0;
|
||||||
@ -15,11 +17,17 @@
|
|||||||
function next() {
|
function next() {
|
||||||
locale_select(get_next($locale_index).code);
|
locale_select(get_next($locale_index).code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
show = true;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button on:click={next}>
|
{#if show}
|
||||||
{get_next($locale_index).icon}
|
<button on:click={next}>
|
||||||
</button>
|
{get_next($locale_index).icon}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
button {
|
button {
|
||||||
|
@ -19,28 +19,18 @@ function color() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function click() {
|
function click() {
|
||||||
let audio = new Audio("/click.wav");
|
let audio = new Audio("/assets/click.wav");
|
||||||
audio.play();
|
audio.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
function urljoin(url, path = null, query = {}) {
|
function urljoin(url, path = null) {
|
||||||
if (undefined === url || null === url) return;
|
if (undefined === url || null === url) return;
|
||||||
|
|
||||||
let url_len = url.length;
|
if (url[url.length - 1] != "/") url += "/";
|
||||||
|
|
||||||
if (url[url_len - 1] != "/") url += "/";
|
if (null === path || "" === path) return url;
|
||||||
|
if (path[0] === "/") return url + path.slice(1);
|
||||||
if (null === path || "" === path) url = new URL(url);
|
return url + path;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function app_url(path = null, query = {}) {
|
|
||||||
return urljoin(import.meta.env.WEBSITE_APP_URL, path, query);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function time_from_ts(ts) {
|
function time_from_ts(ts) {
|
||||||
@ -68,4 +58,4 @@ function date_from_ts(ts) {
|
|||||||
}).format(new Date(ts * 1000));
|
}).format(new Date(ts * 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
export { color, click, urljoin, app_url, time_from_ts, date_from_ts };
|
export { color, click, urljoin, time_from_ts, date_from_ts };
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"donate": "donate"
|
"donate": "donate"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"title": "Hello world!",
|
"title": "hello world!",
|
||||||
"welcome": {
|
"welcome": {
|
||||||
"title": "about",
|
"title": "about",
|
||||||
"desc": "Welcome to my website, I'm ngn",
|
"desc": "Welcome to my website, I'm ngn",
|
||||||
@ -29,9 +29,9 @@
|
|||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"title": "services",
|
"title": "services",
|
||||||
"desc": "A part from working on stupid shit, I host free (as in freedom, and price) services available for all",
|
"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",
|
"speed": "All of these services are available over an 1 Gbit interface",
|
||||||
"security": "All use SSL encrypted connection and they are all privacy-respecting",
|
"security": "All use SSL encrypted connection and they respect your privacy and freedom",
|
||||||
"privacy": "Accessible from clearnet, TOR and I2P, no region or network blocks",
|
"privacy": "Accessible from clearnet, TOR and I2P, no region or network blocks",
|
||||||
"bullshit": "No CDNs, no cloudflare, no CAPTCHA, no analytics, no bullshit",
|
"bullshit": "No CDNs, no cloudflare, no CAPTCHA, no analytics, no bullshit",
|
||||||
"link": "See all the services!"
|
"link": "See all the services!"
|
||||||
@ -42,7 +42,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"title": "Service Status",
|
"title": "service status",
|
||||||
"none": "No services found",
|
"none": "No services found",
|
||||||
"search": "Search for a service",
|
"search": "Search for a service",
|
||||||
"feed": "News and updates",
|
"feed": "News and updates",
|
||||||
@ -54,7 +54,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"donate": {
|
"donate": {
|
||||||
"title": "Donate Money!",
|
"title": "donate!",
|
||||||
"info": "I spend a lot of time and money on different projects and maintaining different services.",
|
"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).",
|
"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.",
|
"details": "So even a small donation would be useful. And it would help me keep everything up and running.",
|
||||||
@ -64,20 +64,16 @@
|
|||||||
"address": "Adress/Link"
|
"address": "Adress/Link"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"doc": {
|
|
||||||
"title": "Documentation"
|
|
||||||
},
|
|
||||||
"error": {
|
"error": {
|
||||||
"title": "Something went wrong!",
|
"title": "something went wrong!",
|
||||||
"report": "Report this issue"
|
"report": "Report this issue"
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"source": "Source",
|
"source": "Source",
|
||||||
"license": "License",
|
"license": "License",
|
||||||
"privacy": "Privacy",
|
"privacy": "Privacy",
|
||||||
"powered": "Powered by Svelte, Go, SQLite and donations",
|
|
||||||
"number": "Visited {total} times since {since}",
|
"number": "Visited {total} times since {since}",
|
||||||
"wow": "wow!!",
|
"wow": "wow!!",
|
||||||
"version": "Using API version {api_version}, frontend version {frontend_version}"
|
"js": "Enable javascript to display all the elements"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"donate": "bağış"
|
"donate": "bağış"
|
||||||
},
|
},
|
||||||
"home": {
|
"home": {
|
||||||
"title": "Merhaba Dünya!",
|
"title": "merhaba dünya!",
|
||||||
"welcome": {
|
"welcome": {
|
||||||
"title": "hakkımda",
|
"title": "hakkımda",
|
||||||
"desc": "Websiteme hoşgeldiniz, ben ngn",
|
"desc": "Websiteme hoşgeldiniz, ben ngn",
|
||||||
@ -30,11 +30,10 @@
|
|||||||
"services": {
|
"services": {
|
||||||
"title": "servisler",
|
"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",
|
"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",
|
"speed": "Tüm servisler 1 Gbit ağ arayüzü üzerinden erişilebilir",
|
||||||
"security": "Hepsi SSL şifreli bağlantı kullanıyor ve hepsi gizliğinize önem veriyor",
|
"security": "Hepsi SSL şifreli bağlantı kullanıyor ve hepsi gizliliğinize ve özgürlüğünüze saygı gösteriyor",
|
||||||
"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",
|
"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",
|
"bullshit": "CDN yok, cloudflare yok, CAPTCHA yok, analitikler ve diğer saçmalıklar yok",
|
||||||
"link": "Tüm servisleri incele!"
|
"link": "Tüm servisleri incele!"
|
||||||
},
|
},
|
||||||
"projects": {
|
"projects": {
|
||||||
@ -43,7 +42,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"title": "Servis Durumu",
|
"title": "servis durumu",
|
||||||
"none": "Servis bulunamadı",
|
"none": "Servis bulunamadı",
|
||||||
"search": "Bir servisi ara",
|
"search": "Bir servisi ara",
|
||||||
"feed": "Yenilikler ve güncellemeler",
|
"feed": "Yenilikler ve güncellemeler",
|
||||||
@ -55,7 +54,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"donate": {
|
"donate": {
|
||||||
"title": "Para Bağışla!",
|
"title": "bağış yap!",
|
||||||
"info": "Farklı projeler ve farklı servisleri yönetmek için oldukça zaman ve para harcıyorum.",
|
"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$).",
|
"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.",
|
"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.",
|
||||||
@ -65,20 +64,16 @@
|
|||||||
"address": "Adres/Bağlantı"
|
"address": "Adres/Bağlantı"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"doc": {
|
|
||||||
"title": "Dökümantasyon"
|
|
||||||
},
|
|
||||||
"error": {
|
"error": {
|
||||||
"title": "Birşeyler yanlış gitti!",
|
"title": "birşeyler yanlış gitti!",
|
||||||
"report": "Bu sorunu raporlayın"
|
"report": "Bu sorunu raporlayın"
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"source": "Kaynak",
|
"source": "Kaynak",
|
||||||
"license": "Lisans",
|
"license": "Lisans",
|
||||||
"privacy": "Gizlilik",
|
"privacy": "Gizlilik",
|
||||||
"powered": "Svelte, Go, SQLite ve bağışlar tarafından destekleniyor",
|
|
||||||
"number": "{since} tarihinden beri {total} kez ziyaret edildi",
|
"number": "{since} tarihinden beri {total} kez ziyaret edildi",
|
||||||
"wow": "vay be!!",
|
"wow": "vay be!!",
|
||||||
"version": "Kullan API versiyonu {api_version}, arayüz versiyonu {frontend_version}"
|
"js": "Tüm elementleri görüntelemek için javascript'i açın"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@import "/global.css";
|
@import "/css/global.css";
|
||||||
|
|
||||||
main {
|
main {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { api_version } from "$lib/api.js";
|
||||||
import Header from "$lib/header.svelte";
|
import Header from "$lib/header.svelte";
|
||||||
import Error from "$lib/error.svelte";
|
import Error from "$lib/error.svelte";
|
||||||
import Head from "$lib/head.svelte";
|
import Head from "$lib/head.svelte";
|
||||||
import Card from "$lib/card.svelte";
|
import Card from "$lib/card.svelte";
|
||||||
import Link from "$lib/link.svelte";
|
import Link from "$lib/link.svelte";
|
||||||
|
|
||||||
|
import { browser } from "$app/environment";
|
||||||
import { _, locale } from "svelte-i18n";
|
import { _, locale } from "svelte-i18n";
|
||||||
import { color } from "$lib/util.js";
|
import { color } from "$lib/util.js";
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
|
|
||||||
|
if (browser) {
|
||||||
|
window._version = {};
|
||||||
|
window._version.app = pkg.version;
|
||||||
|
window._version.api = api_version;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Head title="home" desc="home page of my personal website" />
|
<Head title="home" desc="home page of my personal website" />
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { doc_get_list, doc_get } from "$lib/doc";
|
import { doc_get } from "$lib/doc";
|
||||||
|
|
||||||
export async function load({ fetch, params }) {
|
export async function load({ fetch, params }) {
|
||||||
try {
|
try {
|
||||||
return {
|
return {
|
||||||
docs: await doc_get_list(fetch),
|
|
||||||
doc: await doc_get(fetch, params.name),
|
doc: await doc_get(fetch, params.name),
|
||||||
error: "",
|
error: "",
|
||||||
};
|
};
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Head title="documentation" desc="website and API documentation" />
|
<Head title="documentation" desc="website and API documentation" />
|
||||||
<Header picture="reader" title={$_("doc.title")} />
|
<Header picture="reader" title={data.doc[$locale].title} />
|
||||||
|
|
||||||
{#if data.error.length !== 0}
|
{#if data.error.length !== 0}
|
||||||
{#if !data.error.includes("not found")}
|
{#if !data.error.includes("not found")}
|
||||||
@ -30,94 +30,21 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<main>
|
<main>
|
||||||
{#if data.doc !== undefined}
|
<div class="markdown-body" style="--link-color: var(--{color()})">
|
||||||
<div class="markdown-body" style="--link-color: var(--{color()})">
|
{@html marked.parse(data.doc[$locale].content)}
|
||||||
{@html marked.parse(data.doc[$locale].content)}
|
</div>
|
||||||
</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>
|
</main>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@import "/markdown.css";
|
@import "/css/markdown.css";
|
||||||
|
|
||||||
main {
|
main {
|
||||||
padding: 50px;
|
padding: 50px;
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: start;
|
|
||||||
gap: 30px;
|
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) {
|
main .markdown-body :global(a) {
|
||||||
color: var(--link-color);
|
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>
|
</style>
|
||||||
|
@ -7,9 +7,11 @@
|
|||||||
|
|
||||||
import { api_urljoin } from "$lib/api.js";
|
import { api_urljoin } from "$lib/api.js";
|
||||||
import { locale, _ } from "svelte-i18n";
|
import { locale, _ } from "svelte-i18n";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
let { data } = $props();
|
let { data } = $props();
|
||||||
let services = $state(data.services);
|
let services = $state(data.services);
|
||||||
|
let show_input = $state(false);
|
||||||
|
|
||||||
function change(input) {
|
function change(input) {
|
||||||
let value = input.target.value.toLowerCase();
|
let value = input.target.value.toLowerCase();
|
||||||
@ -31,6 +33,10 @@
|
|||||||
return s.desc[$locale] !== "" && s.desc[$locale] !== null && s.desc[$locale] !== undefined;
|
return s.desc[$locale] !== "" && s.desc[$locale] !== null && s.desc[$locale] !== undefined;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
show_input = true;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Head title="services" desc="my self-hosted services and projects" />
|
<Head title="services" desc="my self-hosted services and projects" />
|
||||||
@ -41,7 +47,9 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<main>
|
<main>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<input oninput={change} type="text" placeholder={$_("services.search")} />
|
{#if show_input}
|
||||||
|
<input oninput={change} type="text" placeholder={$_("services.search")} />
|
||||||
|
{/if}
|
||||||
<div>
|
<div>
|
||||||
<Link icon="nf-fa-feed" link={api_urljoin("/news/" + $locale)}>{$_("services.feed")}</Link>
|
<Link icon="nf-fa-feed" link={api_urljoin("/news/" + $locale)}>{$_("services.feed")}</Link>
|
||||||
</div>
|
</div>
|
||||||
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
@ -9,13 +9,3 @@
|
|||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes typing {
|
|
||||||
from {
|
|
||||||
width: 0%;
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
7
app/static/css/font.css
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "Ubuntu";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("/assets/ubuntu.woff2") format("woff2");
|
||||||
|
}
|
@ -1,4 +1,12 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
* animations.css: stuff like the cursor animation
|
||||||
|
* webfont.css: webfont dumped from https://www.nerdfonts.com/assets/css/webfont.css
|
||||||
|
* font.css: the main font (Ubuntu)
|
||||||
|
|
||||||
|
*/
|
||||||
@import "./animations.css";
|
@import "./animations.css";
|
||||||
|
@import "./nerdfonts.css";
|
||||||
@import "./font.css";
|
@import "./font.css";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@ -31,7 +39,8 @@
|
|||||||
rgba(20, 20, 20, 0.3) 0px 18px 36px -18px inset;
|
rgba(20, 20, 20, 0.3) 0px 18px 36px -18px inset;
|
||||||
|
|
||||||
--text-shadow: 3px 2px 8px rgba(50, 50, 50, 0.8);
|
--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");
|
--background: linear-gradient(rgba(11, 11, 11, 0.808), rgba(1, 1, 1, 0.96)),
|
||||||
|
url("/assets/banner.png");
|
||||||
--profile-size: 220px;
|
--profile-size: 220px;
|
||||||
}
|
}
|
||||||
|
|
@ -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");
|
|
||||||
}
|
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 22 KiB |
3
app/static/robots.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
User-Agent: *
|
||||||
|
Disallow: /doc/
|
||||||
|
Disallow: /api/
|
@ -3,21 +3,38 @@ import { defineConfig } from "vite";
|
|||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
import { readFileSync } from "fs";
|
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 = {
|
const default_env = {
|
||||||
REPORT_URL: "https://github.com/ngn13/website/issues",
|
source_url: "https://git.ngn.tf/ngn/website",
|
||||||
SOURCE_URL: "https://github.com/ngn13/website",
|
report_url: "https://git.ngn.tf/ngn/website/issues",
|
||||||
APP_URL: "http://localhost:7001",
|
doc_url: "http://localhost:7003",
|
||||||
API_URL: "http://localhost:7002",
|
api: {
|
||||||
DOC_URL: "http://localhost:7003",
|
url: "http://localhost:7002",
|
||||||
|
path: "http://localhost:7002",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const file = fileURLToPath(new URL("package.json", import.meta.url));
|
const package_file = fileURLToPath(new URL("package.json", import.meta.url));
|
||||||
const json = readFileSync(file, "utf8");
|
const package_json = readFileSync(package_file, "utf8");
|
||||||
const pkg = JSON.parse(json);
|
const package_data = JSON.parse(package_json);
|
||||||
|
|
||||||
for (let env in default_env) {
|
env_from("WEBSITE", default_env);
|
||||||
if (process.env["WEBSITE_" + env] === undefined) process.env["WEBSITE_" + env] = default_env[env];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [sveltekit()],
|
plugins: [sveltekit()],
|
||||||
@ -31,6 +48,6 @@ export default defineConfig({
|
|||||||
strictPort: true,
|
strictPort: true,
|
||||||
},
|
},
|
||||||
define: {
|
define: {
|
||||||
pkg: pkg,
|
pkg: package_data,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Before Width: | Height: | Size: 156 KiB |
Before Width: | Height: | Size: 111 KiB |
@ -7,19 +7,20 @@ services:
|
|||||||
args:
|
args:
|
||||||
WEBSITE_SOURCE_URL: "http://github.com/ngn13/website"
|
WEBSITE_SOURCE_URL: "http://github.com/ngn13/website"
|
||||||
WEBSITE_REPORT_URL: "http://github.com/ngn13/website/issues"
|
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_DOC_URL: "http://doc:7003"
|
||||||
|
WEBSITE_API_URL: "http://api:7002"
|
||||||
|
WEBSITE_API_PATH: "http://localhost:7002"
|
||||||
security_opt:
|
security_opt:
|
||||||
- "no-new-privileges:true"
|
- "no-new-privileges:true"
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:7001:7001"
|
- "127.0.0.1:7001:7001"
|
||||||
restart: unless-stopped
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- api
|
- api
|
||||||
- doc
|
- doc
|
||||||
|
read_only: true
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
api:
|
api:
|
||||||
container_name: "website_api"
|
container_name: "website_api"
|
||||||
@ -34,28 +35,28 @@ services:
|
|||||||
- "127.0.0.1:7002:7002"
|
- "127.0.0.1:7002:7002"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data.db:/api/data.db:rw
|
- ./data.db:/api/data.db:rw
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
environment:
|
||||||
WEBSITE_DEBUG: "false"
|
WEBSITE_DEBUG: "false"
|
||||||
WEBSITE_APP_URL: "http://localhost:7001/"
|
WEBSITE_APP_URL: "http://localhost:7001"
|
||||||
WEBSITE_PASSWORD: "change_me"
|
WEBSITE_PASSWORD: "change_me"
|
||||||
WEBSITE_HOST: "0.0.0.0:7002"
|
WEBSITE_HOST: "0.0.0.0:7002"
|
||||||
WEBSITE_IP_HEADER: "X-Real-IP"
|
WEBSITE_IP_HEADER: "X-Real-IP"
|
||||||
WEBSITE_INTERVAL: "1h"
|
WEBSITE_INTERVAL: "1h"
|
||||||
WEBSITE_TIMEOUT: "15s"
|
WEBSITE_TIMEOUT: "15s"
|
||||||
WEBSITE_LIMIT: "5s"
|
WEBSITE_LIMIT: "5s"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
doc:
|
doc:
|
||||||
container_name: "website_doc"
|
container_name: "website_doc"
|
||||||
image: website_doc
|
image: website_doc
|
||||||
read_only: true
|
|
||||||
build:
|
build:
|
||||||
context: ./doc
|
context: ./doc
|
||||||
security_opt:
|
security_opt:
|
||||||
- "no-new-privileges:true"
|
- "no-new-privileges:true"
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
environment:
|
||||||
WEBSITE_HOST: "0.0.0.0:7003"
|
WEBSITE_HOST: "0.0.0.0:7003"
|
||||||
WEBSITE_DOCS_DIR: "./docs"
|
WEBSITE_DOCS_DIR: "./docs"
|
||||||
|
read_only: true
|
||||||
|
restart: unless-stopped
|
||||||
|
@ -81,7 +81,7 @@ BreakBeforeTernaryOperators: true
|
|||||||
BreakConstructorInitializers: BeforeColon
|
BreakConstructorInitializers: BeforeColon
|
||||||
BreakInheritanceList: BeforeColon
|
BreakInheritanceList: BeforeColon
|
||||||
BreakStringLiterals: true
|
BreakStringLiterals: true
|
||||||
ColumnLimit: 120
|
ColumnLimit: 80
|
||||||
CommentPragmas: '^ IWYU pragma:'
|
CommentPragmas: '^ IWYU pragma:'
|
||||||
CompactNamespaces: false
|
CompactNamespaces: false
|
||||||
ConstructorInitializerIndentWidth: 4
|
ConstructorInitializerIndentWidth: 4
|
||||||
|
35
doc/.clang-tidy
Normal 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
|
||||||
|
...
|
||||||
|
|
@ -1,9 +1,9 @@
|
|||||||
FROM ghcr.io/ngn13/ctorm:1.7
|
FROM ghcr.io/ngn13/ctorm:1.8.1
|
||||||
|
|
||||||
WORKDIR /doc
|
WORKDIR /doc
|
||||||
|
|
||||||
COPY Makefile ./
|
COPY Makefile ./
|
||||||
COPY docs ./docs
|
COPY pages ./pages
|
||||||
COPY inc ./inc
|
COPY inc ./inc
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
|
|
||||||
|
@ -26,10 +26,13 @@ $(DISTDIR)/%.o: src/%.c
|
|||||||
format:
|
format:
|
||||||
clang-format -i -style=file $(CSRCS) $(HSRCS)
|
clang-format -i -style=file $(CSRCS) $(HSRCS)
|
||||||
|
|
||||||
|
lint:
|
||||||
|
clang-tidy --warnings-as-errors --config= $(CSRCS) $(HSRCS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(DISTDIR)
|
rm -rf $(DISTDIR)
|
||||||
|
|
||||||
run:
|
run:
|
||||||
./doc.elf
|
./doc.elf
|
||||||
|
|
||||||
.PHONY: format clean run
|
.PHONY: format lint clean run
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"title": "Privacy",
|
|
||||||
"desc": "Learn how I respect your privacy"
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"title": "Gizlilik",
|
|
||||||
"desc": "Gizliliğinize nasıl önem verdiğimi öğrenin"
|
|
||||||
}
|
|
@ -3,13 +3,13 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
|
||||||
#include "util.h"
|
#include "file.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
DIR *dir;
|
DIR *dir;
|
||||||
util_file_t *file;
|
file_t *file;
|
||||||
char name[NAME_MAX + 1];
|
char name[NAME_MAX + 1];
|
||||||
char *lang;
|
char *lang;
|
||||||
} docs_t;
|
} docs_t;
|
||||||
|
|
||||||
bool docs_init(docs_t *docs, char *dir);
|
bool docs_init(docs_t *docs, char *dir);
|
||||||
|
10
doc/inc/file.h
Normal 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);
|
@ -4,16 +4,8 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
|
||||||
typedef struct {
|
#define util_toupper(str) \
|
||||||
char *content;
|
for (char *c = str; *c != 0; c++) \
|
||||||
uint64_t size;
|
|
||||||
} util_file_t;
|
|
||||||
|
|
||||||
#define util_toupper(str) \
|
|
||||||
for (char *c = str; *c != 0; c++) \
|
|
||||||
*c = toupper(*c)
|
*c = toupper(*c)
|
||||||
uint64_t util_endswith(char *str, char *suf);
|
uint64_t util_endswith(char *str, char *suf);
|
||||||
void util_send(ctorm_res_t *res, uint16_t code, cJSON *json);
|
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);
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"title": "API documentation",
|
"title": "API",
|
||||||
"desc": "Website's API documentation"
|
"desc": "Website's API documentation"
|
||||||
}
|
}
|
@ -1,8 +1,9 @@
|
|||||||
My website's API stores information about my self-hosted services, it also allows me to
|
My website's API, stores information about my self-hosted services, it also allows me
|
||||||
publish news and updates about these services using an Atom feed and it keeps track of
|
to publish news and updates about these services using an Atom feed and it keeps track
|
||||||
visitor metrics. The API itself is written in Go and uses SQLite for storage.
|
of visitor metrics.
|
||||||
|
|
||||||
This documentation contains information about all the available API endpoints.
|
This documentation contains information about all the available API endpoints. All the
|
||||||
|
endpoints can be accessed using the `/api` route.
|
||||||
|
|
||||||
## Version 1 Endpoints
|
## Version 1 Endpoints
|
||||||
Each version 1 endpoint, can be accessed using the `/v1` route.
|
Each version 1 endpoint, can be accessed using the `/v1` route.
|
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"title": "API dökümantasyonu",
|
"title": "API",
|
||||||
"desc": "Websitesinin API dökümantasyonu"
|
"desc": "Websitesinin API dökümantasyonu"
|
||||||
}
|
}
|
@ -1,8 +1,9 @@
|
|||||||
Websitemin API self-host edilen servisler hakkında bilgileri tutuyor, bu servisler hakkında
|
Websitemin API, self-host edilen servisler hakkında bilgileri tutuyor, bu servisler hakkında
|
||||||
haberleri ve güncellemeleri bir Atom feed'i aracılığı ile paylaşmama izin veriyor ve ziyartçi
|
haberleri ve güncellemeleri bir Atom feed'i aracılığı ile paylaşmama izin veriyor ve ziyartçi
|
||||||
metriklerini takip ediyor. API'ın kendisi Go ile yazıldı ve veritabanı olarak SQLite kullanıyor.
|
metriklerini takip ediyor.
|
||||||
|
|
||||||
Bu dökümentasyon tüm erişeme açık API endpoint'leri hakkında bilgiler içeriyor.
|
Bu dökümentasyon tüm erişime açık API endpoint'leri hakkında bilgiler içeriyor. Tüm endpoint'lere
|
||||||
|
`/api` yolu ile erişilebilir.
|
||||||
|
|
||||||
## Versyion 1 Endpoint'leri
|
## Versyion 1 Endpoint'leri
|
||||||
Tüm versiyon 1 endpoint'leri `/v1` yolu ile erişilebilir.
|
Tüm versiyon 1 endpoint'leri `/v1` yolu ile erişilebilir.
|
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"title": "License",
|
"title": "source license",
|
||||||
"desc": "Source code license"
|
"desc": "Source code license"
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"title": "Lisans",
|
"title": "kaynak lisansı",
|
||||||
"desc": "Kaynak kodu lisansı"
|
"desc": "Kaynak kodu lisansı"
|
||||||
}
|
}
|
4
doc/pages/privacy.en.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"title": "privacy",
|
||||||
|
"desc": "Privacy policy"
|
||||||
|
}
|
4
doc/pages/privacy.tr.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"title": "gizlilik",
|
||||||
|
"desc": "Gizlilik ilkesi"
|
||||||
|
}
|
@ -10,22 +10,26 @@
|
|||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
option_t options[] = {
|
option_t options[] = {
|
||||||
{"host", "0.0.0.0:7003", true }, // host the server should listen on
|
// name value requied
|
||||||
{"docs_dir", "./docs", true }, // documentation directory
|
{"host", "0.0.0.0:7003", true }, // host the server should listen on
|
||||||
{"", NULL, false},
|
{"dir", "./pages", true }, // documentation pages directory
|
||||||
|
{"", NULL, false},
|
||||||
};
|
};
|
||||||
|
|
||||||
bool config_load(config_t *conf) {
|
bool config_load(config_t *conf) {
|
||||||
bzero(conf, sizeof(*conf));
|
memset(conf, 0, sizeof(*conf));
|
||||||
|
|
||||||
char name_env[OPT_NAME_MAX + 10], name_copy[OPT_NAME_MAX], *value = NULL;
|
char name_env[OPT_NAME_MAX + 10], name_copy[OPT_NAME_MAX], *value = NULL;
|
||||||
conf->options = options;
|
conf->options = options;
|
||||||
|
|
||||||
for (option_t *opt = conf->options; opt->value != NULL; opt++, conf->count++) {
|
for (option_t *opt = conf->options; opt->value != NULL;
|
||||||
strcpy(name_copy, opt->name);
|
opt++, conf->count++) {
|
||||||
|
// convert option name to environment variable name
|
||||||
|
strncpy(name_copy, opt->name, OPT_NAME_MAX);
|
||||||
util_toupper(name_copy);
|
util_toupper(name_copy);
|
||||||
snprintf(name_env, sizeof(name_env), "WEBSITE_%s", name_copy);
|
snprintf(name_env, sizeof(name_env), "WEBSITE_%s", name_copy);
|
||||||
|
|
||||||
|
// attempt to load the value from the environment
|
||||||
if ((value = getenv(name_env)) != NULL)
|
if ((value = getenv(name_env)) != NULL)
|
||||||
opt->value = value;
|
opt->value = value;
|
||||||
|
|
||||||
@ -35,7 +39,9 @@ bool config_load(config_t *conf) {
|
|||||||
if (!opt->required || NULL != opt->value)
|
if (!opt->required || NULL != opt->value)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ctorm_fail("please specify a value for the required config option: %s (%s)", opt->name, name_env);
|
ctorm_fail("please specify a value for the required config option: %s (%s)",
|
||||||
|
opt->name,
|
||||||
|
name_env);
|
||||||
errno = EFAULT;
|
errno = EFAULT;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#include <linux/limits.h>
|
#include <linux/limits.h>
|
||||||
|
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
#define DOCS_LANG_CODE_LEN 2
|
#define DOCS_LANG_CODE_LEN 2
|
||||||
|
|
||||||
bool __docs_parse_name(docs_t *docs, char *ext) {
|
bool _docs_parse_name(docs_t *docs, char *ext) {
|
||||||
// check the extension
|
// check the extension
|
||||||
uint64_t ext_pos = util_endswith(docs->name, ext);
|
uint64_t ext_pos = util_endswith(docs->name, ext);
|
||||||
|
|
||||||
@ -22,7 +22,8 @@ bool __docs_parse_name(docs_t *docs, char *ext) {
|
|||||||
// example.en\0json\0
|
// example.en\0json\0
|
||||||
// |
|
// |
|
||||||
// `--- find this
|
// `--- find this
|
||||||
for (docs->lang = docs->name; *docs->lang != 0 && *docs->lang != '.'; docs->lang++)
|
for (docs->lang = docs->name; *docs->lang != 0 && *docs->lang != '.';
|
||||||
|
docs->lang++)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (*docs->lang != '.')
|
if (*docs->lang != '.')
|
||||||
@ -39,11 +40,11 @@ bool __docs_parse_name(docs_t *docs, char *ext) {
|
|||||||
return strlen(docs->lang) == DOCS_LANG_CODE_LEN && *docs->name != 0;
|
return strlen(docs->lang) == DOCS_LANG_CODE_LEN && *docs->name != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void __docs_clean(docs_t *docs) {
|
void _docs_clean(docs_t *docs) {
|
||||||
if (NULL == docs->file)
|
if (NULL == docs->file)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
util_file_free(docs->file);
|
file_free(docs->file);
|
||||||
docs->file = NULL;
|
docs->file = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ bool docs_init(docs_t *docs, char *dir) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bzero(docs, sizeof(*docs));
|
memset(docs, 0, sizeof(*docs));
|
||||||
return NULL != (docs->dir = opendir(dir));
|
return NULL != (docs->dir = opendir(dir));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,15 +65,15 @@ char *docs_next(docs_t *docs, char *name, bool content) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct dirent *ent = NULL;
|
struct dirent *ent = NULL;
|
||||||
__docs_clean(docs);
|
_docs_clean(docs);
|
||||||
|
|
||||||
while (NULL != (ent = readdir(docs->dir))) {
|
while (NULL != (ent = readdir(docs->dir))) {
|
||||||
if (*ent->d_name == '.')
|
if (*ent->d_name == '.')
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
strcpy(docs->name, ent->d_name);
|
strncpy(docs->name, ent->d_name, NAME_MAX);
|
||||||
|
|
||||||
if (!__docs_parse_name(docs, content ? ".md" : ".json"))
|
if (!_docs_parse_name(docs, content ? ".md" : ".json"))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (NULL == name || strncmp(docs->name, name, NAME_MAX) == 0)
|
if (NULL == name || strncmp(docs->name, name, NAME_MAX) == 0)
|
||||||
@ -84,7 +85,7 @@ char *docs_next(docs_t *docs, char *name, bool content) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NULL == (docs->file = util_file_load(dirfd(docs->dir), ent->d_name)))
|
if (NULL == (docs->file = file_load(dirfd(docs->dir), ent->d_name)))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
return docs->file->content;
|
return docs->file->content;
|
||||||
@ -99,8 +100,8 @@ void docs_free(docs_t *docs) {
|
|||||||
if (NULL == docs)
|
if (NULL == docs)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
__docs_clean(docs);
|
_docs_clean(docs);
|
||||||
closedir(docs->dir);
|
closedir(docs->dir);
|
||||||
|
|
||||||
bzero(docs, sizeof(*docs));
|
memset(docs, 0, sizeof(*docs));
|
||||||
}
|
}
|
||||||
|
56
doc/src/file.c
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include "file.h"
|
||||||
|
|
||||||
|
file_t *file_load(int dirfd, char *path) {
|
||||||
|
if (NULL == path) {
|
||||||
|
errno = EINVAL;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
file_t *file = NULL;
|
||||||
|
int fd = -1;
|
||||||
|
|
||||||
|
// open the taarget file
|
||||||
|
if ((fd = openat(dirfd, path, O_RDONLY)) < 0)
|
||||||
|
goto end; // errno set by open
|
||||||
|
|
||||||
|
// allocate a new file structure
|
||||||
|
if (NULL == (file = calloc(1, sizeof(file_t))))
|
||||||
|
goto end; // errno set by malloc
|
||||||
|
|
||||||
|
// calculate the file size
|
||||||
|
if ((file->size = lseek(fd, 0, SEEK_END)) < 0)
|
||||||
|
goto end;
|
||||||
|
|
||||||
|
// memory map the file
|
||||||
|
if (NULL ==
|
||||||
|
(file->content = mmap(0, file->size, PROT_READ, MAP_PRIVATE, fd, 0)))
|
||||||
|
goto end; // errno set by mmap
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (fd != -1)
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
if (NULL != file && NULL == file->content) {
|
||||||
|
free(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
void file_free(file_t *file) {
|
||||||
|
if (NULL == file)
|
||||||
|
return;
|
||||||
|
|
||||||
|
munmap(file->content, file->size);
|
||||||
|
free(file);
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
#include <ctorm/app.h>
|
||||||
#include <ctorm/ctorm.h>
|
#include <ctorm/ctorm.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
@ -13,28 +14,34 @@ int main() {
|
|||||||
|
|
||||||
if (!config_load(&conf))
|
if (!config_load(&conf))
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
host = config_get(&conf, "host");
|
|
||||||
|
|
||||||
|
if (NULL == (host = config_get(&conf, "host"))) {
|
||||||
|
ctorm_fail("failed to get the host configuration");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize the app config
|
||||||
ctorm_config_new(&app_config);
|
ctorm_config_new(&app_config);
|
||||||
app_config.disable_logging = true;
|
app_config.disable_logging = true;
|
||||||
app = ctorm_app_new(&app_config);
|
|
||||||
|
// create a new app
|
||||||
|
app = ctorm_app_new(&app_config);
|
||||||
|
|
||||||
// middlewares
|
// middlewares
|
||||||
MIDDLEWARE_ALL(app, "/*", route_cors);
|
ALL(app, "/*", route_cors);
|
||||||
MIDDLEWARE_ALL(app, "/*/*", route_cors);
|
ALL(app, "/*/*", route_cors);
|
||||||
MIDDLEWARE_ALL(app, "/*/*/*", route_cors);
|
|
||||||
|
|
||||||
// routes
|
// routes
|
||||||
GET(app, "/list", route_list);
|
GET(app, "/list", route_list);
|
||||||
GET(app, "/get/:name", route_get);
|
GET(app, "/get/:name", route_get);
|
||||||
|
|
||||||
ctorm_app_all(app, route_notfound);
|
ctorm_app_default(app, route_notfound);
|
||||||
ctorm_app_local(app, "config", &conf);
|
ctorm_app_local(app, "config", &conf);
|
||||||
|
|
||||||
ctorm_info("starting the web server on %s", host);
|
ctorm_info("starting the web server on %s", host);
|
||||||
|
|
||||||
if (!ctorm_app_run(app, host))
|
if (!ctorm_app_run(app, host))
|
||||||
ctorm_fail("failed to start the app: %s", ctorm_geterror());
|
ctorm_fail("failed to start the app: %s", ctorm_error());
|
||||||
|
|
||||||
ctorm_app_free(app);
|
ctorm_app_free(app);
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
void route_cors(ctorm_req_t *req, ctorm_res_t *res) {
|
void route_cors(ctorm_req_t *req, ctorm_res_t *res) {
|
||||||
RES_SET("Access-Control-Allow-Origin", "*");
|
RES_SET("Access-Control-Allow-Origin", "*");
|
||||||
RES_SET("Access-Control-Allow-Headers",
|
RES_SET("Access-Control-Allow-Headers",
|
||||||
"Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, "
|
"Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, "
|
||||||
|
"Authorization, accept, origin, Cache-Control, "
|
||||||
"X-Requested-With");
|
"X-Requested-With");
|
||||||
RES_SET("Access-Control-Allow-Methods", "PUT, DELETE, GET");
|
RES_SET("Access-Control-Allow-Methods", "PUT, DELETE, GET");
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,30 @@
|
|||||||
#include <cjson/cJSON.h>
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <linux/limits.h>
|
#include <linux/limits.h>
|
||||||
|
#include <cjson/cJSON.h>
|
||||||
#include <ctorm/ctorm.h>
|
#include <ctorm/ctorm.h>
|
||||||
|
|
||||||
#include <string.h>
|
#include <dirent.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "routes.h"
|
#include "routes.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "util.h"
|
||||||
#include "docs.h"
|
#include "docs.h"
|
||||||
|
|
||||||
void route_get(ctorm_req_t *req, ctorm_res_t *res) {
|
void route_get(ctorm_req_t *req, ctorm_res_t *res) {
|
||||||
config_t *conf = REQ_LOCAL("config");
|
config_t *conf = REQ_LOCAL("config");
|
||||||
char *doc_name = REQ_PARAM("name");
|
char *name = REQ_PARAM("name");
|
||||||
char *docs_dir = config_get(conf, "docs_dir"), *doc_data = NULL;
|
char *dir = config_get(conf, "dir"), *doc_data = NULL;
|
||||||
cJSON *json = NULL, *doc_json = NULL;
|
cJSON *json = NULL, *doc_json = NULL;
|
||||||
docs_t docs;
|
docs_t docs;
|
||||||
|
|
||||||
if (NULL == doc_name) {
|
if (NULL == name) {
|
||||||
ctorm_fail("documentation name not specified (how did that even happend)");
|
ctorm_fail("documentation name not specified (how did that even happend)");
|
||||||
util_send(res, 500, NULL);
|
util_send(res, 500, NULL);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!docs_init(&docs, docs_dir)) {
|
if (!docs_init(&docs, dir)) {
|
||||||
ctorm_fail("docs_init failed: %s", ctorm_geterror());
|
ctorm_fail("docs_init failed: %s", ctorm_error());
|
||||||
util_send(res, 500, NULL);
|
util_send(res, 500, NULL);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
@ -36,7 +35,7 @@ void route_get(ctorm_req_t *req, ctorm_res_t *res) {
|
|||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (NULL != (doc_data = docs_next(&docs, doc_name, false))) {
|
while (NULL != (doc_data = docs_next(&docs, name, false))) {
|
||||||
if (NULL == (doc_json = cJSON_Parse(doc_data))) {
|
if (NULL == (doc_json = cJSON_Parse(doc_data))) {
|
||||||
ctorm_fail("failed to parse JSON: %s (%s)", docs.name, docs.lang);
|
ctorm_fail("failed to parse JSON: %s (%s)", docs.name, docs.lang);
|
||||||
continue;
|
continue;
|
||||||
@ -53,7 +52,7 @@ void route_get(ctorm_req_t *req, ctorm_res_t *res) {
|
|||||||
|
|
||||||
docs_reset(&docs);
|
docs_reset(&docs);
|
||||||
|
|
||||||
while (NULL != (doc_data = docs_next(&docs, doc_name, true))) {
|
while (NULL != (doc_data = docs_next(&docs, name, true))) {
|
||||||
if (NULL == (doc_json = cJSON_GetObjectItem(json, docs.lang)))
|
if (NULL == (doc_json = cJSON_GetObjectItem(json, docs.lang)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -3,21 +3,21 @@
|
|||||||
#include <ctorm/ctorm.h>
|
#include <ctorm/ctorm.h>
|
||||||
|
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <string.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "routes.h"
|
#include "routes.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "util.h"
|
||||||
#include "docs.h"
|
#include "docs.h"
|
||||||
|
|
||||||
void route_list(ctorm_req_t *req, ctorm_res_t *res) {
|
void route_list(ctorm_req_t *req, ctorm_res_t *res) {
|
||||||
config_t *conf = REQ_LOCAL("config");
|
config_t *conf = REQ_LOCAL("config");
|
||||||
char *docs_dir = config_get(conf, "docs_dir"), *doc_data = NULL;
|
char *dir = config_get(conf, "dir"), *doc_data = NULL;
|
||||||
cJSON *array = NULL, *json = NULL, *doc_json = NULL;
|
cJSON *array = NULL, *json = NULL, *doc_json = NULL;
|
||||||
docs_t docs;
|
docs_t docs;
|
||||||
|
|
||||||
if (!docs_init(&docs, docs_dir)) {
|
if (!docs_init(&docs, dir)) {
|
||||||
ctorm_fail("docs_init failed: %s", ctorm_geterror());
|
ctorm_fail("docs_init failed: %s", ctorm_error());
|
||||||
util_send(res, 500, NULL);
|
util_send(res, 500, NULL);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
@ -31,7 +31,8 @@ void route_list(ctorm_req_t *req, ctorm_res_t *res) {
|
|||||||
while (NULL != (doc_data = docs_next(&docs, NULL, false))) {
|
while (NULL != (doc_data = docs_next(&docs, NULL, false))) {
|
||||||
if (NULL == (array = cJSON_GetObjectItem(json, docs.lang)) &&
|
if (NULL == (array = cJSON_GetObjectItem(json, docs.lang)) &&
|
||||||
NULL == (array = cJSON_AddArrayToObject(json, docs.lang))) {
|
NULL == (array = cJSON_AddArrayToObject(json, docs.lang))) {
|
||||||
ctorm_fail("failed to create an array object for the language %s", docs.lang);
|
ctorm_fail(
|
||||||
|
"failed to create an array object for the language %s", docs.lang);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,17 +1,8 @@
|
|||||||
#include <linux/limits.h>
|
#include <linux/limits.h>
|
||||||
#include <ctorm/ctorm.h>
|
#include <ctorm/ctorm.h>
|
||||||
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <dirent.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
@ -57,45 +48,3 @@ void util_send(ctorm_res_t *res, uint16_t code, cJSON *json) {
|
|||||||
|
|
||||||
RES_JSON(json);
|
RES_JSON(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
util_file_t *util_file_load(int dirfd, char *path) {
|
|
||||||
if (NULL == path) {
|
|
||||||
errno = EINVAL;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
util_file_t *file = NULL;
|
|
||||||
struct stat buf;
|
|
||||||
int fd = -1;
|
|
||||||
|
|
||||||
if (NULL == (file = malloc(sizeof(util_file_t))))
|
|
||||||
goto end; // errno set by malloc
|
|
||||||
|
|
||||||
if ((fd = openat(dirfd, path, O_RDONLY)) < 0)
|
|
||||||
goto end; // errno set by open
|
|
||||||
|
|
||||||
if (fstat(fd, &buf) < 0)
|
|
||||||
goto end; // errno set by fstat
|
|
||||||
|
|
||||||
if (NULL == (file->content = mmap(0, (file->size = buf.st_size), PROT_READ, MAP_PRIVATE, fd, 0)))
|
|
||||||
goto end; // errno set by mmap
|
|
||||||
|
|
||||||
end:
|
|
||||||
if (fd != -1)
|
|
||||||
close(fd);
|
|
||||||
|
|
||||||
if (NULL == file->content) {
|
|
||||||
free(file);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
void util_file_free(util_file_t *file) {
|
|
||||||
if (NULL == file)
|
|
||||||
return;
|
|
||||||
|
|
||||||
munmap(file->content, file->size);
|
|
||||||
free(file);
|
|
||||||
}
|
|
||||||
|