5 Commits

Author SHA1 Message Date
tom
3be2a47c56 CHANGE: user login style 2025-02-21 20:44:35 +01:00
tom
f6e77bda66 CHANGE: postgres init db 2025-02-21 20:44:20 +01:00
tom
5fb6254c04 CHANGE: Multiarch deployment 2025-02-21 20:43:54 +01:00
tom
f2a5e9874d CHANGE: updated to use personal number as login 2025-02-21 15:58:34 +01:00
tom
32a4f5277a CHANGE: added db initialization script 2025-02-21 14:19:44 +01:00
17 changed files with 230 additions and 71 deletions

View File

@@ -1,12 +1,21 @@
FROM golang:alpine
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM golang:alpine AS build
ARG TARGETOS
ARG TARGETARCH
ENV CGO_ENABLED=0 \
GOOS=$TARGETOS \
GOARCH=$TARGETARCH
WORKDIR /app
COPY go.mod go.sum /app/
RUN go mod download && go mod verify
COPY . .
RUN go build -o /usr/local/bin/app
RUN go build -o server .
CMD ["app"]
FROM alpine
WORKDIR /app
COPY --from=build /app/server /app/server
COPY /static /app/static
ENTRYPOINT ["./server"]

View File

@@ -43,14 +43,19 @@ func parseTimestamp(r *http.Request , get_key string, fallback string) (time.Tim
// Returns bookings from DB with similar card uid -> checks for card uid in http query params
func getBookings(w http.ResponseWriter, r *http.Request) {
// if(!Session.Exists(r.Context(), "user")){
// log.Println("No user in session storage!")
// http.Error(w, "Not logged in!", http.StatusForbidden)
// return
// }
var user models.User
var err error
if(helper.GetEnv("GO_ENV", "production") == "debug"){
user, err = (*models.User).GetByPersonalNummer(nil, 123)
}else{
if(!Session.Exists(r.Context(), "user")){
log.Println("No user in session storage!")
http.Error(w, "Not logged in!", http.StatusForbidden)
return
}
// user, err := (*models.User).GetByPersonalNummer(nil, Session.GetInt(r.Context(), "user"))
user, err := (*models.User).GetByPersonalNummer(nil, 123)
user, err = (*models.User).GetByPersonalNummer(nil, Session.GetInt(r.Context(), "user"))
}
if(err != nil){
log.Println("No user found with the given personal number!")
http.Error(w, "No user found", http.StatusNotFound)

View File

@@ -5,6 +5,7 @@ import (
"arbeitszeitmessung/templates"
"log"
"net/http"
"strconv"
"time"
"github.com/alexedwards/scs/v2"
@@ -20,18 +21,18 @@ func CreateSessionManager(lifetime time.Duration) *scs.SessionManager {
func LoginHandler(w http.ResponseWriter, r *http.Request){
switch r.Method{
case http.MethodGet: showForm(w, r)
case http.MethodGet: showForm(w, r, false)
break
case http.MethodPost: loginUser(w, r)
break
default:
showForm(w, r)
showForm(w, r, false)
break
}
}
func showForm(w http.ResponseWriter, r *http.Request){
templates.LoginForm().Render(r.Context(), w)
func showForm(w http.ResponseWriter, r *http.Request, failed bool){
templates.LoginForm(failed).Render(r.Context(), w)
}
func loginUser(w http.ResponseWriter, r *http.Request){
@@ -39,15 +40,24 @@ func loginUser(w http.ResponseWriter, r *http.Request){
if err != nil {
log.Println("Error parsing form!", err)
http.Error(w, "Internal error", http.StatusBadRequest)
return
}
card_uid := r.FormValue("card_uid")
if(card_uid == ""){
log.Println("No card_uid provided!")
http.Error(w, "No card_uid provided", http.StatusBadRequest)
_personal_nummer := r.FormValue("personal_nummer")
if(_personal_nummer == ""){
log.Println("No personal_nummer provided!")
http.Error(w, "No personal_nummer provided", http.StatusBadRequest)
return
}
user, err := (*models.User).GetByCardUID(nil, card_uid)
personal_nummer, err := strconv.Atoi(_personal_nummer)
if(err != nil){
log.Println("No user found under this card_uid!")
log.Println("Cannot parse personal nubmer!")
http.Error(w, "Cannot parse number", http.StatusBadRequest)
return
}
user, err := (*models.User).GetByPersonalNummer(nil, personal_nummer)
if(err != nil){
log.Println("No user found under this personal number!")
http.Error(w, "No user found!", http.StatusNotFound)
}
@@ -56,7 +66,9 @@ func loginUser(w http.ResponseWriter, r *http.Request){
log.Printf("New succesfull user login from %s %s!\n", user.Vorname, user.Name)
Session.Put(r.Context(), "user", user.PersonalNummer)
http.Redirect(w, r, "/time", http.StatusSeeOther) //with this browser always uses GET
}else{
showForm(w, r, true)
}
showForm(w, r)
showForm(w, r, false)
}

View File

@@ -8,4 +8,6 @@ require github.com/lib/pq v1.10.9
require github.com/a-h/templ v0.3.833
require github.com/alexedwards/scs/v2 v2.8.0 // indirect
require github.com/alexedwards/scs/v2 v2.8.0
require github.com/joho/godotenv v1.5.1

View File

@@ -4,5 +4,7 @@ github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZx
github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=

View File

@@ -10,6 +10,7 @@ import (
"net/http"
"time"
"github.com/joho/godotenv"
_ "github.com/lib/pq"
)
@@ -17,6 +18,12 @@ import (
func main() {
var err error
err = godotenv.Load(".env")
if err != nil {
log.Println("No .env file found in directory!")
}
models.DB, err = OpenDatabase()
if err != nil {
log.Fatal(err)

View File

@@ -9,12 +9,11 @@ type User struct {
CardUID string `json:"card_uid"`
Name string `json:"name"`
Vorname string `json:"vorname"`
HauptbeschaeftigungsOrt int8 `json:"hauptbeschaeftigungsort"`
PersonalNummer int `json:"personal_nummer"`
}
func (u *User) GetAll() ([]User, error) {
qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname, hauptbeschaeftigung_ort FROM personal_daten;`))
qStr, err := DB.Prepare((`SELECT card_uid, vorname, nachname FROM personal_daten;`))
var users []User
if err != nil {
fmt.Printf("Error preparing query statement %v\n", err)
@@ -28,7 +27,7 @@ func (u *User) GetAll() ([]User, error) {
defer rows.Close()
for rows.Next() {
var user User
if err := rows.Scan(&user.CardUID, &user.Vorname, &user.Name, &user.HauptbeschaeftigungsOrt); err != nil {
if err := rows.Scan(&user.CardUID, &user.Vorname, &user.Name); err != nil {
return users, nil
}
users = append(users, user)
@@ -69,11 +68,11 @@ func (u *User) Logout() error {
func (u *User) GetByCardUID(card_uid string) (User, error) {
var user User
qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname, hauptbeschaeftigung_ort FROM personal_daten WHERE card_uid = $1;`))
qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname FROM personal_daten WHERE card_uid = $1;`))
if err != nil {
return user, err
}
err = qStr.QueryRow(card_uid).Scan(&user.PersonalNummer, &user.CardUID, &user.Vorname, &user.Name, &user.HauptbeschaeftigungsOrt)
err = qStr.QueryRow(card_uid).Scan(&user.PersonalNummer, &user.CardUID, &user.Vorname, &user.Name)
if err != nil{
return user, err
}
@@ -83,11 +82,11 @@ func (u *User) GetByCardUID(card_uid string) (User, error) {
func (u *User) GetByPersonalNummer (personalNummer int) (User, error) {
var user User
qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname, hauptbeschaeftigung_ort FROM personal_daten WHERE personal_nummer = $1;`))
qStr, err := DB.Prepare((`SELECT personal_nummer, card_uid, vorname, nachname FROM personal_daten WHERE personal_nummer = $1;`))
if err != nil {
return user, err
}
err = qStr.QueryRow(personalNummer).Scan(&user.PersonalNummer, &user.CardUID, &user.Vorname, &user.Name, &user.HauptbeschaeftigungsOrt)
err = qStr.QueryRow(personalNummer).Scan(&user.PersonalNummer, &user.CardUID, &user.Vorname, &user.Name)
if err != nil{
return user, err
}

View File

@@ -600,9 +600,21 @@
.h-4 {
height: calc(var(--spacing) * 4);
}
.h-\[90vh\] {
height: 90vh;
}
.h-\[100vh\] {
height: 100vh;
}
.h-full {
height: 100%;
}
.w-1 {
width: calc(var(--spacing) * 1);
}
.w-1\/2 {
width: calc(1/2 * 100%);
}
.w-2 {
width: calc(var(--spacing) * 2);
}
@@ -702,18 +714,24 @@
.border-neutral-200 {
border-color: var(--color-neutral-200);
}
.border-neutral-300 {
border-color: var(--color-neutral-300);
}
.border-neutral-400 {
border-color: var(--color-neutral-400);
}
.border-neutral-900 {
border-color: var(--color-neutral-900);
}
.border-red-600 {
border-color: var(--color-red-600);
}
.bg-accent {
background-color: var(--color-accent);
}
.bg-neutral-100 {
background-color: var(--color-neutral-100);
}
.bg-neutral-200 {
background-color: var(--color-neutral-200);
}
.bg-neutral-300 {
background-color: var(--color-neutral-300);
}
@@ -726,6 +744,9 @@
.bg-purple-600 {
background-color: var(--color-purple-600);
}
.bg-red-200 {
background-color: var(--color-red-200);
}
.bg-red-600 {
background-color: var(--color-red-600);
}
@@ -745,6 +766,10 @@
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
}
.text-xs {
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
}
.font-bold {
--tw-font-weight: var(--font-weight-bold);
font-weight: var(--font-weight-bold);
@@ -817,6 +842,13 @@
}
}
}
.hover\:border-neutral-500 {
&:hover {
@media (hover: hover) {
border-color: var(--color-neutral-500);
}
}
}
.hover\:bg-neutral-200 {
&:hover {
@media (hover: hover) {
@@ -824,13 +856,6 @@
}
}
}
.hover\:bg-neutral-300 {
&:hover {
@media (hover: hover) {
background-color: var(--color-neutral-300);
}
}
}
.hover\:bg-neutral-700 {
&:hover {
@media (hover: hover) {

View File

@@ -1,10 +1,22 @@
package templates
templ LoginForm(){
templ LoginForm(failed bool){
{{
failedClass := ""
if(failed){
failedClass = "border border-red-600"
}
}}
@Style()
<form method="POST">
<input name="card_uid" type="text" placeholder="card_uid"/>
<input name="password" type="passwort" placeholder="password"/>
<button type="submit">Send</button>
<div class="w-full h-[100vh] flex flex-col justify-center items-center">
<form method="POST" class={"w-1/2 flex flex-col gap-4 p-2", failedClass}>
<input name="personal_nummer" placeholder="Personalnummer" type="text" class="w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500"/>
<input name="password" placeholder="Passwort" type="password" class="w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500"/>
if failed {
<p class="text-red-600 text-sm" >Login fehlgeschlagen, bitte erneut versuchen!</p>
}
<button type="submit" class="cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-300 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50">Login</button>
</form>
</div>
}

View File

@@ -8,7 +8,7 @@ package templates
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func LoginForm() templ.Component {
func LoginForm(failed bool) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@@ -29,11 +29,48 @@ func LoginForm() templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
failedClass := ""
if failed {
failedClass = "border border-red-600"
}
templ_7745c5c3_Err = Style().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<form method=\"POST\"><input name=\"card_uid\" type=\"text\" placeholder=\"card_uid\"> <input name=\"password\" type=\"passwort\" placeholder=\"password\"> <button type=\"submit\">Send</button></form>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"w-full h-[100vh] flex flex-col justify-center items-center\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 = []any{"w-1/2 flex flex-col gap-4 p-2", failedClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<form method=\"POST\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/user.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><input name=\"personal_nummer\" placeholder=\"Personalnummer\" type=\"text\" class=\"w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500\"> <input name=\"password\" placeholder=\"Passwort\" type=\"password\" class=\"w-full placeholder:text-neutral-400 text-neutral-700 text-sm border border-neutral-300 rounded-md px-3 py-2 transition duration-300 ease focus:outline-none hover:border-neutral-500\"> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if failed {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<p class=\"text-red-600 text-sm\">Login fehlgeschlagen, bitte erneut versuchen!</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<button type=\"submit\" class=\"cursor-pointer rounded-md text-neutral-800 p-2 md:px-4 border text-center text-sm hover:text-white transition-colors border-neutral-300 focus:bg-neutral-700 active:bg-neutral-700 hover:bg-neutral-700 disabled:pointer-events-none disabled:opacity-50\">Login</button></form></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@@ -0,0 +1,40 @@
-- ----------------------------
-- Table structure for anwesenheit
-- ----------------------------
DROP TABLE IF EXISTS "anwesenheit";
CREATE TABLE "anwesenheit" (
"counter_id" bigserial PRIMARY KEY,
"timestamp" timestamp(6) DEFAULT CURRENT_TIMESTAMP,
"card_uid" varchar(255),
"check_in_out" int2,
"geraet_id" int2
);
COMMENT ON COLUMN "anwesenheit"."check_in_out" IS '1=Check In 2=Check Out , 3=Check in Manuell, 4=Check out manuell255=Automatic Check Out';
COMMENT ON COLUMN "anwesenheit"."geraet_id" IS 'ID des Lesegerätes';
-- ----------------------------
-- Table structure for personal_daten
-- ----------------------------
DROP TABLE IF EXISTS "personal_daten";
CREATE TABLE "personal_daten" (
"personal_nummer" int4 NOT NULL PRIMARY KEY,
"aktiv_beschaeftigt" bool,
"vorname" varchar(255),
"nachname" varchar(255),
"geburtsdatum" date,
"plz" varchar(255),
"adresse" varchar(255),
"geschlecht" int2,
"card_uid" varchar(255),
"hauptbeschaeftigungs_ort" int2,
"arbeitszeit_per_tag" float4,
"arbeitszeit_min_start" time(6),
"arbeitszeit_max_ende" time(6),
"vorgesetzter_pers_nr" int4
);
COMMENT ON COLUMN "personal_daten"."geschlecht" IS '1==weiblich, 2==maennlich, 3==divers';
-- Insert into personal_daten
INSERT INTO "personal_daten" ("personal_nummer", "aktiv_beschaeftigt", "vorname", "nachname", "geburtsdatum", "plz", "adresse", "geschlecht", "card_uid", "hauptbeschaeftigungs_ort", "arbeitszeit_per_tag", "arbeitszeit_min_start", "arbeitszeit_max_ende", "vorgesetzter_pers_nr") VALUES
(123, 't', 'Max', 'Mustermann', '2003-02-01', '08963', 'Altenburger Str. 44A', 1, 'acde-edca', 1, 7.5, '07:00:00', '20:00:00', 0);

View File

@@ -0,0 +1,14 @@
#!/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 USER $POSTGRES_API_USER WITH ENCRYPTED PASSWORD '$POSTGRES_API_PASSWORD';
GRANT CONNECT ON DATABASE $POSTGRES_DB TO $POSTGRES_API_USER;
GRANT USAGE ON SCHEMA public TO $POSTGRES_API_USER;
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public 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!"

View File

@@ -6,12 +6,10 @@ services:
env_file:
- .env
environment:
POSTGRES_USER: ${POSTGRES_ADMIN}
POSTGRES_PASSWORD: ${POSTGRES_ADMIN_PASS}
POSTGRES_DB: ${POSTGRES_DB}
PGDATA: /var/lib/postgresql/data/pg_data
volumes:
- ${POSTGRES_PATH}:/var/lib/postgresql/data
- ${POSTGRES_PATH}/initdb:/docker-entrypoint-initdb.d
ports:
- 5432:5432
@@ -28,8 +26,8 @@ services:
environment:
POSTGRES_HOST: db
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASS: ${POSTGRES_PASSWORD}
POSTGRES_USER: ${POSTGRES_API_USER}
POSTGRES_PASS: ${POSTGRES_API_PASSWORD}
EXPOSED_PORT: ${EXPOSED_PORT}
DEBUG: true
ports:

View File

@@ -6,8 +6,8 @@ services:
env_file:
- .env
environment:
POSTGRES_USER: ${POSTGRES_ADMIN}
POSTGRES_PASSWORD: ${POSTGRES_ADMIN_PASS}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
PGDATA: /var/lib/postgresql/data/pg_data
volumes:
@@ -21,8 +21,8 @@ services:
environment:
POSTGRES_HOST: db
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASS: ${POSTGRES_PASSWORD}
POSTGRES_USER: ${POSTGRES_API_USER}
POSTGRES_PASS: ${POSTGRES_API_PASSWORD}
EXPOSED_PORT: ${EXPOSED_PORT}
ports:
- ${EXPOSED_PORT}:8080

15
Jenkinsfile vendored
View File

@@ -16,22 +16,9 @@ pipeline {
}
}
steps {
sh 'make build_backend'
sh 'make backend'
}
}
stage('Deploying image arbeitszeit-backend') {
when {
anyOf{
changeset 'Jenkinsfile'
changeset 'Makefile'
changeset 'Backend/**'
}
}
steps {
sh 'make push_backend'
}
}
}
}

View File

@@ -36,3 +36,7 @@ push_%:
full_%:
make build_$*
make push_$*
backend: login_registry
docker buildx build --platform linux/amd64,linux/arm64 -t git.letsstein.de/tom/arbeitszeit-backend:latest Backend --push
docker buildx build --platform linux/amd64,linux/arm64 -t git.letsstein.de/tom/arbeitszeit-backend:${GIT_COMMIT} Backend --push

6
db.sql
View File

@@ -8,6 +8,7 @@ CREATE TABLE "public"."anwesenheit" (
);
COMMENT ON COLUMN "public"."anwesenheit"."check_in_out" IS '1=Check In 2=Check Out 255=Automatic Check Out';
COMMENT ON COLUMN "public"."anwesenheit"."geraet_id" IS 'ID des Lesegerätes';
-- @block create table personaldaten
CREATE TABLE "public"."personal_daten" (
"personal_nummer" SERIAL PRIMARY KEY,
@@ -25,9 +26,13 @@ COMMENT ON COLUMN "public"."personal_daten"."akiv_beschaeftig" IS 'derzeit aktiv
COMMENT ON COLUMN "public"."personal_daten"."geschlecht" IS 'w:1 m:2 div:3 kA:null ';
COMMENT ON COLUMN "public"."personal_daten"."card_uid" IS 'RFID-Karten-UID';
COMMENT ON COLUMN "public"."personal_daten"."hauptbeschaeftigung_ort" IS 'Chemnitz:1 Sayda:2';
-- @block drop tables
DROP TABLE IF EXISTS "public"."anwesenheit";
DROP TABLE IF EXISTS "public"."personal_daten";
-- @block insert into personal_daten
INSERT INTO personal_daten (
personal_nummer,
@@ -53,6 +58,7 @@ VALUES (
'test_card',
'1'
);
-- @block select
SELECT *
FROM personal_daten;