Compare commits
3 Commits
fdda0ea669
...
41c34c42cf
| Author | SHA1 | Date | |
|---|---|---|---|
| 41c34c42cf | |||
| 6998d07c6b | |||
| a5f5c37225 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -40,3 +40,5 @@ atlas.hcl
|
||||
.scannerwork
|
||||
Backend/logs
|
||||
.worktime.txt
|
||||
|
||||
*_templ.go
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
db
|
||||
Dockerfile
|
||||
*_templ.go
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM --platform=$BUILDPLATFORM golang:alpine AS build
|
||||
FROM --platform=$BUILDPLATFORM golang:alpine AS base
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ENV CGO_ENABLED=0 \
|
||||
GOOS=$TARGETOS \
|
||||
GOARCH=$TARGETARCH
|
||||
|
||||
FROM base AS fetch-stage
|
||||
WORKDIR /app
|
||||
COPY go.mod go.sum /app/
|
||||
RUN go mod download && go mod verify
|
||||
|
||||
COPY . .
|
||||
FROM ghcr.io/a-h/templ:latest AS generate-stage
|
||||
COPY --chown=65532:65532 . /app
|
||||
WORKDIR /app
|
||||
RUN ["templ", "generate"]
|
||||
|
||||
FROM base AS build
|
||||
COPY --from=generate-stage /app /app
|
||||
WORKDIR /app
|
||||
RUN go build -o server .
|
||||
|
||||
FROM alpine:3.22
|
||||
|
||||
@@ -50,10 +50,12 @@ func main() {
|
||||
|
||||
defer models.DB.(*sql.DB).Close()
|
||||
|
||||
err = Migrate()
|
||||
if err != nil {
|
||||
slog.Error("Failed to migrate the database to newest version", "Error", err)
|
||||
return
|
||||
if helper.GetEnv("GO_ENV", "production") != "debug" {
|
||||
err = Migrate()
|
||||
if err != nil {
|
||||
slog.Error("Failed to migrate the database to newest version", "Error", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fs := http.FileServer(http.Dir("./static"))
|
||||
|
||||
@@ -1,163 +1,157 @@
|
||||
@import "tailwindcss";
|
||||
@source "../templates/*.templ";
|
||||
@plugin "@iconify/tailwind4" {
|
||||
scale: 1.25;
|
||||
scale: 1.25;
|
||||
}
|
||||
|
||||
@theme {
|
||||
--color-accent-50: #e7fdea;
|
||||
--color-accent-100: #cbfbd1;
|
||||
--color-accent-200: #9cf7a8;
|
||||
--color-accent-300: #68f37a;
|
||||
--color-accent-400: #33ef4d;
|
||||
--color-accent-500: #11db2d;
|
||||
--color-accent-600: #0eaf23;
|
||||
--color-accent-700: #0a851b;
|
||||
--color-accent-800: #075a12;
|
||||
--color-accent-900: #032b09;
|
||||
--color-accent-950: #021805;
|
||||
--color-accent: #0eaf23;
|
||||
--color-text-50: #f7f8f7;
|
||||
--color-text-100: #f2f3f2;
|
||||
--color-text-200: #e2e4e2;
|
||||
--color-text-300: #d2d6d2;
|
||||
--color-text-400: #c2c7c2;
|
||||
--color-text-500: #afb6af;
|
||||
--color-text-600: #97a097;
|
||||
--color-text-700: #7d877d;
|
||||
--color-text-800: #5a625a;
|
||||
--color-text-900: #161816;
|
||||
--color-text-950: #000000;
|
||||
--color-accent-50: #e7fdea;
|
||||
--color-accent-100: #cbfbd1;
|
||||
--color-accent-200: #9cf7a8;
|
||||
--color-accent-300: #68f37a;
|
||||
--color-accent-400: #33ef4d;
|
||||
--color-accent-500: #11db2d;
|
||||
--color-accent-600: #0eaf23;
|
||||
--color-accent-700: #0a851b;
|
||||
--color-accent-800: #075a12;
|
||||
--color-accent-900: #032b09;
|
||||
--color-accent-950: #021805;
|
||||
--color-accent: #0eaf23;
|
||||
--color-text-50: #f7f8f7;
|
||||
--color-text-100: #f2f3f2;
|
||||
--color-text-200: #e2e4e2;
|
||||
--color-text-300: #d2d6d2;
|
||||
--color-text-400: #c2c7c2;
|
||||
--color-text-500: #afb6af;
|
||||
--color-text-600: #97a097;
|
||||
--color-text-700: #7d877d;
|
||||
--color-text-800: #5a625a;
|
||||
--color-text-900: #161816;
|
||||
--color-text-950: #000000;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
-webkit-print-color-adjust: exact !important;
|
||||
print-color-adjust: exact !important;
|
||||
background-color: white;
|
||||
}
|
||||
body {
|
||||
-webkit-print-color-adjust: exact !important;
|
||||
print-color-adjust: exact !important;
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.grid-main {
|
||||
display: grid;
|
||||
grid-template-columns: 4fr 3fr 3fr 1fr;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.grid-sub {
|
||||
display: grid;
|
||||
grid-template-columns: subgrid;
|
||||
grid-column: 1 / -1;
|
||||
border-color: var(--color-neutral-400);
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.grid-sub.responsive {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.grid-sub:hover {
|
||||
background-color: var(--color-neutral-200);
|
||||
}
|
||||
|
||||
.grid-cell {
|
||||
padding: calc(var(--spacing) * 2);
|
||||
border-color: var(--color-neutral-400);
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-neutral-800);
|
||||
font-size: var(--text-sm);
|
||||
text-align: center;
|
||||
padding: calc(var(--spacing) * 2);
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
border-color: var(--color-neutral-800);
|
||||
transition-property:
|
||||
color, background-color, border-color, outline-color,
|
||||
text-decoration-color, fill, stroke, --tw-gradient-from,
|
||||
--tw-gradient-via, --tw-gradient-to;
|
||||
transition-timing-function: var(
|
||||
--tw-ease,
|
||||
var(--default-transition-timing-function)
|
||||
);
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
}
|
||||
|
||||
input.btn,
|
||||
select.btn {
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
color: var(--color-white);
|
||||
background-color: var(--color-neutral-700);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 50%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
input.btn,
|
||||
select.btn {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
input.btn:hover,
|
||||
select.btn:hover {
|
||||
border-color: var(--color-neutral-300);
|
||||
background-color: var(--color-neutral-100);
|
||||
color: var(--color-neutral-800);
|
||||
}
|
||||
|
||||
.edit-box {
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
border-color: var(--color-neutral-500);
|
||||
transition-property: background-color, border-color;
|
||||
transition-timing-function: var(--default-transition-timing-function) * 2;
|
||||
transition-duration: var(--default-transition-duration);
|
||||
outline: none;
|
||||
|
||||
&:is(:where(.group):is(.edit) *) {
|
||||
border-width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-box:hover {
|
||||
&:is(:where(.group):is(.edit) *) {
|
||||
background-color: var(--color-white);
|
||||
border-color: var(--color-neutral-300);
|
||||
}
|
||||
}
|
||||
|
||||
.edit-box input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
div.edit {
|
||||
border-width: 1px;
|
||||
background-color: var(--color-neutral-300);
|
||||
}
|
||||
|
||||
@media (width >=48rem) {
|
||||
.grid-main {
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
margin: 0 10%;
|
||||
display: grid;
|
||||
grid-template-columns: 4fr 3fr 3fr 1fr;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.grid-sub {
|
||||
display: grid;
|
||||
grid-template-columns: subgrid;
|
||||
grid-column: 1 / -1;
|
||||
border-color: var(--color-neutral-400);
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.grid-sub.responsive {
|
||||
display: grid;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.grid-sub:hover {
|
||||
background-color: var(--color-neutral-200);
|
||||
}
|
||||
|
||||
.grid-cell {
|
||||
padding: calc(var(--spacing) * 2);
|
||||
border-color: var(--color-neutral-400);
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding-inline: calc(var(--spacing) * 4);
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-neutral-800);
|
||||
font-size: var(--text-sm);
|
||||
text-align: center;
|
||||
padding: calc(var(--spacing) * 2);
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
border-color: var(--color-neutral-800);
|
||||
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
}
|
||||
|
||||
input.btn,
|
||||
select.btn {
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
color: var(--color-white);
|
||||
background-color: var(--color-neutral-700);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 50%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
input.btn,
|
||||
select.btn {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
input.btn:hover,
|
||||
select.btn:hover {
|
||||
border-color: var(--color-neutral-300);
|
||||
background-color: var(--color-neutral-100);
|
||||
color: var(--color-neutral-800);
|
||||
}
|
||||
|
||||
.edit-box {
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
border-color: var(--color-neutral-500);
|
||||
transition-property: background-color, border-color;
|
||||
transition-timing-function: var(--default-transition-timing-function) * 2;
|
||||
transition-duration: var(--default-transition-duration);
|
||||
outline: none;
|
||||
|
||||
&:is(:where(.group):is(.edit) *) {
|
||||
border-width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-box:hover {
|
||||
&:is(:where(.group):is(.edit) *) {
|
||||
background-color: var(--color-white);
|
||||
border-color: var(--color-neutral-300);
|
||||
}
|
||||
}
|
||||
|
||||
.edit-box input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
div.edit {
|
||||
border-width: 1px;
|
||||
background-color: var(--color-neutral-300);
|
||||
}
|
||||
|
||||
@media (width >=48rem) {
|
||||
.grid-main {
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
margin: 0 10%;
|
||||
}
|
||||
|
||||
.grid-sub.responsive {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding-inline: calc(var(--spacing) * 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
templ TeamPage(weeks []models.WorkWeek, userWeek models.WorkWeek) {
|
||||
@Base()
|
||||
@headerComponent()
|
||||
<div class="grid-main divide-y-1">
|
||||
<div class="grid-sub lg:divide-x-1 max-md:divide-y-1 responsive @container">
|
||||
<div class="grid-main divide-y-1 @container">
|
||||
<div class="grid-sub lg:divide-x-1 max-md:divide-y-1 responsive">
|
||||
<div class="grid-cell col-span-full bg-neutral-300 lg:border-0">
|
||||
<h2 class="text-xl uppercase font-bold">Eigene Abrechnung</h2>
|
||||
</div>
|
||||
@@ -34,7 +34,7 @@ templ workWeekComponent(week models.WorkWeek, onlyAccept bool) {
|
||||
year, kw := week.WeekStart.ISOWeek()
|
||||
progress := (float32(week.WorktimeVirtual.Hours()) / week.User.ArbeitszeitPerWoche) * 100
|
||||
}}
|
||||
<div class="employeComponent grid-sub responsive lg:divide-x-1 max-md:divide-y-1 @container">
|
||||
<div class="employeComponent grid-sub responsive lg:divide-x-1 max-md:divide-y-1">
|
||||
<div class="grid-cell flex flex-col max-md:bg-neutral-300 gap-2">
|
||||
if !onlyAccept {
|
||||
<div class="lg:hidden">
|
||||
|
||||
@@ -45,7 +45,7 @@ func TeamPage(weeks []models.WorkWeek, userWeek models.WorkWeek) templ.Component
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"grid-main divide-y-1\"><div class=\"grid-sub lg:divide-x-1 max-md:divide-y-1 responsive @container\"><div class=\"grid-cell col-span-full bg-neutral-300 lg:border-0\"><h2 class=\"text-xl uppercase font-bold\">Eigene Abrechnung</h2></div></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"grid-main divide-y-1 @container\"><div class=\"grid-sub lg:divide-x-1 max-md:divide-y-1 responsive\"><div class=\"grid-cell col-span-full bg-neutral-300 lg:border-0\"><h2 class=\"text-xl uppercase font-bold\">Eigene Abrechnung</h2></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@@ -96,7 +96,7 @@ func workWeekComponent(week models.WorkWeek, onlyAccept bool) templ.Component {
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
year, kw := week.WeekStart.ISOWeek()
|
||||
progress := (float32(week.WorktimeVirtual.Hours()) / week.User.ArbeitszeitPerWoche) * 100
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"employeComponent grid-sub responsive lg:divide-x-1 max-md:divide-y-1 @container\"><div class=\"grid-cell flex flex-col max-md:bg-neutral-300 gap-2\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"employeComponent grid-sub responsive lg:divide-x-1 max-md:divide-y-1\"><div class=\"grid-cell flex flex-col max-md:bg-neutral-300 gap-2\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
56
DBB/initdb/01_create_user.sh
Executable file
56
DBB/initdb/01_create_user.sh
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/bin/bash
|
||||
set -e # Exit on error
|
||||
|
||||
echo "Creating PostgreSQL user and setting permissions... $POSTGRES_USER for API user $POSTGRES_API_USER"
|
||||
|
||||
|
||||
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||
CREATE ROLE migrate LOGIN ENCRYPTED PASSWORD '$POSTGRES_PASSWORD';
|
||||
GRANT USAGE, CREATE ON SCHEMA public TO migrate;
|
||||
GRANT CONNECT ON DATABASE arbeitszeitmessung TO migrate;
|
||||
EOSQL
|
||||
|
||||
# psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||
|
||||
# GRANT SELECT, INSERT, UPDATE ON anwesenheit, abwesenheit, user_password, wochen_report, s_feiertage TO $POSTGRES_API_USER;
|
||||
# GRANT DELETE ON abwesenheit TO $POSTGRES_API_USER;
|
||||
# GRANT SELECT ON s_personal_daten, s_abwesenheit_typen, s_anwesenheit_typen, s_feiertage TO $POSTGRES_API_USER;
|
||||
# GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO $POSTGRES_API_USER;
|
||||
# EOSQL
|
||||
|
||||
echo "User creation and permissions setup complete!"
|
||||
|
||||
|
||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||
|
||||
-- privilege roles
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'app_base') THEN
|
||||
CREATE ROLE app_base NOLOGIN;
|
||||
END IF;
|
||||
END
|
||||
\$\$;
|
||||
|
||||
-- dynamic login role
|
||||
DO \$\$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '$POSTGRES_API_USER') THEN
|
||||
CREATE ROLE $POSTGRES_API_USER
|
||||
LOGIN
|
||||
ENCRYPTED PASSWORD '$POSTGRES_API_PASS';
|
||||
END IF;
|
||||
END
|
||||
\$\$;
|
||||
|
||||
-- grant base privileges
|
||||
GRANT app_base TO $POSTGRES_API_USER;
|
||||
GRANT CONNECT ON DATABASE $POSTGRES_DB TO $POSTGRES_API_USER;
|
||||
GRANT USAGE ON SCHEMA public TO $POSTGRES_API_USER;
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
EOSQL
|
||||
|
||||
# psql -v ON_ERROR_STOP=1 --username root --dbname arbeitszeitmessung
|
||||
@@ -13,9 +13,14 @@ services:
|
||||
- ${POSTGRES_PATH}/initdb:/docker-entrypoint-initdb.d
|
||||
ports:
|
||||
- ${POSTGRES_PORT}:5432
|
||||
healthcheck:
|
||||
test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER}", "--dbname", "${POSTGRES_DB}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
backend:
|
||||
image: git.letsstein.de/tom/arbeitszeitmessung-webserver
|
||||
image: git.letsstein.de/tom/arbeitszeitmessung-webserver:dev
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
@@ -24,7 +29,8 @@ services:
|
||||
ports:
|
||||
- ${WEB_PORT}:8080
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ${LOG_PATH}:/app/logs
|
||||
restart: unless-stopped
|
||||
|
||||
66
Jenkinsfile
vendored
66
Jenkinsfile
vendored
@@ -1,66 +0,0 @@
|
||||
pipeline {
|
||||
environment {
|
||||
DOCKER_USERNAME = 'jenkins'
|
||||
DOCKER_PASSWORD = credentials('gitea_jenkins')
|
||||
SONAR_TOKEN = credentials('sonarcube_token')
|
||||
POSTGRES_USER = 'postgres'
|
||||
POSTGRES_PASSWORD = 'password'
|
||||
POSTGRES_DB = 'arbeitszeitmessung'
|
||||
}
|
||||
|
||||
agent any
|
||||
|
||||
stages {
|
||||
stage('Test') {
|
||||
agent {
|
||||
docker {
|
||||
image ''
|
||||
args ''
|
||||
args ''
|
||||
}
|
||||
}
|
||||
steps {
|
||||
script {
|
||||
sh '''
|
||||
docker run -d --rm \
|
||||
--name test-db \
|
||||
-e POSTGRES_USER={$POSTGRES_USER} \
|
||||
-e POSTGRES_PASSWORD={$POSTGRES_PASSWORD} \
|
||||
-e POSTGRES_DB={$POSTGRES_DB} \
|
||||
-v ./DB/initdb:/docker-entrypoint-initdb.d\
|
||||
-p "5432:5432" \
|
||||
postgres:16
|
||||
'''
|
||||
// docker.image('golang:1.24.5').withRun(
|
||||
// '-u root:root --network=host'
|
||||
// ) { go ->
|
||||
// // wait for DB to start
|
||||
// sh '''
|
||||
// cd Backend \
|
||||
// go mod download && go mod tidy \
|
||||
// go test ./... -v
|
||||
|
||||
// '''
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('SonarCube Analysis') {
|
||||
steps {
|
||||
sh 'make scan'
|
||||
}
|
||||
}
|
||||
stage('Building image arbeitszeit-backend') {
|
||||
when {
|
||||
anyOf {
|
||||
changeset 'Jenkinsfile'
|
||||
changeset 'Makefile'
|
||||
changeset 'Backend/**'
|
||||
}
|
||||
}
|
||||
steps {
|
||||
sh 'make backend'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Readme.md
26
Readme.md
@@ -120,3 +120,29 @@ Antwort `202` Akzeptiert und eingefügt
|
||||
|
||||
Antwort `409` Konflikt
|
||||
Die vorherige Buchung am selben Tag hat den gleichen Buchungstyp
|
||||
|
||||
# Filestrukture
|
||||
|
||||
```
|
||||
├── Backend (Webserver)
|
||||
│ ├── doc (Templates for Document Creator --> typst used to create PDF Reports)
|
||||
│ │ ├── static
|
||||
│ │ └── templates
|
||||
│ ├── endpoints (HTML Server endpoints (see main.go for Routes))
|
||||
│ ├── helper (Helper classes)
|
||||
│ │ ├── logs
|
||||
│ │ └── paramParser
|
||||
│ ├── logs (Log Folder, no sourcecode)
|
||||
│ ├── migrations (DB Migrations Folder, no direct sourcecode)
|
||||
│ ├── models (DB Models and their function)
|
||||
│ ├── src (Tailwind src --> used to config css formatter)
|
||||
│ ├── static (Webserver static, used to server static content, e.g. JS and CSS files)
|
||||
│ │ └── css
|
||||
│ └── templates (HTML Templates for every page written in templ and compiled to go)
|
||||
├── Cron (all Cron Scripts)
|
||||
├── DB (local Database mount Point)
|
||||
│ └── initdb (initialization scripts for DB)
|
||||
├── Docker (Docker Files, only docker-compose.yaml used)
|
||||
├── docs
|
||||
└── └── images
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user