diff --git a/server/configuration/validator.go b/server/configuration/validator.go index a2bf2920..6aba6d11 100644 --- a/server/configuration/validator.go +++ b/server/configuration/validator.go @@ -17,7 +17,7 @@ var ( "WARN": true, "ERROR": true, } - validLanguages = map[string]bool{ + ValidLanguages = map[string]bool{ "en": true, "de": true, } @@ -46,7 +46,7 @@ func validateConfiguration() { configErrors = append(configErrors, fmt.Errorf("admin.password is empty, please set a password")) } - if !validLanguages[systemConfiguration.System.Frontend.DefaultLanguage] { + if !ValidLanguages[systemConfiguration.System.Frontend.DefaultLanguage] { configErrors = append(configErrors, fmt.Errorf("system.frontend.default_language has to be either 'en', or 'de'")) } diff --git a/server/db/init.go b/server/db/init.go index 2f88fb48..f4d28aff 100644 --- a/server/db/init.go +++ b/server/db/init.go @@ -9,6 +9,7 @@ import ( "time" "github.com/FachschaftMathPhysInfo/kummerkasten/configuration" + "github.com/FachschaftMathPhysInfo/kummerkasten/migrations" "github.com/FachschaftMathPhysInfo/kummerkasten/models" "github.com/uptrace/bun" "github.com/uptrace/bun/dialect/pgdialect" @@ -17,21 +18,9 @@ import ( ) var ( - db *bun.DB - sqldb *sql.DB - err error - tables = []interface{}{ - (*models.User)(nil), - (*models.Label)(nil), - (*models.Setting)(nil), - (*models.Ticket)(nil), - (*models.QuestionAnswerPair)(nil), - (*models.Session)(nil), - } - - relations = []interface{}{ - (*models.LabelsToTickets)(nil), - } + db *bun.DB + sqldb *sql.DB + err error ) const MaxDbPings = 10 @@ -67,34 +56,13 @@ func Init(ctx context.Context) (*sql.DB, *bun.DB) { } db = bun.NewDB(sqldb, pgdialect.New()) - db.AddQueryHook(bundebug.NewQueryHook()) + db.WithQueryHook(bundebug.NewQueryHook()) db.RegisterModel((*models.LabelsToTickets)(nil)) - if err := createTables(ctx, tables); err != nil { - slog.Error("Failed to create basic tabels", "error", err) - panic("Failed to create basic tables") - } - - slog.Info("Basic Database Tables successfully initialized") - - if err := createTables(ctx, relations); err != nil { - slog.Error("Failed to create basic relations", "error", err) - panic("Failed to create basic relations") + if err := migrations.RunMigrations(db, ctx); err != nil { + slog.Error("Error running migrations. To save the data from corruption the service will abort.", "error", err) + panic(err) } - slog.Info("Basic Database Relations successfully initialized") - return sqldb, db } - -func createTables(ctx context.Context, tables []interface{}) error { - for _, table := range tables { - if _, err := db.NewCreateTable(). - Model(table). - IfNotExists(). - Exec(ctx); err != nil { - return err - } - } - return nil -} diff --git a/server/db/seed.go b/server/db/seed.go index 543465a0..9ede2c48 100644 --- a/server/db/seed.go +++ b/server/db/seed.go @@ -81,7 +81,7 @@ func seedTestData(ctx context.Context, db *bun.DB) error { func removeTestUsers(ctx context.Context, db *bun.DB) error { slog.Info("Removing test users...") - if _, err := db.NewDelete().Model((*models.User)(nil)).Where("mail IN (?)", bun.In(testEmails)).Exec(ctx); err != nil { + if _, err := db.NewDelete().Model((*models.User)(nil)).Where("mail IN (?)", bun.List(testEmails)).Exec(ctx); err != nil { return err } slog.Info("Test users removed!") @@ -202,7 +202,7 @@ func createSettings(ctx context.Context, db *bun.DB) error { if err := db.NewSelect(). Model(&existing). - Where("key IN (?)", bun.In(keys)). + Where("key IN (?)", bun.List(keys)). Scan(ctx); err != nil { return fmt.Errorf("failed to fetch settings: %w", err) } @@ -660,7 +660,7 @@ func insertData[T any](ctx context.Context, db *bun.DB, model T, data []T, descr if _, err := db.NewInsert().Model(&data).Exec(ctx); err != nil { return fmt.Errorf("%s: %s", description, err) } - slog.Info(fmt.Sprintf("%s seeded successfully\n", description)) + slog.Info(fmt.Sprintf("%s seeded successfully", description)) } return nil } diff --git a/server/go.mod b/server/go.mod index ef753ba6..ee8c41a9 100644 --- a/server/go.mod +++ b/server/go.mod @@ -1,58 +1,56 @@ module github.com/FachschaftMathPhysInfo/kummerkasten -go 1.24.3 +go 1.25.0 require ( - github.com/99designs/gqlgen v0.17.76 - github.com/go-chi/chi/v5 v5.2.2 + github.com/99designs/gqlgen v0.17.89 + github.com/go-chi/chi/v5 v5.2.5 github.com/google/uuid v1.6.0 github.com/knadh/koanf/parsers/json v1.0.0 github.com/knadh/koanf/providers/env/v2 v2.0.0 github.com/knadh/koanf/providers/file v1.2.1 - github.com/knadh/koanf/v2 v2.3.0 + github.com/knadh/koanf/v2 v2.3.4 github.com/robfig/cron v1.2.0 github.com/rs/cors v1.11.1 - github.com/vektah/gqlparser/v2 v2.5.30 + github.com/vektah/gqlparser/v2 v2.5.32 ) require ( - github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect - github.com/fatih/color v1.18.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/fatih/color v1.19.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/knadh/koanf/maps v0.1.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-isatty v0.0.21 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sosodev/duration v1.3.1 // indirect - github.com/urfave/cli/v2 v2.27.7 // indirect - github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect - go.opentelemetry.io/otel v1.37.0 // indirect - go.opentelemetry.io/otel/trace v1.37.0 // indirect - golang.org/x/mod v0.29.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/text v0.31.0 // indirect - golang.org/x/tools v0.38.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + github.com/sosodev/duration v1.4.0 // indirect + github.com/urfave/cli/v3 v3.7.0 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/text v0.36.0 // indirect + golang.org/x/tools v0.44.0 // indirect ) require ( github.com/agnivade/levenshtein v1.2.1 // indirect github.com/gorilla/websocket v1.5.3 github.com/jinzhu/inflection v1.0.0 // indirect - github.com/lib/pq v1.10.9 + github.com/lib/pq v1.12.3 github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect - github.com/uptrace/bun v1.2.15 - github.com/uptrace/bun/dialect/pgdialect v1.2.15 - github.com/uptrace/bun/driver/pgdriver v1.2.15 - github.com/uptrace/bun/extra/bundebug v1.2.14 + github.com/uptrace/bun v1.2.18 + github.com/uptrace/bun/dialect/pgdialect v1.2.18 + github.com/uptrace/bun/driver/pgdriver v1.2.18 + github.com/uptrace/bun/extra/bundebug v1.2.18 github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - golang.org/x/crypto v0.45.0 - golang.org/x/sys v0.39.0 // indirect + golang.org/x/crypto v0.50.0 + golang.org/x/sys v0.43.0 // indirect mellium.im/sasl v0.3.2 // indirect ) diff --git a/server/go.sum b/server/go.sum index d40472bd..5e94ed5c 100644 --- a/server/go.sum +++ b/server/go.sum @@ -1,7 +1,7 @@ -github.com/99designs/gqlgen v0.17.76 h1:YsJBcfACWmXWU2t1yCjoGdOmqcTfOFpjbLAE443fmYI= -github.com/99designs/gqlgen v0.17.76/go.mod h1:miiU+PkAnTIDKMQ1BseUOIVeQHoiwYDZGCswoxl7xec= -github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo= -github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= +github.com/99designs/gqlgen v0.17.89 h1:KzEcxPiMgQoMw3m/E85atUEHyZyt0PbAflMia5Kw8z8= +github.com/99designs/gqlgen v0.17.89/go.mod h1:GFqruTVGB7ZTdrf1uzOagpXbY7DrEt1pIxnTdhIbWvQ= +github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw= +github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ= github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= @@ -10,20 +10,22 @@ github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kk github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= -github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= -github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= -github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -42,22 +44,18 @@ github.com/knadh/koanf/providers/env/v2 v2.0.0 h1:Ad5H3eun722u+FvchiIcEIJZsZ2M6o github.com/knadh/koanf/providers/env/v2 v2.0.0/go.mod h1:1g01PE+Ve1gBfWNNw2wmULRP0tc8RJrjn5p2N/jNCIc= github.com/knadh/koanf/providers/file v1.2.1 h1:bEWbtQwYrA+W2DtdBrQWyXqJaJSG3KrP3AESOJYp9wM= github.com/knadh/koanf/providers/file v1.2.1/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA= -github.com/knadh/koanf/v2 v2.3.0 h1:Qg076dDRFHvqnKG97ZEsi9TAg2/nFTa9hCdcSa1lvlM= -github.com/knadh/koanf/v2 v2.3.0/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/knadh/koanf/v2 v2.3.4 h1:fnynNSDlujWE+v83hAp8wKr/cdoxHLO0629SN+U8Urc= +github.com/knadh/koanf/v2 v2.3.4/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28= +github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= +github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs= +github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= @@ -66,56 +64,48 @@ github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= -github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= -github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/sosodev/duration v1.4.0 h1:35ed0KiVFriGHHzZZJaZLgmTEEICIyt8Sx0RQfj9IjE= +github.com/sosodev/duration v1.4.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= -github.com/uptrace/bun v1.2.15 h1:Ut68XRBLDgp9qG9QBMa9ELWaZOmzHNdczHQdrOZbEFE= -github.com/uptrace/bun v1.2.15/go.mod h1:Eghz7NonZMiTX/Z6oKYytJ0oaMEJ/eq3kEV4vSqG038= -github.com/uptrace/bun/dialect/pgdialect v1.2.15 h1:er+/3giAIqpfrXJw+KP9B7ujyQIi5XkPnFmgjAVL6bA= -github.com/uptrace/bun/dialect/pgdialect v1.2.15/go.mod h1:QSiz6Qpy9wlGFsfpf7UMSL6mXAL1jDJhFwuOVacCnOQ= -github.com/uptrace/bun/driver/pgdriver v1.2.15 h1:eZZ60ZtUUE6jjv6VAI1pCMaTgtx3sxmChQzwbvchOOo= -github.com/uptrace/bun/driver/pgdriver v1.2.15/go.mod h1:s2zz/BAeScal4KLFDI8PURwATN8s9RDBsElEbnPAjv4= -github.com/uptrace/bun/extra/bundebug v1.2.14 h1:BThGgGZ83sytVqa6Kyr3uyWRDTQ0/d2/p5rNQ3HJliw= -github.com/uptrace/bun/extra/bundebug v1.2.14/go.mod h1:dV+aN6aMACb1MGv7g2K3bs3vw5tl2lfopO96K+kqW5c= -github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= -github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= -github.com/vektah/gqlparser/v2 v2.5.30 h1:EqLwGAFLIzt1wpx1IPpY67DwUujF1OfzgEyDsLrN6kE= -github.com/vektah/gqlparser/v2 v2.5.30/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= +github.com/uptrace/bun v1.2.18 h1:3HnRcMfS6OBPMG1eSOzlbFJ/X/AyMEJb7rMxE6VQvDU= +github.com/uptrace/bun v1.2.18/go.mod h1:wNltaKJk4JtOt4SG5I5zmA7v0/Mzjh1+/S906Rayd3Y= +github.com/uptrace/bun/dialect/pgdialect v1.2.18 h1:IZ6nM2+OYrL8lkEAy7UkSEZvoa3vluTAUlZfPtlRB2k= +github.com/uptrace/bun/dialect/pgdialect v1.2.18/go.mod h1:Tqdf4QP1okrGYpXfodXvCOK6Ob1OOTwSaoAzCgBB3IU= +github.com/uptrace/bun/driver/pgdriver v1.2.18 h1:Zojuc83ulApocXomBLEcx1DqCZweREafHCjPfyXo88I= +github.com/uptrace/bun/driver/pgdriver v1.2.18/go.mod h1:ZRJcARw93nxbQ5WawTrc5EO+F+GygkcYgDLEnT17CcE= +github.com/uptrace/bun/extra/bundebug v1.2.18 h1:5cgkqdvhpSHIEONazSytm4RWYFneNtcznaWLt6r8m4M= +github.com/uptrace/bun/extra/bundebug v1.2.18/go.mod h1:M+U9YJVJcmk0RrszCb2Q1oskJiJ0LuC44FxDhZLP1ws= +github.com/urfave/cli/v3 v3.7.0 h1:AGSnbUyjtLiM+WJUb4dzXKldl/gL+F8OwmRDtVr6g2U= +github.com/urfave/cli/v3 v3.7.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= +github.com/vektah/gqlparser/v2 v2.5.32 h1:k9QPJd4sEDTL+qB4ncPLflqTJ3MmjB9SrVzJrawpFSc= +github.com/vektah/gqlparser/v2 v2.5.32/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= -github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg= -github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= -go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= -go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= -go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= -golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= -golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0= diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index 8bbcae21..8227bfee 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -44,6 +44,7 @@ type User { firstname: String! lastname: String! role: UserRole! + language: String createdAt: Time! lastModified: Time! lastLogin: Time @@ -112,6 +113,7 @@ input NewUser { firstname: String! lastname: String! password: String! + language: String } input UpdateUser { @@ -119,6 +121,7 @@ input UpdateUser { firstname: String lastname: String password: String + language: String } input UpdateTicket { diff --git a/server/graph/schema.resolvers.go b/server/graph/schema.resolvers.go index ba96fff5..36bd097c 100644 --- a/server/graph/schema.resolvers.go +++ b/server/graph/schema.resolvers.go @@ -1,8 +1,9 @@ package graph -// This file will be automatically regenerated based on the schema, any resolver implementations +// This file will be automatically regenerated based on the schema, any resolver +// implementations // will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.76 +// Code generated by github.com/99designs/gqlgen version v0.17.89 import ( "context" @@ -111,7 +112,7 @@ func (r *mutationResolver) CreateTicket(ctx context.Context, ticket model.NewTic // DeleteTicket is the resolver for the deleteTicket field. func (r *mutationResolver) DeleteTicket(ctx context.Context, ids []string) (int32, error) { - result, err := r.DB.NewDelete().Model((*model.Ticket)(nil)).Where("id IN (?)", bun.In(ids)).Exec(ctx) + result, err := r.DB.NewDelete().Model((*model.Ticket)(nil)).Where("id IN (?)", bun.List(ids)).Exec(ctx) if err != nil { slog.Error("Failed to delete tickets ", "error", err) return 0, ErrInternal @@ -173,7 +174,7 @@ func (r *mutationResolver) UpdateTicket(ctx context.Context, id string, ticket m // UpdateTicketState is the resolver for the updateTicketState field. func (r *mutationResolver) UpdateTicketState(ctx context.Context, ids []string, state model.TicketState) (int32, error) { result, err := r.DB.NewUpdate().Model((*models.Ticket)(nil)). - Where("id IN (?)", bun.In(ids)).Set("state = ?", state). + Where("id IN (?)", bun.List(ids)).Set("state = ?", state). Set("last_modified = ?", time.Now()).Exec(ctx) if err != nil { @@ -248,7 +249,7 @@ func (r *mutationResolver) CreateLabel(ctx context.Context, label model.NewLabel // DeleteLabel is the resolver for the deleteLabel field. func (r *mutationResolver) DeleteLabel(ctx context.Context, ids []string) (int32, error) { - result, err := r.DB.NewDelete().Model((*model.Label)(nil)).Where("id IN (?)", bun.In(ids)).Exec(ctx) + result, err := r.DB.NewDelete().Model((*model.Label)(nil)).Where("id IN (?)", bun.List(ids)).Exec(ctx) if err != nil { slog.Error("Failed to delete label", "error", err) return 0, ErrInternal @@ -337,6 +338,11 @@ func (r *mutationResolver) CreateUser(ctx context.Context, user model.NewUser) ( if err != nil { slog.Error("Failed to create user") + return nil, ErrInternal + } + + if user.Language != nil && !configuration.ValidLanguages[*user.Language] { + return nil, fmt.Errorf("the language provided is not supported") } userId := uuid.New().String() @@ -346,6 +352,7 @@ func (r *mutationResolver) CreateUser(ctx context.Context, user model.NewUser) ( Mail: strings.TrimSpace(user.Mail), Firstname: strings.TrimSpace(user.Firstname), Lastname: strings.TrimSpace(user.Lastname), + Language: *user.Language, Password: hashedPassword, Role: model.UserRoleUser, CreatedAt: time.Now(), @@ -362,6 +369,7 @@ func (r *mutationResolver) CreateUser(ctx context.Context, user model.NewUser) ( Mail: newDbUser.Mail, Firstname: newDbUser.Firstname, Lastname: newDbUser.Lastname, + Language: &newDbUser.Language, Role: newDbUser.Role, CreatedAt: newDbUser.CreatedAt, LastModified: newDbUser.LastModified, @@ -377,7 +385,7 @@ func (r *mutationResolver) DeleteUser(ctx context.Context, ids []string) (int32, return 0, fmt.Errorf("no ids provided to DeleteUser()") } - result, err := r.DB.NewDelete().Model((*model.User)(nil)).Where("ID IN (?)", bun.In(ids)).Exec(ctx) + result, err := r.DB.NewDelete().Model((*model.User)(nil)).Where("ID IN (?)", bun.List(ids)).Exec(ctx) if err != nil { slog.Error("Failed to delete user", "error", err) @@ -410,6 +418,13 @@ func (r *mutationResolver) UpdateUser(ctx context.Context, id string, user model if user.Lastname != nil { updatedUser.Lastname = strings.TrimSpace(*user.Lastname) } + if user.Language != nil { + if !configuration.ValidLanguages[*user.Language] { + return "", fmt.Errorf("the provided language is supported") + } + + updatedUser.Language = *user.Language + } if user.Password != nil { hashedPassword, err := auth.HashPassword(*user.Password) @@ -565,11 +580,11 @@ func (r *mutationResolver) CreateSetting(ctx context.Context, setting model.NewS // DeleteSetting is the resolver for the deleteSetting field. func (r *mutationResolver) DeleteSetting(ctx context.Context, keys []string) (int32, error) { - count, err := r.DB.NewSelect().Model((*model.Setting)(nil)).Where("key IN (?)", bun.In(keys)).Count(ctx) + count, err := r.DB.NewSelect().Model((*model.Setting)(nil)).Where("key IN (?)", bun.List(keys)).Count(ctx) if err != nil || count == 0 { return 0, nil } - _, err = r.DB.NewDelete().Model((*model.Setting)(nil)).Where("key IN (?)", bun.In(keys)).Exec(ctx) + _, err = r.DB.NewDelete().Model((*model.Setting)(nil)).Where("key IN (?)", bun.List(keys)).Exec(ctx) if err != nil { slog.Error("Failed to delete settings ", "error", err) return 0, ErrInternal @@ -804,14 +819,14 @@ func (r *mutationResolver) CreateQuestionAnswerPair(ctx context.Context, questio func (r *mutationResolver) DeleteQuestionAnswerPair(ctx context.Context, ids []string) (int32, error) { var positions []int err := r.DB.NewSelect().Model((*models.QuestionAnswerPair)(nil)). - Column("position").Where("id IN (?)", bun.In(ids)).Scan(ctx, &positions) + Column("position").Where("id IN (?)", bun.List(ids)).Scan(ctx, &positions) if err != nil { slog.Error("Failed to fetch position", "error", err) return 0, ErrInternal } result, err := r.DB.NewDelete().Model((*model.QuestionAnswerPair)(nil)). - Where("id IN (?)", bun.In(ids)).Exec(ctx) + Where("id IN (?)", bun.List(ids)).Exec(ctx) if err != nil { slog.Error("Failed to delete QuestionAnswerPair ", "error", err) return 0, ErrInternal @@ -1032,11 +1047,11 @@ func (r *queryResolver) Tickets(ctx context.Context, id []string, state []model. query := r.DB.NewSelect().Model(&dbTickets).Relation("Labels") if len(id) > 0 { - query = query.Where("ticket.id IN (?)", bun.In(id)) + query = query.Where("ticket.id IN (?)", bun.List(id)) } if len(state) > 0 { - query = query.Where("ticket.state IN (?)", bun.In(state)) + query = query.Where("ticket.state IN (?)", bun.List(state)) } if err := query.Scan(ctx); err != nil { @@ -1080,7 +1095,7 @@ func (r *queryResolver) Labels(ctx context.Context, ids []string) ([]*model.Labe query := r.DB.NewSelect().Model(&dbLabels).Relation("Tickets") if len(ids) > 0 { - query = query.Where("label.id IN (?)", bun.In(ids)) + query = query.Where("label.id IN (?)", bun.List(ids)) } if err := query.Scan(ctx); err != nil { @@ -1122,7 +1137,7 @@ func (r *queryResolver) FormLabels(ctx context.Context, ids []string) ([]*model. query := r.DB.NewSelect().Model(&dbLabels).Relation("Tickets") if len(ids) > 0 { - query = query.Where("label.id IN (?)", bun.In(ids)) + query = query.Where("label.id IN (?)", bun.List(ids)) } query = query.Where("label.form_label = ?", true) @@ -1166,10 +1181,10 @@ func (r *queryResolver) Users(ctx context.Context, id []string, mail []string, r query := r.DB.NewSelect().Model(&users) if len(id) > 0 { - query = query.Where("id IN (?)", bun.In(id)) + query = query.Where("id IN (?)", bun.List(id)) } if len(mail) > 0 { - query = query.Where("mail IN (?)", bun.In(mail)) + query = query.Where("mail IN (?)", bun.List(mail)) } if role != nil { query = query.Where("role = ?", *role) @@ -1201,7 +1216,7 @@ func (r *queryResolver) Settings(ctx context.Context, keys []string) ([]*model.S query := r.DB.NewSelect().Model(&settings) if len(keys) > 0 { - query = query.Where("key IN (?)", bun.In(keys)) + query = query.Where("key IN (?)", bun.List(keys)) } if err := query.Scan(ctx); err != nil { @@ -1405,7 +1420,7 @@ func (r *queryResolver) QuestionAnswerPairs(ctx context.Context, ids []string) ( query := r.DB.NewSelect().Model(&questionAnswerPairs) if len(ids) > 0 { - query = query.Where("id IN (?)", bun.In(ids)) + query = query.Where("id IN (?)", bun.List(ids)) } query = query.Order("question_answer_pair.position ASC") diff --git a/server/migrations/20260417001_init.go b/server/migrations/20260417001_init.go new file mode 100644 index 00000000..1cd77a0b --- /dev/null +++ b/server/migrations/20260417001_init.go @@ -0,0 +1,163 @@ +package migrations + +import ( + "context" + "log/slog" + "time" + + "github.com/FachschaftMathPhysInfo/kummerkasten/graph/model" + "github.com/uptrace/bun" +) + +var tables = []interface{}{ + (*User)(nil), + (*Label)(nil), + (*Setting)(nil), + (*Ticket)(nil), + (*QuestionAnswerPair)(nil), + (*Session)(nil), + (*LabelsToTickets)(nil), +} + +func init() { + Migrations.MustRegister(func(ctx context.Context, db *bun.DB) error { + slog.Info("Migration up starting") + + if err := createTables(db, ctx); err != nil { + slog.Error("Failed setting up inital tables", "error", err) + return err + } + + if _, err := db.NewCreateIndex(). + IfNotExists(). + TableExpr("sessions"). + Index("user_id_idx"). + ColumnExpr("user_id"). + Exec(ctx); err != nil { + return err + } + + return nil + }, func(ctx context.Context, db *bun.DB) error { + tableNames := []string{ + "labels_to_tickets", + "sessions", + "question_answer_pairs", + "tickets", + "settings", + "labels", + "users", + } + + if _, err := db.NewDropIndex(). + Index("user_id_idx"). + IfExists(). + Exec(ctx); err != nil { + return err + } + + for _, table := range tableNames { + if _, err := db.NewDropTable(). + TableExpr(table). + IfExists(). + Exec(ctx); err != nil { + return err + } + } + + return nil + }) +} + +func createTables(db *bun.DB, ctx context.Context) error { + for _, table := range tables { + if _, err := db.NewCreateTable(). + Model(table). + IfNotExists(). + Exec(ctx); err != nil { + return err + } + } + return nil +} + +type Label struct { + bun.BaseModel `bun:"table:labels"` + + ID string `bun:",pk,default:gen_random_UUID(),type:uuid"` + Name string `bun:",notnull,unique"` + Color string `bun:"type:varchar(8),default:'#7a7777'"` + FormLabel bool `bun:",default:false,notnull"` + Tickets []*Ticket `bun:"m2m:labels_to_tickets"` +} + +type QuestionAnswerPair struct { + bun.BaseModel `bun:"table:question_answer_pairs"` + + ID string `bun:",pk,default:gen_random_UUID(),type:uuid"` + Question string `bun:",unique,notnull"` + Answer string `bun:",notnull"` + Position int `bun:",unique,notnull"` +} + +type Session struct { + bun.BaseModel `bun:"table:sessions"` + + ID string `bun:",pk,default:gen_random_UUID(),type:uuid"` + UserID string `bun:",type:uuid,notnull"` + LastInteraction time.Time `bun:",notnull"` + ExpiresAt time.Time `bun:",notnull"` +} + +func (*Session) AfterCreateTable(ctx context.Context, query *bun.CreateTableQuery) error { + _, err := query.DB().NewCreateIndex().IfNotExists(). + Model((*Session)(nil)). + Index("user_id_idx"). + Column("user_id"). + Exec(ctx) + return err +} + +type Setting struct { + bun.BaseModel `bun:"table:settings"` + + Key string `bun:",pk"` + Value string `bun:",notnull"` +} + +type Ticket struct { + bun.BaseModel `bun:"table:tickets"` + + ID string `bun:",pk,default:gen_random_UUID(),type:uuid"` + OriginalTitle string `bun:",notnull"` + Title string `bun:",notnull"` + Text string `bun:",notnull"` + Note string `bun:""` + State model.TicketState `bun:",notnull,default:'NEW'"` + CreatedAt time.Time `bun:",notnull,default:current_timestamp"` + LastModified time.Time `bun:",notnull,default:current_timestamp"` + Labels []*Label `bun:"m2m:labels_to_tickets"` +} + +type LabelsToTickets struct { + bun.BaseModel `bun:"table:labels_to_tickets,alias:ltt"` + + TicketID string `bun:",pk,type:uuid,notnull"` + LabelID string `bun:",pk,type:uuid,notnull"` + Ticket *Ticket `bun:"rel:belongs-to,join:ticket_id=id"` + Label *Label `bun:"rel:belongs-to,join:label_id=id"` +} + +type User struct { + bun.BaseModel `bun:"table:users"` + + ID string `bun:",pk,default:gen_random_UUID(),type:uuid"` + Mail string `bun:",unique,notnull,type:varchar(255)"` + Firstname string `bun:",notnull,type:varchar(255)"` + Lastname string `bun:",notnull,type:varchar(255)"` + Role model.UserRole `bun:",notnull"` + Password string `bun:",notnull"` + CreatedAt time.Time `bun:",notnull"` + LastModified time.Time `bun:",notnull"` + LastLogin time.Time +} diff --git a/server/migrations/20260418001_add_languages_to_users.go b/server/migrations/20260418001_add_languages_to_users.go new file mode 100644 index 00000000..b1e6b01d --- /dev/null +++ b/server/migrations/20260418001_add_languages_to_users.go @@ -0,0 +1,36 @@ +package migrations + +import ( + "context" + "log/slog" + + "github.com/FachschaftMathPhysInfo/kummerkasten/models" + "github.com/uptrace/bun" +) + +func init() { + Migrations.MustRegister(func(ctx context.Context, db *bun.DB) error { + if _, err := db. + NewAddColumn(). + Model((*models.User)(nil)). + ColumnExpr("language VARCHAR(2)"). + IfNotExists(). + Exec(ctx); err != nil { + slog.Error("failed adding table") + return err + } + + return nil + }, func(ctx context.Context, db *bun.DB) error { + if _, err := db. + NewDropColumn(). + Model((*models.User)(nil)). + Column("language"). + Exec(ctx); err != nil { + slog.Error("Failed to drop column", "table", "users", "column", "language", "err", err) + return err + } + + return nil + }) +} diff --git a/server/migrations/init.go b/server/migrations/init.go new file mode 100644 index 00000000..045b9ebd --- /dev/null +++ b/server/migrations/init.go @@ -0,0 +1,45 @@ +package migrations + +import ( + "context" + "log/slog" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/migrate" +) + +var Migrations = migrate.NewMigrations() +var migrator *migrate.Migrator + +func RunMigrations(db *bun.DB, ctx context.Context) error { + migrator = migrate.NewMigrator(db, Migrations) + if err := migrator.Init(ctx); err != nil { + slog.Error("Failed to initialize Migrations", "error", err.Error()) + panic(err) + } + + if err := migrator.Lock(ctx); err != nil { + slog.Error("Failed to acquire migration lock", "error", err.Error()) + } + + defer func(migrator *migrate.Migrator, ctx context.Context) { + err := migrator.Unlock(ctx) + if err != nil { + slog.Error("Failed to unlock migration lock", "error", err.Error()) + } + }(migrator, ctx) + + group, err := migrator.Migrate(ctx) + + if err != nil { + slog.Error("Failed to run Migrations", "error", err.Error()) + } + + if group.IsZero() { + slog.Info("There were no new Migrations to be done.") + return nil + } + + slog.Info("Migrations done.", "current_group", group) + return nil +} diff --git a/server/models/user.go b/server/models/user.go index 2a990661..906a0a4c 100644 --- a/server/models/user.go +++ b/server/models/user.go @@ -16,7 +16,8 @@ type User struct { Lastname string `bun:",notnull,type:varchar(255)"` Role model.UserRole `bun:",notnull"` Password string `bun:",notnull"` - CreatedAt time.Time `bun:",notnull"` - LastModified time.Time `bun:",notnull"` + Language string + CreatedAt time.Time `bun:",notnull"` + LastModified time.Time `bun:",notnull"` LastLogin time.Time }