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
|
.scannerwork
|
||||||
Backend/logs
|
Backend/logs
|
||||||
.worktime.txt
|
.worktime.txt
|
||||||
|
|
||||||
|
*_templ.go
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
db
|
db
|
||||||
Dockerfile
|
Dockerfile
|
||||||
|
*_templ.go
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM --platform=$BUILDPLATFORM golang:alpine AS build
|
FROM --platform=$BUILDPLATFORM golang:alpine AS base
|
||||||
ARG TARGETOS
|
ARG TARGETOS
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ENV CGO_ENABLED=0 \
|
ENV CGO_ENABLED=0 \
|
||||||
GOOS=$TARGETOS \
|
GOOS=$TARGETOS \
|
||||||
GOARCH=$TARGETARCH
|
GOARCH=$TARGETARCH
|
||||||
|
|
||||||
|
FROM base AS fetch-stage
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY go.mod go.sum /app/
|
COPY go.mod go.sum /app/
|
||||||
RUN go mod download && go mod verify
|
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 .
|
RUN go build -o server .
|
||||||
|
|
||||||
FROM alpine:3.22
|
FROM alpine:3.22
|
||||||
|
|||||||
@@ -50,10 +50,12 @@ func main() {
|
|||||||
|
|
||||||
defer models.DB.(*sql.DB).Close()
|
defer models.DB.(*sql.DB).Close()
|
||||||
|
|
||||||
err = Migrate()
|
if helper.GetEnv("GO_ENV", "production") != "debug" {
|
||||||
if err != nil {
|
err = Migrate()
|
||||||
slog.Error("Failed to migrate the database to newest version", "Error", err)
|
if err != nil {
|
||||||
return
|
slog.Error("Failed to migrate the database to newest version", "Error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fs := http.FileServer(http.Dir("./static"))
|
fs := http.FileServer(http.Dir("./static"))
|
||||||
|
|||||||
@@ -1,163 +1,157 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@source "../templates/*.templ";
|
@source "../templates/*.templ";
|
||||||
@plugin "@iconify/tailwind4" {
|
@plugin "@iconify/tailwind4" {
|
||||||
scale: 1.25;
|
scale: 1.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--color-accent-50: #e7fdea;
|
--color-accent-50: #e7fdea;
|
||||||
--color-accent-100: #cbfbd1;
|
--color-accent-100: #cbfbd1;
|
||||||
--color-accent-200: #9cf7a8;
|
--color-accent-200: #9cf7a8;
|
||||||
--color-accent-300: #68f37a;
|
--color-accent-300: #68f37a;
|
||||||
--color-accent-400: #33ef4d;
|
--color-accent-400: #33ef4d;
|
||||||
--color-accent-500: #11db2d;
|
--color-accent-500: #11db2d;
|
||||||
--color-accent-600: #0eaf23;
|
--color-accent-600: #0eaf23;
|
||||||
--color-accent-700: #0a851b;
|
--color-accent-700: #0a851b;
|
||||||
--color-accent-800: #075a12;
|
--color-accent-800: #075a12;
|
||||||
--color-accent-900: #032b09;
|
--color-accent-900: #032b09;
|
||||||
--color-accent-950: #021805;
|
--color-accent-950: #021805;
|
||||||
--color-accent: #0eaf23;
|
--color-accent: #0eaf23;
|
||||||
--color-text-50: #f7f8f7;
|
--color-text-50: #f7f8f7;
|
||||||
--color-text-100: #f2f3f2;
|
--color-text-100: #f2f3f2;
|
||||||
--color-text-200: #e2e4e2;
|
--color-text-200: #e2e4e2;
|
||||||
--color-text-300: #d2d6d2;
|
--color-text-300: #d2d6d2;
|
||||||
--color-text-400: #c2c7c2;
|
--color-text-400: #c2c7c2;
|
||||||
--color-text-500: #afb6af;
|
--color-text-500: #afb6af;
|
||||||
--color-text-600: #97a097;
|
--color-text-600: #97a097;
|
||||||
--color-text-700: #7d877d;
|
--color-text-700: #7d877d;
|
||||||
--color-text-800: #5a625a;
|
--color-text-800: #5a625a;
|
||||||
--color-text-900: #161816;
|
--color-text-900: #161816;
|
||||||
--color-text-950: #000000;
|
--color-text-950: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
body {
|
body {
|
||||||
-webkit-print-color-adjust: exact !important;
|
-webkit-print-color-adjust: exact !important;
|
||||||
print-color-adjust: exact !important;
|
print-color-adjust: exact !important;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer components {
|
@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-main {
|
||||||
grid-template-columns: repeat(5, 1fr);
|
display: grid;
|
||||||
margin: 0 10%;
|
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 {
|
.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 {
|
.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) {
|
templ TeamPage(weeks []models.WorkWeek, userWeek models.WorkWeek) {
|
||||||
@Base()
|
@Base()
|
||||||
@headerComponent()
|
@headerComponent()
|
||||||
<div class="grid-main divide-y-1">
|
<div class="grid-main divide-y-1 @container">
|
||||||
<div class="grid-sub lg:divide-x-1 max-md:divide-y-1 responsive @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">
|
<div class="grid-cell col-span-full bg-neutral-300 lg:border-0">
|
||||||
<h2 class="text-xl uppercase font-bold">Eigene Abrechnung</h2>
|
<h2 class="text-xl uppercase font-bold">Eigene Abrechnung</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,7 +34,7 @@ templ workWeekComponent(week models.WorkWeek, onlyAccept bool) {
|
|||||||
year, kw := week.WeekStart.ISOWeek()
|
year, kw := week.WeekStart.ISOWeek()
|
||||||
progress := (float32(week.WorktimeVirtual.Hours()) / week.User.ArbeitszeitPerWoche) * 100
|
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">
|
<div class="grid-cell flex flex-col max-md:bg-neutral-300 gap-2">
|
||||||
if !onlyAccept {
|
if !onlyAccept {
|
||||||
<div class="lg:hidden">
|
<div class="lg:hidden">
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func TeamPage(weeks []models.WorkWeek, userWeek models.WorkWeek) templ.Component
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ func workWeekComponent(week models.WorkWeek, onlyAccept bool) templ.Component {
|
|||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
year, kw := week.WeekStart.ISOWeek()
|
year, kw := week.WeekStart.ISOWeek()
|
||||||
progress := (float32(week.WorktimeVirtual.Hours()) / week.User.ArbeitszeitPerWoche) * 100
|
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 {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
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
|
- ${POSTGRES_PATH}/initdb:/docker-entrypoint-initdb.d
|
||||||
ports:
|
ports:
|
||||||
- ${POSTGRES_PORT}:5432
|
- ${POSTGRES_PORT}:5432
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER}", "--dbname", "${POSTGRES_DB}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
image: git.letsstein.de/tom/arbeitszeitmessung-webserver
|
image: git.letsstein.de/tom/arbeitszeitmessung-webserver:dev
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
@@ -24,7 +29,8 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- ${WEB_PORT}:8080
|
- ${WEB_PORT}:8080
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
db:
|
||||||
|
condition: service_healthy
|
||||||
volumes:
|
volumes:
|
||||||
- ${LOG_PATH}:/app/logs
|
- ${LOG_PATH}:/app/logs
|
||||||
restart: unless-stopped
|
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
|
Antwort `409` Konflikt
|
||||||
Die vorherige Buchung am selben Tag hat den gleichen Buchungstyp
|
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