Compare commits
7 Commits
2.0.0-rc.3
...
41c34c42cf
| Author | SHA1 | Date | |
|---|---|---|---|
| 41c34c42cf | |||
| 6998d07c6b | |||
| a5f5c37225 | |||
| fdda0ea669 | |||
| c10ab98997 | |||
| 8dc8c4eed3 | |||
| 3f49da49b6 |
@@ -6,6 +6,7 @@ on:
|
|||||||
- "*"
|
- "*"
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- dev/main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
webserver:
|
webserver:
|
||||||
|
|||||||
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,24 +1,34 @@
|
|||||||
# 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
|
||||||
RUN apk add --no-cache tzdata typst
|
RUN apk add --no-cache tzdata typst
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=build /app/server /app/server
|
COPY --from=build /app/server /app/server
|
||||||
COPY ./doc/static /doc/static
|
|
||||||
COPY ./doc/templates /doc/templates
|
COPY migrations /app/migrations
|
||||||
|
|
||||||
|
COPY doc /doc
|
||||||
|
|
||||||
COPY /static /app/static
|
COPY /static /app/static
|
||||||
ENTRYPOINT ["./server"]
|
ENTRYPOINT ["./server"]
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"github.com/golang-migrate/migrate/v4"
|
"github.com/golang-migrate/migrate/v4"
|
||||||
|
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||||
_ "github.com/golang-migrate/migrate/v4/database/postgres"
|
_ "github.com/golang-migrate/migrate/v4/database/postgres"
|
||||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
@@ -20,21 +21,34 @@ func OpenDatabase() (models.IDatabase, error) {
|
|||||||
dbPassword := helper.GetEnv("POSTGRES_API_PASS", "password")
|
dbPassword := helper.GetEnv("POSTGRES_API_PASS", "password")
|
||||||
dbTz := helper.GetEnv("TZ", "Europe/Berlin")
|
dbTz := helper.GetEnv("TZ", "Europe/Berlin")
|
||||||
|
|
||||||
connStr := fmt.Sprintf("postgres://%s:%s@%s:5432/%s?sslmode=disable&TimeZone=%s", dbUser, dbPassword, dbHost, dbName, dbTz)
|
connStr := fmt.Sprintf(
|
||||||
|
"host=%s user=%s dbname=%s password=%s sslmode=disable TimeZone=%s",
|
||||||
|
dbHost, dbUser, dbName, dbPassword, dbTz)
|
||||||
|
|
||||||
return sql.Open("postgres", connStr)
|
return sql.Open("postgres", connStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Migrate() error {
|
func Migrate() error {
|
||||||
dbHost := helper.GetEnv("POSTGRES_HOST", "localhost")
|
dbHost := helper.GetEnv("POSTGRES_HOST", "localhost")
|
||||||
dbName := helper.GetEnv("POSTGRES_DB", "arbeitszeitmessung")
|
dbName := helper.GetEnv("POSTGRES_DB", "arbeitszeitmessung")
|
||||||
// dbUser := helper.GetEnv("POSTGRES_USER", "api_nutzer")
|
|
||||||
dbPassword := helper.GetEnv("POSTGRES_PASSWORD", "password")
|
dbPassword := helper.GetEnv("POSTGRES_PASSWORD", "password")
|
||||||
dbTz := helper.GetEnv("TZ", "Europe/Berlin")
|
dbTz := helper.GetEnv("TZ", "Europe/Berlin")
|
||||||
|
|
||||||
migrations := helper.GetEnv("MIGRATIONS_PATH", "../migrations")
|
connStr := fmt.Sprintf(
|
||||||
|
"host=%s user=%s dbname=%s password=%s sslmode=disable TimeZone=%s",
|
||||||
|
dbHost, "migrate", dbName, dbPassword, dbTz)
|
||||||
|
|
||||||
connStr := fmt.Sprintf("postgres://%s:%s@%s:5432/%s?sslmode=disable&TimeZone=%s", "migrate", dbPassword, dbHost, dbName, dbTz)
|
db, err := sql.Open("postgres", connStr)
|
||||||
m, err := migrate.New(fmt.Sprintf("file://%s", migrations), connStr)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
driver, err := postgres.WithInstance(db, &postgres.Config{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := migrate.NewWithDatabaseInstance("file:///app/migrations", "postgres", driver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,11 +50,13 @@ func main() {
|
|||||||
|
|
||||||
defer models.DB.(*sql.DB).Close()
|
defer models.DB.(*sql.DB).Close()
|
||||||
|
|
||||||
|
if helper.GetEnv("GO_ENV", "production") != "debug" {
|
||||||
err = Migrate()
|
err = Migrate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("Failed to migrate the database to newest version", "Error", err)
|
slog.Error("Failed to migrate the database to newest version", "Error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fs := http.FileServer(http.Dir("./static"))
|
fs := http.FileServer(http.Dir("./static"))
|
||||||
endpoints.CreateSessionManager(24 * time.Hour)
|
endpoints.CreateSessionManager(24 * time.Hour)
|
||||||
|
|||||||
@@ -78,14 +78,8 @@
|
|||||||
border-style: var(--tw-border-style);
|
border-style: var(--tw-border-style);
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-color: var(--color-neutral-800);
|
border-color: var(--color-neutral-800);
|
||||||
transition-property:
|
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
|
||||||
color, background-color, border-color, outline-color,
|
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||||
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));
|
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -205,45 +205,24 @@
|
|||||||
.top-0 {
|
.top-0 {
|
||||||
top: calc(var(--spacing) * 0);
|
top: calc(var(--spacing) * 0);
|
||||||
}
|
}
|
||||||
.top-1 {
|
|
||||||
top: calc(var(--spacing) * 1);
|
|
||||||
}
|
|
||||||
.top-1\/2 {
|
.top-1\/2 {
|
||||||
top: calc(1/2 * 100%);
|
top: calc(1/2 * 100%);
|
||||||
}
|
}
|
||||||
.top-2 {
|
|
||||||
top: calc(var(--spacing) * 2);
|
|
||||||
}
|
|
||||||
.top-2\.5 {
|
.top-2\.5 {
|
||||||
top: calc(var(--spacing) * 2.5);
|
top: calc(var(--spacing) * 2.5);
|
||||||
}
|
}
|
||||||
.top-25 {
|
|
||||||
top: calc(var(--spacing) * 25);
|
|
||||||
}
|
|
||||||
.top-26 {
|
|
||||||
top: calc(var(--spacing) * 26);
|
|
||||||
}
|
|
||||||
.top-\[0\.125rem\] {
|
.top-\[0\.125rem\] {
|
||||||
top: 0.125rem;
|
top: 0.125rem;
|
||||||
}
|
}
|
||||||
.right-1 {
|
.right-1 {
|
||||||
right: calc(var(--spacing) * 1);
|
right: calc(var(--spacing) * 1);
|
||||||
}
|
}
|
||||||
.right-2 {
|
|
||||||
right: calc(var(--spacing) * 2);
|
|
||||||
}
|
|
||||||
.right-2\.5 {
|
.right-2\.5 {
|
||||||
right: calc(var(--spacing) * 2.5);
|
right: calc(var(--spacing) * 2.5);
|
||||||
}
|
}
|
||||||
.left-1 {
|
|
||||||
left: calc(var(--spacing) * 1);
|
|
||||||
}
|
|
||||||
.left-1\/2 {
|
.left-1\/2 {
|
||||||
left: calc(1/2 * 100%);
|
left: calc(1/2 * 100%);
|
||||||
}
|
}
|
||||||
.z-10 {
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
.z-100 {
|
.z-100 {
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
@@ -404,9 +383,6 @@
|
|||||||
.h-2 {
|
.h-2 {
|
||||||
height: calc(var(--spacing) * 2);
|
height: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
.h-3 {
|
|
||||||
height: calc(var(--spacing) * 3);
|
|
||||||
}
|
|
||||||
.h-3\.5 {
|
.h-3\.5 {
|
||||||
height: calc(var(--spacing) * 3.5);
|
height: calc(var(--spacing) * 3.5);
|
||||||
}
|
}
|
||||||
@@ -431,9 +407,6 @@
|
|||||||
.w-2 {
|
.w-2 {
|
||||||
width: calc(var(--spacing) * 2);
|
width: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
.w-3 {
|
|
||||||
width: calc(var(--spacing) * 3);
|
|
||||||
}
|
|
||||||
.w-3\.5 {
|
.w-3\.5 {
|
||||||
width: calc(var(--spacing) * 3.5);
|
width: calc(var(--spacing) * 3.5);
|
||||||
}
|
}
|
||||||
@@ -443,9 +416,6 @@
|
|||||||
.w-5 {
|
.w-5 {
|
||||||
width: calc(var(--spacing) * 5);
|
width: calc(var(--spacing) * 5);
|
||||||
}
|
}
|
||||||
.w-9 {
|
|
||||||
width: calc(var(--spacing) * 9);
|
|
||||||
}
|
|
||||||
.w-9\/10 {
|
.w-9\/10 {
|
||||||
width: calc(9/10 * 100%);
|
width: calc(9/10 * 100%);
|
||||||
}
|
}
|
||||||
@@ -458,9 +428,6 @@
|
|||||||
.w-full {
|
.w-full {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.flex-shrink {
|
|
||||||
flex-shrink: 1;
|
|
||||||
}
|
|
||||||
.flex-shrink-0 {
|
.flex-shrink-0 {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@@ -476,21 +443,10 @@
|
|||||||
.basis-\[content\] {
|
.basis-\[content\] {
|
||||||
flex-basis: content;
|
flex-basis: content;
|
||||||
}
|
}
|
||||||
.border-collapse {
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
.-translate-x-1 {
|
|
||||||
--tw-translate-x: calc(var(--spacing) * -1);
|
|
||||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
||||||
}
|
|
||||||
.-translate-x-1\/2 {
|
.-translate-x-1\/2 {
|
||||||
--tw-translate-x: calc(calc(1/2 * 100%) * -1);
|
--tw-translate-x: calc(calc(1/2 * 100%) * -1);
|
||||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||||
}
|
}
|
||||||
.-translate-y-1 {
|
|
||||||
--tw-translate-y: calc(var(--spacing) * -1);
|
|
||||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
||||||
}
|
|
||||||
.-translate-y-1\/2 {
|
.-translate-y-1\/2 {
|
||||||
--tw-translate-y: calc(calc(1/2 * 100%) * -1);
|
--tw-translate-y: calc(calc(1/2 * 100%) * -1);
|
||||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||||
@@ -501,9 +457,6 @@
|
|||||||
.cursor-pointer {
|
.cursor-pointer {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.resize {
|
|
||||||
resize: both;
|
|
||||||
}
|
|
||||||
.scroll-m-2 {
|
.scroll-m-2 {
|
||||||
scroll-margin: calc(var(--spacing) * 2);
|
scroll-margin: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
@@ -661,9 +614,6 @@
|
|||||||
.bg-red-600 {
|
.bg-red-600 {
|
||||||
background-color: var(--color-red-600);
|
background-color: var(--color-red-600);
|
||||||
}
|
}
|
||||||
.mask-repeat {
|
|
||||||
mask-repeat: repeat;
|
|
||||||
}
|
|
||||||
.p-1 {
|
.p-1 {
|
||||||
padding: calc(var(--spacing) * 1);
|
padding: calc(var(--spacing) * 1);
|
||||||
}
|
}
|
||||||
@@ -740,16 +690,9 @@
|
|||||||
.uppercase {
|
.uppercase {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
.underline {
|
|
||||||
text-decoration-line: underline;
|
|
||||||
}
|
|
||||||
.opacity-0 {
|
.opacity-0 {
|
||||||
opacity: 0%;
|
opacity: 0%;
|
||||||
}
|
}
|
||||||
.outline {
|
|
||||||
outline-style: var(--tw-outline-style);
|
|
||||||
outline-width: 1px;
|
|
||||||
}
|
|
||||||
.filter {
|
.filter {
|
||||||
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||||
}
|
}
|
||||||
@@ -1195,11 +1138,6 @@
|
|||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
inherits: false;
|
||||||
}
|
}
|
||||||
@property --tw-outline-style {
|
|
||||||
syntax: "*";
|
|
||||||
inherits: false;
|
|
||||||
initial-value: solid;
|
|
||||||
}
|
|
||||||
@property --tw-blur {
|
@property --tw-blur {
|
||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
inherits: false;
|
||||||
@@ -1272,7 +1210,6 @@
|
|||||||
--tw-border-style: solid;
|
--tw-border-style: solid;
|
||||||
--tw-divide-y-reverse: 0;
|
--tw-divide-y-reverse: 0;
|
||||||
--tw-font-weight: initial;
|
--tw-font-weight: initial;
|
||||||
--tw-outline-style: solid;
|
|
||||||
--tw-blur: initial;
|
--tw-blur: initial;
|
||||||
--tw-brightness: initial;
|
--tw-brightness: initial;
|
||||||
--tw-contrast: initial;
|
--tw-contrast: initial;
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ echo "Creating PostgreSQL user and setting permissions... $POSTGRES_USER for API
|
|||||||
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||||
CREATE ROLE migrate LOGIN ENCRYPTED PASSWORD '$POSTGRES_PASSWORD';
|
CREATE ROLE migrate LOGIN ENCRYPTED PASSWORD '$POSTGRES_PASSWORD';
|
||||||
GRANT USAGE, CREATE ON SCHEMA public TO migrate;
|
GRANT USAGE, CREATE ON SCHEMA public TO migrate;
|
||||||
|
GRANT CONNECT ON DATABASE arbeitszeitmessung TO migrate;
|
||||||
EOSQL
|
EOSQL
|
||||||
|
|
||||||
# psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
# psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||||
|
|||||||
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,12 +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
|
||||||
|
|
||||||
# document-creator:
|
|
||||||
# image: git.letsstein.de/tom/arbeitszeitmessung-doc-creator
|
|
||||||
# container_name: ${TYPST_CONTAINER}
|
|
||||||
# restart: unless-stopped
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ POSTGRES_API_PASS=password # Postgres API Passwort (fü
|
|||||||
POSTGRES_PATH=__ROOT__/DB # Datebank Pfad (relativ zu Docker Ordner oder absoluter pfad mit /...)
|
POSTGRES_PATH=__ROOT__/DB # Datebank Pfad (relativ zu Docker Ordner oder absoluter pfad mit /...)
|
||||||
POSTGRES_DB=arbeitszeitmessung # Postgres Datenbank Name
|
POSTGRES_DB=arbeitszeitmessung # Postgres Datenbank Name
|
||||||
POSTGRES_PORT=127.0.0.1:5432 # Postgres Port normalerweise nicht freigegeben. regex:^[0-9]{1,5}$
|
POSTGRES_PORT=127.0.0.1:5432 # Postgres Port normalerweise nicht freigegeben. regex:^[0-9]{1,5}$
|
||||||
MIGRATIONS_PATH=__ROOT__/migrations # Pfad zu DB migrations (wenn nicht verändert wurde, bei default bleiben)
|
|
||||||
TZ=Europe/Berlin # Zeitzone
|
TZ=Europe/Berlin # Zeitzone
|
||||||
API_TOKEN=dont_access # API Token für ESP Endpoints
|
API_TOKEN=dont_access # API Token für ESP Endpoints
|
||||||
WEB_PORT=8000 # Port unter welchem Webserver erreichbar ist. regex:^[0-9]{1,5}$
|
WEB_PORT=8000 # Port unter welchem Webserver erreichbar ist. regex:^[0-9]{1,5}$
|
||||||
|
|||||||
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'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2
Makefile
2
Makefile
@@ -44,7 +44,7 @@ generateFrontend:
|
|||||||
|
|
||||||
|
|
||||||
backend: generateFrontend login_registry
|
backend: generateFrontend login_registry
|
||||||
docker buildx build --platform linux/amd64,linux/arm64 -t ${IMAGE_REGISTRY}/${PACKAGE_OWNER}/arbeitszeitmessung:latest Backend --push
|
docker buildx build --platform linux/amd64,linux/arm64 -t ${IMAGE_REGISTRY}/${PACKAGE_OWNER}/arbeitszeitmessung-webserver:dev Backend --push
|
||||||
# docker buildx build --platform linux/amd64,linux/arm64 -t ${IMAGE_REGISTRY}/${PACKAGE_OWNER}/arbeitszeitmessung:${GIT_COMMIT} Backend //--push
|
# docker buildx build --platform linux/amd64,linux/arm64 -t ${IMAGE_REGISTRY}/${PACKAGE_OWNER}/arbeitszeitmessung:${GIT_COMMIT} Backend //--push
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
|||||||
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
|
||||||
|
```
|
||||||
|
|||||||
@@ -123,9 +123,8 @@ echo "Created logs folder at $LOG_PATH"
|
|||||||
|
|
||||||
echo -e "\n\n"
|
echo -e "\n\n"
|
||||||
echo "Start containers with docker compose up -d? [y/N]"
|
echo "Start containers with docker compose up -d? [y/N]"
|
||||||
read -r start_containersmkdi
|
read -r start_containers
|
||||||
if [[ "$start_containers" =~ ^[Yy]$ ]]; then
|
if [[ "$start_containers" =~ ^[Yy]$ ]]; then
|
||||||
|
|
||||||
cd Docker
|
cd Docker
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
echo "Containers started."
|
echo "Containers started."
|
||||||
@@ -172,6 +171,7 @@ if [[ "$setup_cron" =~ ^[Yy]$ ]]; then
|
|||||||
|
|
||||||
( crontab -l ; echo "$cron_timing $(pwd)/$file" )| awk '!x[$0]++' | crontab -
|
( crontab -l ; echo "$cron_timing $(pwd)/$file" )| awk '!x[$0]++' | crontab -
|
||||||
echo "Added entry to crontab: $cron_timing $(pwd)/$file."
|
echo "Added entry to crontab: $cron_timing $(pwd)/$file."
|
||||||
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
if systemctl is-active --quiet cron.service ; then
|
if systemctl is-active --quiet cron.service ; then
|
||||||
|
|||||||
Reference in New Issue
Block a user