3 Commits

26 changed files with 386 additions and 1190 deletions

View File

@@ -10,7 +10,7 @@ import (
func OpenDatabase() (*sql.DB, error) {
dbHost := helper.GetEnv("POSTGRES_HOST", "localhost")
dbName := helper.GetEnv("POSTGRES_DB", "arbeitszeitmessung")
dbUser := helper.GetEnv("POSTGRES_API_USER", "arbeit_zeit")
dbUser := helper.GetEnv("POSTGRES_API_USER", "api_nutzer")
dbPassword := helper.GetEnv("POSTGRES_API_PASS", "password")
connStr := fmt.Sprintf("postgres://%s:%s@%s:5432/%s?sslmode=disable&TimeZone=Europe/Berlin", dbUser, dbPassword, dbHost, dbName)

View File

@@ -29,9 +29,10 @@ func autoLogout(w http.ResponseWriter) {
err = user.Logout()
if err != nil {
fmt.Printf("Error logging out user %v\n", err)
}
}else {
logged_out_users = append(logged_out_users, user)
}
}
}
w.Header().Set("Content-Type", "application/json")

View File

@@ -0,0 +1,45 @@
package endpoints
import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/models"
"arbeitszeitmessung/templates"
"log"
"net/http"
)
func TeamPresenceHandler(w http.ResponseWriter, r *http.Request){
helper.RequiresLogin(Session, w, r)
helper.SetCors(w)
switch r.Method {
case http.MethodGet:
teamPresence(w, r)
break
case http.MethodOptions:
// just support options header for non GET Requests from SWAGGER
w.WriteHeader(http.StatusOK)
break
default:
http.Error(w, "Method not allowed!", http.StatusMethodNotAllowed)
break
}
}
func teamPresence(w http.ResponseWriter, r *http.Request){
user, err := (*models.User).GetUserFromSession(nil, Session, r.Context())
if err != nil {
log.Println("Error getting user!", err)
}
team, err := user.GetTeamMembers()
teamPresence := make(map[bool][]models.User)
for _, user := range team {
present := user.CheckAnwesenheit()
teamPresence[present] = append(teamPresence[present], user)
}
if(err != nil){
log.Println("Error getting team", err)
}
templates.TeamPresencePage(teamPresence).Render(r.Context(), w)
}

View File

@@ -100,7 +100,7 @@ func updateBooking(w http.ResponseWriter, r *http.Request) {
log.Println("Error getting booking!", err)
continue
}
parsedTime, err := time.ParseInLocation("15:04", possibleBooking[0], time.Local)
parsedTime, err := time.ParseInLocation("15:04", possibleBooking[0], booking.Timestamp.Location())
if err != nil {
log.Println("Error parsing time!", err)
continue

View File

@@ -69,6 +69,5 @@ func checkPassword(r *http.Request) bool {
return false
}
}
log.Println(authHeaders)
return authToken == authHeaders[_authStart:]
}

View File

@@ -34,7 +34,7 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
}
}
func UserHandler(w http.ResponseWriter, r *http.Request) {
func UserSettingsHandler(w http.ResponseWriter, r *http.Request) {
helper.RequiresLogin(Session, w, r)
switch r.Method {
case http.MethodGet:

View File

@@ -1,6 +1,7 @@
package helper
import (
"context"
"net/http"
"os"
@@ -20,6 +21,7 @@ func SetCors(w http.ResponseWriter) {
}
func RequiresLogin(session *scs.SessionManager, w http.ResponseWriter, r *http.Request) {
r = r.WithContext(context.WithValue(r.Context(), "session", session))
if GetEnv("GO_ENV", "production") == "debug" {
return
}

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/joho/godotenv"
@@ -21,6 +22,16 @@ func main() {
if err != nil {
log.Println("No .env file found in directory!")
}
if(helper.GetEnv("GO_ENV", "production") == "debug") {
log.Println("Debug mode enabled")
log.Println("Environment Variables")
envs := os.Environ()
for _, e := range envs {
fmt.Println(e)
}
}
models.DB, err = OpenDatabase()
if err != nil {
@@ -38,9 +49,9 @@ func main() {
server.Handle("/time", ParamsMiddleware(endpoints.TimeHandler))
server.HandleFunc("/logout", endpoints.LogoutHandler)
server.HandleFunc("/user/login", endpoints.LoginHandler)
server.HandleFunc("/user", endpoints.UserHandler)
server.HandleFunc("/user/settings", endpoints.UserSettingsHandler)
server.HandleFunc("/team", endpoints.TeamHandler)
server.HandleFunc("/team/presence", endpoints.TeamHandler)
server.HandleFunc("/team/presence", endpoints.TeamPresenceHandler)
server.Handle("/", http.RedirectHandler("/time", http.StatusPermanentRedirect))
server.Handle("/static/", http.StripPrefix("/static/", fs))

View File

@@ -1,6 +1,7 @@
package models
import (
"arbeitszeitmessung/helper"
"database/sql"
"fmt"
"log"
@@ -161,16 +162,23 @@ func (b Booking) Save() {
}
func (b *Booking) GetBookingType() string {
debug := (helper.GetEnv("GO_ENV", "production") == "debug")
switch b.CheckInOut {
case 1: //manuelle Änderung
return "kommen"
case 3:
if(debug){
return "kommen manuell"
}
return "kommen"
case 2: //manuelle Änderung
return "gehen"
case 4:
if(debug){
return "gehen manuell"
case 255:
}
return "gehen"
case 254:
return "abgemeldet"
default:
return "Buchungs Typ unbekannt"
@@ -221,11 +229,11 @@ func (b *Booking) UpdateTime(newTime time.Time) {
// TODO: add check for time overlap
var newBooking Booking
newBooking.Timestamp = time.Date(b.Timestamp.Year(), b.Timestamp.Month(), b.Timestamp.Day(), hour, minute, 0, 0, time.Local)
newBooking.Timestamp = time.Date(b.Timestamp.Year(), b.Timestamp.Month(), b.Timestamp.Day(), hour, minute, 0, 0, b.Timestamp.Location())
if b.CheckInOut < 3 {
newBooking.CheckInOut = b.CheckInOut + 2
}
if b.CheckInOut == 255 {
if b.CheckInOut == 254 {
newBooking.CheckInOut = 4
}
b.Update(newBooking)

View File

@@ -68,7 +68,7 @@ func (u *User) GetAll() ([]User, error) {
// Returns true if there is a booking 1 for today -> meaning the user is at work
// Returns false if there is no booking today or the user is already booked out of the system
func (u *User) CheckAnwesenheit() bool {
qStr, err := DB.Prepare((`SELECT check_in_out FROM anwesenheit WHERE card_uid = $1 AND "timestamp" >= now()::date + interval '1h' ORDER BY "timestamp" DESC`))
qStr, err := DB.Prepare((`SELECT check_in_out FROM anwesenheit WHERE card_uid = $1 AND "timestamp" >= now()::date + interval '1h' ORDER BY "timestamp" DESC LIMIT 1;`))
if err != nil {
fmt.Printf("Error preparing query statement %v\n", err)
return false
@@ -79,12 +79,12 @@ func (u *User) CheckAnwesenheit() bool {
if err != nil {
return false
}
return check_in_out == 1
return check_in_out%2 == 1
}
// Creates a new booking for the user -> check_in_out will be 255 for automatic check out
// Creates a new booking for the user -> check_in_out will be 254 for automatic check out
func (u *User) Logout() error {
booking := (*Booking).New(nil, u.CardUID, 0, 255)
booking := (*Booking).New(nil, u.CardUID, 0, 254)
err := booking.Insert()
if err != nil {
fmt.Printf("Error inserting booking %v\n", err)
@@ -167,6 +167,15 @@ func (u *User) GetTeamMembers() ([]User, error) {
return teamMembers, nil
}
func (u *User) IsTeamLeader() bool {
team, err := u.GetTeamMembers()
if err != nil {
log.Println("Error getting team Members", err)
return false
}
return len(team) > 0
}
func (u *User) GetWeek(tsFrom time.Time) WorkWeek {
var bookings []WorkDay
weekStart := tsFrom.AddDate(0, 0, -1*int(tsFrom.Local().Weekday())-1)

View File

@@ -1,9 +1,11 @@
package models
import (
"arbeitszeitmessung/helper"
"encoding/json"
"fmt"
"log"
"strconv"
"time"
)
@@ -19,6 +21,7 @@ type WorkDay struct {
func (d *WorkDay) GetWorkDays(card_uid string, tsFrom, tsTo time.Time) []WorkDay {
var workDays []WorkDay
var workSec, pauseSec float64
qStr, err := DB.Prepare(`
WITH all_days AS (
SELECT generate_series($2::DATE, $3::DATE - INTERVAL '1 day', INTERVAL '1 day')::DATE AS work_date
@@ -43,7 +46,7 @@ func (d *WorkDay) GetWorkDays(card_uid string, tsFrom, tsTo time.Time) []WorkDay
COALESCE(
EXTRACT(EPOCH FROM SUM(
CASE
WHEN b.prev_check IN (1, 3) AND b.check_in_out IN (2, 4, 255)
WHEN b.prev_check IN (1, 3) AND b.check_in_out IN (2, 4, 254)
THEN b.timestamp - b.prev_timestamp
ELSE INTERVAL '0'
END
@@ -52,7 +55,7 @@ func (d *WorkDay) GetWorkDays(card_uid string, tsFrom, tsTo time.Time) []WorkDay
COALESCE(
EXTRACT(EPOCH FROM SUM(
CASE
WHEN b.prev_check IN (2, 4, 255) AND b.check_in_out IN (1, 3)
WHEN b.prev_check IN (2, 4, 254) AND b.check_in_out IN (1, 3)
THEN b.timestamp - b.prev_timestamp
ELSE INTERVAL '0'
END
@@ -80,6 +83,7 @@ func (d *WorkDay) GetWorkDays(card_uid string, tsFrom, tsTo time.Time) []WorkDay
return workDays
}
defer rows.Close()
emptyDays, _ := strconv.ParseBool(helper.GetEnv("EMPTY_DAYS", "false"))
for rows.Next() {
var workDay WorkDay
var bookings []byte
@@ -100,8 +104,11 @@ func (d *WorkDay) GetWorkDays(card_uid string, tsFrom, tsTo time.Time) []WorkDay
} else {
workDay.calcPauseTime()
}
if emptyDays || workDay.Bookings[0].CounterId != 0 {
workDays = append(workDays, workDay)
} else {
log.Println("no booking on day", workDay.Day.Format("02.01.2006"))
}
}
if err = rows.Err(); err != nil {
return workDays
@@ -172,7 +179,7 @@ func (d *WorkDay) GetWorkTimeString() (string, string) {
// returns bool wheter the workday was ended with an automatic logout
func (d *WorkDay) RequiresAction() bool {
return d.Bookings[len(d.Bookings)-1].CheckInOut == 255
return d.Bookings[len(d.Bookings)-1].CheckInOut == 254
}
// returns a integer percentage of how much day has been worked of

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +1,14 @@
package templates
templ headerComponent() {
<div class="flex flex-row justify-between md:mx-[10%] py-2">
<a href="/time">Zeitverwaltung</a>
<a href="/team">Abrechnung</a>
<a href="/user">Einstellungen</a>
if true {
<a href="/team/presence">Anwesenheit</a>
}
<a href="/user/settings">Einstellungen</a>
</div>
}

View File

@@ -29,7 +29,17 @@ func headerComponent() templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"flex flex-row justify-between md:mx-[10%] py-2\"><a href=\"/time\">Zeitverwaltung</a> <a href=\"/team\">Abrechnung</a> <a href=\"/user\">Einstellungen</a></div>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"flex flex-row justify-between md:mx-[10%] py-2\"><a href=\"/time\">Zeitverwaltung</a> <a href=\"/team\">Abrechnung</a> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if true {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<a href=\"/team/presence\">Anwesenheit</a> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<a href=\"/user/settings\">Einstellungen</a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@@ -115,3 +115,27 @@ templ NavPage() {
</div>
</div>
}
templ TeamPresencePage(teamPresence map[bool][]models.User) {
@Base()
@headerComponent()
<div class="grid-main divide-y-1">
<div class="grid-sub divide-x-1">
<h2 class="grid-cell font-bold uppercase">Anwesend</h2>
<div class="flex flex-col col-span-2 md:col-span-4">
for _, user := range teamPresence[true] {
@userPresenceComponent(user, true)
}
</div>
</div>
<div class="grid-sub divide-x-1">
<h2 class="grid-cell font-bold uppercase">Nicht Anwesend</h2>
<div class="flex flex-col col-span-2 md:col-span-4">
for _, user := range teamPresence[false] {
@userPresenceComponent(user, false)
}
</div>
</div>
</div>
}

View File

@@ -344,4 +344,61 @@ func NavPage() templ.Component {
})
}
func TeamPresencePage(teamPresence map[bool][]models.User) 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 {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
if templ_7745c5c3_Var11 == nil {
templ_7745c5c3_Var11 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = Base().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = headerComponent().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<div class=\"grid-main divide-y-1\"><div class=\"grid-sub divide-x-1\"><h2 class=\"grid-cell font-bold uppercase\">Anwesend</h2><div class=\"flex flex-col col-span-2 md:col-span-4\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, user := range teamPresence[true] {
templ_7745c5c3_Err = userPresenceComponent(user, true).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</div></div><div class=\"grid-sub divide-x-1\"><h2 class=\"grid-cell font-bold uppercase\">Nicht Anwesend</h2><div class=\"flex flex-col col-span-2 md:col-span-4\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, user := range teamPresence[false] {
templ_7745c5c3_Err = userPresenceComponent(user, false).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@@ -56,3 +56,14 @@ templ employeComponent(week models.WorkWeek) {
</form>
</div>
}
templ userPresenceComponent(user models.User, present bool){
<div class="grid-cell group flex flex-row gap-2">
if present {
<div class="h-8 bg-accent rounded-md group-hover:text-black md:text-transparent text-center p-1" >Anwesend</div>
} else {
<div class="h-8 bg-red-600 rounded-md group-hover:text-white md:text-transparent text-center p-1" >Abwesend</div>
}
<p>{user.Vorname} {user.Name}</p>
</div>
}

View File

@@ -250,4 +250,74 @@ func employeComponent(week models.WorkWeek) templ.Component {
})
}
func userPresenceComponent(user models.User, present 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 {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var15 := templ.GetChildren(ctx)
if templ_7745c5c3_Var15 == nil {
templ_7745c5c3_Var15 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div class=\"grid-cell group flex flex-row gap-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if present {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<div class=\"h-8 bg-accent rounded-md group-hover:text-black md:text-transparent text-center p-1\">Anwesend</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<div class=\"h-8 bg-red-600 rounded-md group-hover:text-white md:text-transparent text-center p-1\">Abwesend</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(user.Vorname)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 67, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(user.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/teamComponents.templ`, Line: 67, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@@ -1,6 +1,7 @@
package templates
import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/models"
"fmt"
"net/url"
@@ -63,14 +64,16 @@ templ dayComponent(workDay models.WorkDay) {
<div class="time-component flex flex-row md:col-span-3 gap-2 w-full grid-cell">
@lineComponent()
<form id={ "time-" + workDay.Day.Format("2006-01-02") } class="flex flex-col gap-2 group w-full justify-between" style={ justify } method="post">
if len(workDay.Bookings) <= 1 && workDay.Bookings[len(workDay.Bookings)-1].CounterId == 0 {
if len(workDay.Bookings) <= 1 && workDay.Bookings[len(workDay.Bookings)-1].CounterId == 0 && helper.GetEnv("GO_ENV", "production") == "debug" {
@noBookingComponent(workDay)
} else {
for _, booking := range workDay.Bookings {
@bookingComponent(booking)
}
if helper.GetEnv("GO_ENV", "production") == "debug" {
@newBookingComponent(workDay)
}
}
</form>
</div>
<div class="grid-cell">

View File

@@ -9,6 +9,7 @@ import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"arbeitszeitmessung/helper"
"arbeitszeitmessung/models"
"fmt"
"net/url"
@@ -47,7 +48,7 @@ func inputForm() templ.Component {
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(user.Vorname + " " + user.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 18, Col: 66}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 19, Col: 66}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
@@ -68,7 +69,7 @@ func inputForm() templ.Component {
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(urlParams.Get("time_from"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 27, Col: 57}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 28, Col: 57}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
@@ -81,7 +82,7 @@ func inputForm() templ.Component {
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(urlParams.Get("time_to"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 28, Col: 55}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 29, Col: 55}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
@@ -137,7 +138,7 @@ func dayComponent(workDay models.WorkDay) templ.Component {
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.Day.Format("Mon"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 51, Col: 94}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 52, Col: 94}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
@@ -150,7 +151,7 @@ func dayComponent(workDay models.WorkDay) templ.Component {
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(workDay.Day.Format("02.01.2006"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 51, Col: 139}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 52, Col: 139}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
@@ -178,7 +179,7 @@ func dayComponent(workDay models.WorkDay) templ.Component {
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(work)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 57, Col: 36}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 58, Col: 36}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
@@ -196,7 +197,7 @@ func dayComponent(workDay models.WorkDay) templ.Component {
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(pause)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 59, Col: 40}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 60, Col: 40}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
@@ -222,7 +223,7 @@ func dayComponent(workDay models.WorkDay) templ.Component {
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs("time-" + workDay.Day.Format("2006-01-02"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 65, Col: 56}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 66, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
@@ -235,7 +236,7 @@ func dayComponent(workDay models.WorkDay) templ.Component {
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(justify)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 65, Col: 131}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 66, Col: 131}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
@@ -245,7 +246,7 @@ func dayComponent(workDay models.WorkDay) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(workDay.Bookings) <= 1 && workDay.Bookings[len(workDay.Bookings)-1].CounterId == 0 {
if len(workDay.Bookings) <= 1 && workDay.Bookings[len(workDay.Bookings)-1].CounterId == 0 && helper.GetEnv("GO_ENV", "production") == "debug" {
templ_7745c5c3_Err = noBookingComponent(workDay).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
@@ -261,11 +262,13 @@ func dayComponent(workDay models.WorkDay) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if helper.GetEnv("GO_ENV", "production") == "debug" {
templ_7745c5c3_Err = newBookingComponent(workDay).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</form></div><div class=\"grid-cell\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
@@ -394,7 +397,7 @@ func timeGaugeComponent(progress uint8, today bool, warning bool) templ.Componen
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("height: %d%%", int(progress)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 116, Col: 149}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 119, Col: 149}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
@@ -526,7 +529,7 @@ func newBookingComponent(d models.WorkDay) templ.Component {
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(time.Now().Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 151, Col: 55}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 154, Col: 55}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil {
@@ -588,7 +591,7 @@ func bookingComponent(booking models.Booking) templ.Component {
var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(booking.Timestamp.Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 164, Col: 97}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 167, Col: 97}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil {
@@ -601,7 +604,7 @@ func bookingComponent(booking models.Booking) templ.Component {
var templ_7745c5c3_Var26 string
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs("booking_" + strconv.Itoa(booking.CounterId))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 165, Col: 70}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 168, Col: 70}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
if templ_7745c5c3_Err != nil {
@@ -614,7 +617,7 @@ func bookingComponent(booking models.Booking) templ.Component {
var templ_7745c5c3_Var27 string
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(booking.Timestamp.Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 165, Col: 126}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 168, Col: 126}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
if templ_7745c5c3_Err != nil {
@@ -627,7 +630,7 @@ func bookingComponent(booking models.Booking) templ.Component {
var templ_7745c5c3_Var28 string
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(booking.GetBookingType())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 166, Col: 29}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/timeComponents.templ`, Line: 169, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
if templ_7745c5c3_Err != nil {

View File

@@ -75,7 +75,7 @@ CREATE TABLE "abwesenheiten" (
"counter_id" bigserial PRIMARY KEY,
"card_uid" varchar(255),
"abwesenheit_typ" int2,
"datum" timestamptz(6) DEFAULT NOW()::DATE,
"datum" timestamptz(6) DEFAULT NOW()::DATE
);
-- Adds crypto extension

View File

@@ -8,3 +8,4 @@ EXPOSED_PORT=8000
TZ=Europe/Berlin
PGTZ=Europe/Berlin
API_TOKEN=dont_access
EMPTY_DAYS=false

View File

@@ -28,9 +28,7 @@
"paths": {
"/time": {
"get": {
"tags": [
"booking"
],
"tags": ["booking"],
"summary": "Gets all the bookings from one card_uid",
"description": "Returns all the bookings optionally filtered with cardID",
"operationId": "getBooking",
@@ -100,11 +98,7 @@
"check_in_out": {
"type": "integer",
"example": 1,
"enum": [
1,
2,
255
]
"enum": [1, 2, 254]
},
"timestamp": {
"type": "string",
@@ -131,9 +125,7 @@
},
"/time/new": {
"put": {
"tags": [
"booking"
],
"tags": ["booking"],
"summary": "Create new Booking",
"description": "Creates a new booking with the supplied parameters",
"operationId": "pcreateBooking",
@@ -171,11 +163,7 @@
"required": true,
"schema": {
"type": "integer",
"enum": [
1,
2,
255
]
"enum": [1, 2, 254]
}
}
],
@@ -203,11 +191,7 @@
"check_in_out": {
"type": "integer",
"example": 1,
"enum": [
1,
2,
255
]
"enum": [1, 2, 254]
},
"timestamp": {
"type": "string",
@@ -228,9 +212,7 @@
}
},
"get": {
"tags": [
"booking"
],
"tags": ["booking"],
"summary": "Create new Booking",
"description": "Creates a new booking with the supplied parameters",
"operationId": "gcreateBooking",
@@ -277,11 +259,7 @@
"required": true,
"schema": {
"type": "integer",
"enum": [
1,
2,
255
]
"enum": [1, 2, 254]
}
}
],
@@ -309,11 +287,7 @@
"check_in_out": {
"type": "integer",
"example": 1,
"enum": [
1,
2,
255
]
"enum": [1, 2, 254]
},
"timestamp": {
"type": "string",
@@ -339,11 +313,9 @@
},
"/logout": {
"get": {
"tags": [
"booking"
],
"tags": ["booking"],
"summary": "Logs out all logged in users",
"description": "With this call all actively logged in users (last booking today has check_in_out=1) will be logged out automaticly (check_in_out=255)",
"description": "With this call all actively logged in users (last booking today has check_in_out=1) will be logged out automaticly (check_in_out=254)",
"operationId": "autoLogout",
"responses": {
"200": {
@@ -412,11 +384,7 @@
"check_in_out": {
"type": "integer",
"example": 1,
"enum": [
1,
2,
255
]
"enum": [1, 2, 254]
},
"timestamp": {
"type": "string",
@@ -450,11 +418,7 @@
"check_in_out": {
"type": "integer",
"example": 1,
"enum": [
1,
2,
255
]
"enum": [1, 2, 254]
},
"timestamp": {
"type": "string",

View File

@@ -88,7 +88,7 @@ paths:
enum:
- 1
- 2
- 255
- 254
responses:
"200":
description: successfully created booking
@@ -137,7 +137,7 @@ paths:
enum:
- 1
- 2
- 255
- 254
responses:
"200":
description: successfully created booking
@@ -154,7 +154,7 @@ paths:
tags:
- booking
summary: Logs out all logged in users
description: With this call all actively logged in users (last booking today has check_in_out=1) will be logged out automaticly (check_in_out=255)
description: With this call all actively logged in users (last booking today has check_in_out=1) will be logged out automaticly (check_in_out=254)
operationId: autoLogout
responses:
"200":
@@ -196,7 +196,7 @@ components:
enum:
- 1
- 2
- 255
- 254
timestamp:
type: string
format: date-time

View File

@@ -26,15 +26,26 @@ Nutzeransicht (/user):
![user](docs/images/user.png)
## Buchungstypen
1 - Kommen
2 - Gehen
3 - Kommen Manuell
4 - Gehen Manuell
254 - Automatisch abgemeldet
## API
Nutzung der API
wenn die `dev-docker-compose.yml` Datei gestartet wird, ist direkt ein SwaggerUI Server mit entsprechender Datei inbegriffen.
### Buchungen [/time]
#### [GET] Anfrage
Parameter: cardID (string)
Antwort: `200`
```json
[
{
@@ -50,25 +61,30 @@ Antwort: `200`
"bookingTyp": 1,
"loggedTime": "2024-09-05T08:51:12.670827Z",
"id": 6
},
}
]
```
Antwort `500`
Serverfehler
#### [PUT] Anfrage
Parameter: id (int)
Body: (veränderte Parameter)
```json
{
"cradID": "test_card",
"readerID": "mytest",
"bookingTyp": 1,
"loggedTime": "2024-09-05T08:51:12.670827Z",
"loggedTime": "2024-09-05T08:51:12.670827Z"
}
```
Antwort `200`
```json
Antwort `200`
```json
{
"cradID": "test_card",
"readerID": "mytest",
@@ -76,16 +92,20 @@ Body: (veränderte Parameter)
"loggedTime": "2024-09-05T08:51:12.670827Z",
"id": 6
}
```
```
### Neue Buchung [/time/new]
#### [PUT] Anfrage
Parameter:
- cardID (string)
- readerID (string)
- bookingType (string)
Antwort `202` Akzeptiert und eingefügt
```json
{
"cradID": "test_card",

2
db.sql
View File

@@ -6,7 +6,7 @@ CREATE TABLE "public"."anwesenheit" (
"check_in_out" int2,
"geraet_id" int2
);
COMMENT ON COLUMN "public"."anwesenheit"."check_in_out" IS '1=Check In 2=Check Out 255=Automatic Check Out';
COMMENT ON COLUMN "public"."anwesenheit"."check_in_out" IS '1=Check In 2=Check Out 254=Automatic Check Out';
COMMENT ON COLUMN "public"."anwesenheit"."geraet_id" IS 'ID des Lesegerätes';
-- @block create table personaldaten