feat: add the study data models and schema migrations

Courses, meetings, class sessions, deadlines, readings with per-user
progress, materials with tags and file attachments. Timestamps are
stored in UTC and held as Europe/Warsaw text so the admin text inputs
can write them.
This commit is contained in:
Jakub Zych
2026-10-02 14:26:08 +02:00
parent cfdc6862bc
commit 1046237771
25 changed files with 1427 additions and 3 deletions

89
go.mod
View File

@@ -2,39 +2,122 @@ module git.golem15.com/jakub/sm-bm-plugin
go 1.27.0
toolchain go1.27.0
require (
git.golem15.com/golem15/sm-user-plugin v0.0.0
git.golem15.com/golem15/summercms v0.0.0
github.com/go-gormigrate/gormigrate/v2 v2.1.7
github.com/jackc/pgx/v5 v5.10.0
github.com/testcontainers/testcontainers-go v0.44.0
github.com/testcontainers/testcontainers-go/modules/postgres v0.44.0
gorm.io/gorm v1.31.2
)
require (
dario.cat/mergo v1.0.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/disintegration/imaging v1.6.2 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.7.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/ebitengine/purego v0.10.1 // indirect
github.com/felixge/httpsnoop v1.1.0 // indirect
github.com/fsnotify/fsnotify v1.10.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.15 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.30.4 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/gax-go/v2 v2.19.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.18.6 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/parsers/yaml v1.1.1 // indirect
github.com/knadh/koanf/providers/confmap v1.0.1 // indirect
github.com/knadh/koanf/providers/env/v2 v2.0.1 // indirect
github.com/knadh/koanf/providers/file v1.2.1 // indirect
github.com/knadh/koanf/v2 v2.3.6 // indirect
github.com/leodido/go-urn v1.5.0 // indirect
github.com/lib/pq v1.12.3 // indirect
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.2.0 // indirect
github.com/moby/moby/api v1.55.0 // indirect
github.com/moby/moby/client v0.5.0 // indirect
github.com/moby/patternmatcher v0.6.1 // indirect
github.com/moby/sys/sequential v0.7.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/nicksnyder/go-i18n/v2 v2.6.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/riverqueue/river v0.47.0 // indirect
github.com/riverqueue/river/riverdriver v0.47.0 // indirect
github.com/riverqueue/river/riverdriver/riverdatabasesql v0.47.0 // indirect
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.47.0 // indirect
github.com/riverqueue/river/rivershared v0.47.0 // indirect
github.com/riverqueue/river/rivertype v0.47.0 // indirect
github.com/shirou/gopsutil/v4 v4.26.6 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/spf13/cobra v1.10.2 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/stretchr/testify v1.12.1 // indirect
github.com/tidwall/gjson v1.19.0 // indirect
github.com/tidwall/match v1.2.0 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/tklauser/go-sysconf v0.4.0 // indirect
github.com/tklauser/numcpus v0.12.0 // indirect
github.com/wneessen/go-mail v0.8.1 // indirect
github.com/yuin/goldmark v1.8.6 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect
go.opentelemetry.io/otel v1.44.0 // indirect
go.opentelemetry.io/otel/metric v1.44.0 // indirect
go.opentelemetry.io/otel/sdk v1.44.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect
go.opentelemetry.io/otel/trace v1.44.0 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
gocloud.dev v0.46.0 // indirect
golang.org/x/crypto v0.57.0 // indirect
golang.org/x/image v0.46.0 // indirect
golang.org/x/net v0.58.0 // indirect
golang.org/x/sync v0.23.0 // indirect
golang.org/x/sys v0.48.0 // indirect
golang.org/x/term v0.46.0 // indirect
golang.org/x/text v0.42.0 // indirect
gorm.io/gorm v1.31.2 // indirect
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
google.golang.org/api v0.272.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gorm.io/driver/postgres v1.6.3 // indirect
)
replace git.golem15.com/golem15/summercms => ../../../../summercms.go
replace git.golem15.com/golem15/sm-user-plugin => ../../golem15/user

208
go.sum
View File

@@ -1,15 +1,79 @@
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
cloud.google.com/go/storage v1.61.3 h1:VS//ZfBuPGDvakfD9xyPW1RGF1Vy3BWUoVZXgW1KMOg=
cloud.google.com/go/storage v1.61.3/go.mod h1:JtqK8BBB7TWv0HVGHubtUdzYYrakOQIsMLffZ2Z/HWk=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/aws/aws-sdk-go-v2 v1.41.9 h1:/rYeyO2+HrMztAmxAq9++XJtFMqSIpSsNA0yDGALYq4=
github.com/aws/aws-sdk-go-v2 v1.41.9/go.mod h1:+HsoOEX80qAVUitj1A2DhCNTjmb3edVyuDypb6LNEeo=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.11 h1:h5+3VT69KUBK24grGuuA5saDJTj2IIjLb9au668Fo5I=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.11/go.mod h1:dnakxebH6UwFvcvujL0LVggYQ8nEvBGjU4G/V79Nv94=
github.com/aws/aws-sdk-go-v2/config v1.32.20 h1:8VMDnWc/kEzxsI/1ngGM9mG81a8IGmIHD8KLcYGwagc=
github.com/aws/aws-sdk-go-v2/config v1.32.20/go.mod h1:PuwEpciweIXGULWeOeSTXtSbH4CW9mWdWrhdCKQI1sM=
github.com/aws/aws-sdk-go-v2/credentials v1.19.19 h1:yuFzSV1U0aRNYCQGVaTY2zW2M/L93pYHnXnrJUphYhU=
github.com/aws/aws-sdk-go-v2/credentials v1.19.19/go.mod h1:7y63L1kGzeoDlJaQ3Z578KrnmfBut96JjvJUzGwR+YE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.25 h1:0w6dCiO8iez+YKwRhRBlL1CH/E3GTfdkuzrwj1by8vo=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.25/go.mod h1:9FDWUothyr5RCRAHc45XOiVCzUR8n/IhCYX+uVqw6vk=
github.com/aws/aws-sdk-go-v2/feature/s3/transfermanager v0.2.3 h1:w5OoDiMN6x53ROmiIImGzmVcxXv2q1GXY+aKV4WAJYM=
github.com/aws/aws-sdk-go-v2/feature/s3/transfermanager v0.2.3/go.mod h1:dAhgYp776bX3LuWvnSCFwQEjNs6fuFg7YXIy5PXcP3Q=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.25 h1:Uii3frf9ztec/ABM2/FSH9/z7PLzxfpG8h4RpkUFflQ=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.25/go.mod h1:G6kntsA2GorAxDPbap6xgB2F+amSLUF8GJTi7PUoX44=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.25 h1:r1+/l6m+WaUJF9HISEsNOLHSNj5EXYQxK8VX6Cz9NlA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.25/go.mod h1:cKf+D+NMDK1LndD7BowHbBZPgR9V0/5HubH0PFWvA+c=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.26 h1:A1PmWU2zfkIm9EyFlJncFXL4W4phML+h8KjltUsCvNQ=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.26/go.mod h1:dY4MRzXEizrD4hqtpKvWVGPX7QleSGGVY+EBolo1RmM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.10 h1:d5/908OJ4bXg8lyjeMPvXetEKqoDoLi5Owy1zNue3yg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.10/go.mod h1:a57l7Hwh+FWI+we50g5NPJHYUKeJKfXbc4w8SyXu8Ig=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.18 h1:W/EyPFl9A5rXrtoilfwHYEvzHER+K4SpBPtMXi24Mos=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.18/go.mod h1:UG50K+pvd/uy6xExbobg0rjqFBFZe6I3l75EPDZw4tg=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.25 h1:dD3dhHNglpd98gs72my22Ndqi1hqQGllFFg1F+twfxg=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.25/go.mod h1:0yAbjPfd64gG7mj85RW+fMEYdfBgCRZw8g/oWcL1pjc=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.25 h1:2pQEbwf+/6EDbiit/GcBE2K4IUpMZymaA0kOz3xK978=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.25/go.mod h1:KvT6NCcQ0EZ+ZkVRrlBMt04Po3ok23YELEp7WimhLhM=
github.com/aws/aws-sdk-go-v2/service/s3 v1.102.2 h1:ie4ElCmUKS26pzrZcIk/lmt4yWjAqLLcawstyQCh298=
github.com/aws/aws-sdk-go-v2/service/s3 v1.102.2/go.mod h1:zjsomFeX5duj+4PlMB+o4JoWTIx+G0XMyzjYrUbQkN0=
github.com/aws/aws-sdk-go-v2/service/signin v1.1.1 h1:1VwbP3qMNfxUDEXWki4rCE5iA+44VA1lokTz9HasGzw=
github.com/aws/aws-sdk-go-v2/service/signin v1.1.1/go.mod h1:vUtyoSj0OPji3kjIVSc/GlKuWEiL33f/WFxl6dmpy/A=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.19 h1:N6pIsdFOW1Kd9S4KyFKXdGRBojPPxkP32+uHFWLv4Hc=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.19/go.mod h1:3gt5WJArFooNmyLONS+h/R4J+o86II8du38IgCwj9dE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.2 h1:hc+lBYiiTr8Zk4MTzIsQ92MeDWCIDvWGmzKUWOaBcOg=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.2/go.mod h1:hU6fqB3OJA6/ePheD47LQnxvjYk6br6PtQxs+Q9ojvk=
github.com/aws/aws-sdk-go-v2/service/sts v1.42.3 h1:ErklX/7uhSbkAAeyQD/Y1OoQ9hO3SJXQNEgksORW3Js=
github.com/aws/aws-sdk-go-v2/service/sts v1.42.3/go.mod h1:ULe4HCzfKPiR6R3HEurE3b1upEkuk8AkMrOKtaOxKO8=
github.com/aws/smithy-go v1.26.0 h1:9ouqbi+NyKP7fV3Te7UElCwdAb6Y8uk7LGwPE5tVe/s=
github.com/aws/smithy-go v1.26.0/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
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/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
@@ -21,6 +85,11 @@ github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7np
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
@@ -29,26 +98,69 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY=
github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc=
github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE=
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI=
github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ=
github.com/go-gormigrate/gormigrate/v2 v2.1.7 h1:PdT4jVPbRb4R+0Ey2R0yJOdctVf4Whiq1Qi4necaZdg=
github.com/go-gormigrate/gormigrate/v2 v2.1.7/go.mod h1:3ouXglTuPrKF5+7cQyVGfvAXTU4vLMaYh9+EPl03uog=
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.4 h1:9Rcod2ZPO6mOEG6b4GqyoHE/H6//Ze0RuhOo1hT1x0w=
github.com/go-playground/validator/v10 v10.30.4/go.mod h1:numpT+RPLE91R9oYWMY/R9zRgJBewr3IXHko4OISPpk=
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/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
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/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18=
github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
github.com/googleapis/gax-go/v2 v2.19.0 h1:fYQaUOiGwll0cGj7jmHT/0nPlcrZDFPrZRhTsoCr8hE=
github.com/googleapis/gax-go/v2 v2.19.0/go.mod h1:w2ROXVdfGEVFXzmlciUU4EdjHgWvB5h2n6x/8XSTTJA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0=
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
@@ -67,12 +179,18 @@ github.com/knadh/koanf/providers/file v1.2.1 h1:bEWbtQwYrA+W2DtdBrQWyXqJaJSG3KrP
github.com/knadh/koanf/providers/file v1.2.1/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
github.com/knadh/koanf/v2 v2.3.6 h1:JoQPSJmvS4aP0xNc8xMDr5tcrkSEInL23/Il7pITAKo=
github.com/knadh/koanf/v2 v2.3.6/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
github.com/leodido/go-urn v1.5.0 h1:pLqT2kq1zpHW/1D18QMjMpdtX7cekxqtJJjg5ANyWw0=
github.com/leodido/go-urn v1.5.0/go.mod h1:9BORnCDhdPBJNDEX+w1bJisa8yOKYi116VeO96s4ifE=
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak=
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI=
github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o=
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=
@@ -101,8 +219,25 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/riverqueue/river v0.47.0 h1:j8HOEyiOE8gRRhVS2wllKams372TH1WWiH6xCakXHqc=
github.com/riverqueue/river v0.47.0/go.mod h1:Wgmwx475ZBd8lQnNrJgyG2DWH7BfyNiSAtv0rC9bJBQ=
github.com/riverqueue/river/riverdriver v0.47.0 h1:qU8VkjdMl9plqeRg57SxsDUM/i/eECaSYejZ7HynC60=
github.com/riverqueue/river/riverdriver v0.47.0/go.mod h1:NOXl0fUiF1AT/TaQOjdx2A/c0Davn+SKbW8nAXWjfC4=
github.com/riverqueue/river/riverdriver/riverdatabasesql v0.47.0 h1:4wckhMlLN08F76/GWxGF6vlrbs5HCMuV01dpUnvOGI4=
github.com/riverqueue/river/riverdriver/riverdatabasesql v0.47.0/go.mod h1:EME1EGr7RU/vMUf61mpf9YXhsZJ+GlFk4AHrIHT+YdE=
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.47.0 h1:5N9nvemhQwbUElMxASw4oEaYJ/v6hiS5Y9VcOQfdC5g=
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.47.0/go.mod h1:ZboiXXZKC4+fTkxBxGRVmAsCuUu0NPYliqbWYuQAZyw=
github.com/riverqueue/river/rivershared v0.47.0 h1:jdtFsBexCvLqTXf8wnDnGXvB/eeOtPKQZAmThkjFpLs=
github.com/riverqueue/river/rivershared v0.47.0/go.mod h1:w8Pi1T+6ypyko5/hs9Mv7IIIKo4fAL9eXYnkVV/Y418=
github.com/riverqueue/river/rivertype v0.47.0 h1:SzNavtLGR4nMT1QkrEYQ7n96OMatYsn/z3aJWhewmv0=
github.com/riverqueue/river/rivertype v0.47.0/go.mod h1:XKkcRQR6zm8RR/JQa1Q2ywpj8uXQu21quPa4Lpw1Xhw=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs=
github.com/shirou/gopsutil/v4 v4.26.6/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
@@ -112,10 +247,30 @@ github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
github.com/testcontainers/testcontainers-go v0.44.0 h1:/Fwh6HY1mIikhnm9e7HwoxGycx0lzRAE0f5VQpjFxzI=
github.com/testcontainers/testcontainers-go v0.44.0/go.mod h1:IcnwQrYTO86xHXu5bvMaBH7ATlbS3Qn1M1QWW3c66rE=
github.com/testcontainers/testcontainers-go/modules/postgres v0.44.0 h1:8fdv/9y3JMxjQ+ULAcOG8RtgeNu5t9XF9LolSXDuTwM=
github.com/testcontainers/testcontainers-go/modules/postgres v0.44.0/go.mod h1:CFr2LncGYokw+OKjXcr8ARCKG1SaC2UEnGxFBovE86g=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=
github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU=
github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI=
github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4=
@@ -128,27 +283,80 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/detectors/gcp v1.42.0 h1:kpt2PEJuOuqYkPcktfJqWWDjTEd/FNgrxcniL7kQrXQ=
go.opentelemetry.io/contrib/detectors/gcp v1.42.0/go.mod h1:W9zQ439utxymRrXsUOzZbFX4JhLxXU4+ZnCt8GG7yA8=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA=
go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk=
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
gocloud.dev v0.46.0 h1:niIuZwSjMtBx8K+ITB2s5kZullB13PGOS2ZoQPZxQ4Q=
gocloud.dev v0.46.0/go.mod h1:ACQe+2qO+hEO+pdcvvsM+RB63r8TyGD1W3ESCLFyzvM=
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.46.0 h1:b1+oYj0Jbp6K5MDT4i4/eZpYlk3V8SJhhDKh6LBHAyQ=
golang.org/x/image v0.46.0/go.mod h1:3B3W05VGVQyuXucLINLjXKrqISASfi4Xj+iCVkLMwew=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA=
google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA=
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE=
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:x5julN69+ED4PcFk/XWayw35O0lf/nGa4aNgODCmNmw=
google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5 h1:CogIeEXn4qWYzzQU0QqvYBM8yDF9cFYzDq9ojSpv0Js=
google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0=
gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk=
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo=
gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=

63
models/class_session.go Normal file
View File

@@ -0,0 +1,63 @@
// Code generated by summer make. DO NOT EDIT.
package models
import (
"time"
"gorm.io/gorm"
)
// ClassSession is one block of classes (zajęcia) of a course.
type ClassSession struct {
ID uint `gorm:"column:id;primaryKey"`
CourseID uint `gorm:"column:course_id"`
Course *Course `gorm:"foreignKey:CourseID"`
MeetingID *uint `gorm:"column:meeting_id"`
Meeting *Meeting `gorm:"foreignKey:MeetingID"`
StartsAt DateTime `gorm:"column:starts_at"`
EndsAt DateTime `gorm:"column:ends_at"`
Room *string `gorm:"column:room"`
Kind *string `gorm:"column:kind"`
Notes *string `gorm:"column:notes"`
IsOnline bool `gorm:"column:is_online"`
CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"`
}
// TableName is the plugin table.
func (ClassSession) TableName() string { return "jz_bm_class_sessions" }
// Fillable lists the columns mass assignment may set. course_id and
// meeting_id are set through the admin relation pickers.
func (ClassSession) Fillable() []string {
return []string{"course_id", "meeting_id", "starts_at", "ends_at", "room", "kind", "notes", "is_online"}
}
// Rules are checked by the admin API on every save.
func (ClassSession) Rules() map[string]string {
return map[string]string{
"starts_at": "required",
"ends_at": "required",
"room": "nullable|max:100",
"kind": "nullable|max:100",
"is_online": "boolean",
}
}
// TemporalErrors checks the timestamps, which lagoon.Validate cannot.
func (s *ClassSession) TemporalErrors() map[string][]string {
e := temporalErrors{}
e.dateTime("starts_at", s.StartsAt, true)
e.dateTime("ends_at", s.EndsAt, true)
if len(e) == 0 && !s.EndsAt.MustTime().After(s.StartsAt.MustTime()) {
e.add("ends_at", "The ends at must be a date after starts at.")
}
return e.result()
}
// BeforeSave stores NULL for empty optional text.
func (s *ClassSession) BeforeSave(*gorm.DB) error {
blank(&s.Room, &s.Kind, &s.Notes)
return nil
}

72
models/course.go Normal file
View File

@@ -0,0 +1,72 @@
// Code generated by summer make. DO NOT EDIT.
package models
import (
"time"
"gorm.io/gorm"
)
// Course forms, the values of Course.Form.
const (
FormWyklad = "wyklad"
FormCwiczenia = "cwiczenia"
FormKonwersatorium = "konwersatorium"
FormSeminarium = "seminarium"
FormInne = "inne"
)
// CourseForms lists the allowed Course.Form values.
var CourseForms = []string{FormWyklad, FormCwiczenia, FormKonwersatorium, FormSeminarium, FormInne}
// Course is one przedmiot of the programme in one form (a lecture and its
// tutorial are two courses).
type Course struct {
ID uint `gorm:"column:id;primaryKey"`
Code *string `gorm:"column:code"`
Name string `gorm:"column:name"`
Form string `gorm:"column:form"`
Lecturer *string `gorm:"column:lecturer"`
Semester int `gorm:"column:semester"`
ECTS *float64 `gorm:"column:ects"`
Assessment *string `gorm:"column:assessment"`
// ElectiveGroup marks alternatives ("do wyboru"): courses that share a
// value are options of one choice, such as "specjalnosc".
ElectiveGroup *string `gorm:"column:elective_group"`
Description *string `gorm:"column:description"`
SyllabusURL *string `gorm:"column:syllabus_url"`
SortOrder int `gorm:"column:sort_order"`
CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"`
}
// TableName is the plugin table.
func (Course) TableName() string { return "jz_bm_courses" }
// Fillable lists the columns mass assignment may set.
func (Course) Fillable() []string {
return []string{"code", "name", "form", "lecturer", "semester", "ects", "assessment", "elective_group", "description", "syllabus_url", "sort_order"}
}
// Rules are checked by the admin API on every save.
func (Course) Rules() map[string]string {
return map[string]string{
"code": "nullable|max:50",
"name": "required|max:255",
"form": "required|in:wyklad,cwiczenia,konwersatorium,seminarium,inne",
"lecturer": "nullable|max:255",
"semester": "required|integer|between:1,4",
"ects": "nullable|numeric|between:0,60",
"assessment": "nullable|max:100",
"elective_group": "nullable|max:100",
"syllabus_url": "nullable|max:2048",
"sort_order": "nullable|integer",
}
}
// BeforeSave stores NULL for empty optional text.
func (c *Course) BeforeSave(*gorm.DB) error {
blank(&c.Code, &c.Lecturer, &c.Assessment, &c.ElectiveGroup, &c.Description, &c.SyllabusURL)
return nil
}

56
models/deadline.go Normal file
View File

@@ -0,0 +1,56 @@
// Code generated by summer make. DO NOT EDIT.
package models
import (
"time"
"gorm.io/gorm"
)
// DeadlineKinds lists the allowed Deadline.Kind values.
var DeadlineKinds = []string{"zaliczenie", "egzamin", "praca", "organizacyjne", "inne"}
// Deadline is one entry of the terminarz: an exam, a submission or an
// administrative date, optionally tied to a course.
type Deadline struct {
ID uint `gorm:"column:id;primaryKey"`
Title string `gorm:"column:title"`
DueAt DateTime `gorm:"column:due_at"`
CourseID *uint `gorm:"column:course_id"`
Course *Course `gorm:"foreignKey:CourseID"`
Kind string `gorm:"column:kind"`
Description *string `gorm:"column:description"`
CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"`
}
// TableName is the plugin table.
func (Deadline) TableName() string { return "jz_bm_deadlines" }
// Fillable lists the columns mass assignment may set.
func (Deadline) Fillable() []string {
return []string{"title", "due_at", "course_id", "kind", "description"}
}
// Rules are checked by the admin API on every save.
func (Deadline) Rules() map[string]string {
return map[string]string{
"title": "required|max:255",
"due_at": "required",
"kind": "required|in:zaliczenie,egzamin,praca,organizacyjne,inne",
}
}
// TemporalErrors checks the due time, which lagoon.Validate cannot.
func (d *Deadline) TemporalErrors() map[string][]string {
e := temporalErrors{}
e.dateTime("due_at", d.DueAt, true)
return e.result()
}
// BeforeSave stores NULL for empty optional text.
func (d *Deadline) BeforeSave(*gorm.DB) error {
blank(&d.Description)
return nil
}

75
models/material.go Normal file
View File

@@ -0,0 +1,75 @@
// Code generated by summer make. DO NOT EDIT.
package models
import (
"time"
"gorm.io/gorm"
)
// MaterialTypes lists the allowed Material.Type values.
var MaterialTypes = []string{"pdf", "notatka", "link", "inne"}
// MaterialFilesField is the system_files field of a material's attachments
// (attachMany "files").
const MaterialFilesField = "files"
// Material is a study material: a note, a link or uploaded files, optionally
// tied to a course. A material without published_at is a draft and is not
// served by the API.
type Material struct {
ID uint `gorm:"column:id;primaryKey"`
CourseID *uint `gorm:"column:course_id"`
Course *Course `gorm:"foreignKey:CourseID"`
Title string `gorm:"column:title"`
Type string `gorm:"column:type"`
Body *string `gorm:"column:body"`
Source *string `gorm:"column:source"`
URL *string `gorm:"column:url"`
PublishedAt *DateTime `gorm:"column:published_at"`
CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"`
Tags []Tag `gorm:"many2many:jz_bm_material_tag;joinForeignKey:material_id;joinReferences:tag_id"`
}
// TableName is the plugin table.
func (Material) TableName() string { return "jz_bm_materials" }
// MorphName is the attachment_type of the material's system_files rows.
func (Material) MorphName() string { return `Jz\Bm\Models\Material` }
// Fillable lists the columns mass assignment may set.
func (Material) Fillable() []string {
return []string{"course_id", "title", "type", "body", "source", "url", "published_at"}
}
// Rules are checked by the admin API on every save.
func (Material) Rules() map[string]string {
return map[string]string{
"title": "required|max:255",
"type": "required|in:pdf,notatka,link,inne",
"source": "nullable|max:500",
"url": "nullable|max:2048",
}
}
// TemporalErrors checks published_at, which lagoon.Validate cannot.
func (m *Material) TemporalErrors() map[string][]string {
e := temporalErrors{}
if m.PublishedAt != nil {
e.dateTime("published_at", *m.PublishedAt, false)
}
return e.result()
}
// BeforeSave stores NULL for empty optional text and an empty publication
// time.
func (m *Material) BeforeSave(*gorm.DB) error {
blank(&m.Body, &m.Source, &m.URL)
if m.PublishedAt != nil && *m.PublishedAt == "" {
m.PublishedAt = nil
}
return nil
}

12
models/material_tag.go Normal file
View File

@@ -0,0 +1,12 @@
// Code generated by summer make. DO NOT EDIT.
package models
// MaterialTag is the pivot row between a material and a tag.
type MaterialTag struct {
MaterialID uint `gorm:"column:material_id;primaryKey"`
TagID uint `gorm:"column:tag_id;primaryKey"`
}
// TableName is the pivot table.
func (MaterialTag) TableName() string { return "jz_bm_material_tag" }

52
models/meeting.go Normal file
View File

@@ -0,0 +1,52 @@
// Code generated by summer make. DO NOT EDIT.
package models
import (
"time"
"gorm.io/gorm"
)
// Meeting is one zjazd: a weekend of classes of the part-time programme.
type Meeting struct {
ID uint `gorm:"column:id;primaryKey"`
Number int `gorm:"column:number"`
StartsOn Date `gorm:"column:starts_on"`
EndsOn Date `gorm:"column:ends_on"`
Notes *string `gorm:"column:notes"`
CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"`
}
// TableName is the plugin table.
func (Meeting) TableName() string { return "jz_bm_meetings" }
// Fillable lists the columns mass assignment may set.
func (Meeting) Fillable() []string { return []string{"number", "starts_on", "ends_on", "notes"} }
// Rules are checked by the admin API on every save.
func (Meeting) Rules() map[string]string {
return map[string]string{
"number": "required|integer|min:1|unique:jz_bm_meetings",
"starts_on": "required",
"ends_on": "required",
}
}
// TemporalErrors checks the dates, which lagoon.Validate cannot.
func (m *Meeting) TemporalErrors() map[string][]string {
e := temporalErrors{}
e.date("starts_on", m.StartsOn, true)
e.date("ends_on", m.EndsOn, true)
if len(e) == 0 && m.EndsOn < m.StartsOn {
e.add("ends_on", "The ends on must be a date after or equal to starts on.")
}
return e.result()
}
// BeforeSave stores NULL for empty optional text.
func (m *Meeting) BeforeSave(*gorm.DB) error {
blank(&m.Notes)
return nil
}

50
models/nullable.go Normal file
View File

@@ -0,0 +1,50 @@
package models
import "strings"
// blank clears each optional text column whose value is empty or only
// whitespace, so an emptied admin text input stores NULL rather than "".
func blank(fields ...**string) {
for _, f := range fields {
if *f != nil && strings.TrimSpace(**f) == "" {
*f = nil
}
}
}
// temporalErrors is a field-to-messages map in the shape of lagoon.Validate.
type temporalErrors map[string][]string
func (e temporalErrors) add(field, msg string) { e[field] = append(e[field], msg) }
func (e temporalErrors) dateTime(field string, v DateTime, required bool) {
switch {
case v == "" && required:
e.add(field, "The "+strings.ReplaceAll(field, "_", " ")+" field is required.")
case v != "" && !v.Valid():
e.add(field, "The "+strings.ReplaceAll(field, "_", " ")+" must be a date and time such as 2026-10-03 08:00.")
}
}
func (e temporalErrors) date(field string, v Date, required bool) {
switch {
case v == "" && required:
e.add(field, "The "+strings.ReplaceAll(field, "_", " ")+" field is required.")
case v != "" && !v.Valid():
e.add(field, "The "+strings.ReplaceAll(field, "_", " ")+" must be a date in the YYYY-MM-DD form.")
}
}
func (e temporalErrors) result() map[string][]string {
if len(e) == 0 {
return nil
}
return e
}
// TemporalValidator is implemented by models with Date or DateTime columns.
// The admin controllers call it before a save, because lagoon.Validate has
// no date rules.
type TemporalValidator interface {
TemporalErrors() map[string][]string
}

50
models/reading.go Normal file
View File

@@ -0,0 +1,50 @@
// Code generated by summer make. DO NOT EDIT.
package models
import (
"time"
"gorm.io/gorm"
)
// Reading is one lektura on a course's reading list.
type Reading struct {
ID uint `gorm:"column:id;primaryKey"`
CourseID uint `gorm:"column:course_id"`
Course *Course `gorm:"foreignKey:CourseID"`
Title string `gorm:"column:title"`
Author *string `gorm:"column:author"`
Source *string `gorm:"column:source"`
URL *string `gorm:"column:url"`
IsRequired bool `gorm:"column:is_required"`
SortOrder int `gorm:"column:sort_order"`
CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"`
}
// TableName is the plugin table.
func (Reading) TableName() string { return "jz_bm_readings" }
// Fillable lists the columns mass assignment may set.
func (Reading) Fillable() []string {
return []string{"course_id", "title", "author", "source", "url", "is_required", "sort_order"}
}
// Rules are checked by the admin API on every save.
func (Reading) Rules() map[string]string {
return map[string]string{
"title": "required|max:500",
"author": "nullable|max:255",
"source": "nullable|max:500",
"url": "nullable|max:2048",
"is_required": "boolean",
"sort_order": "nullable|integer",
}
}
// BeforeSave stores NULL for empty optional text.
func (r *Reading) BeforeSave(*gorm.DB) error {
blank(&r.Author, &r.Source, &r.URL)
return nil
}

View File

@@ -0,0 +1,17 @@
// Code generated by summer make. DO NOT EDIT.
package models
import "time"
// ReadingProgress records that a user (a golem15.user users row) has read a
// reading. One row per reading and user.
type ReadingProgress struct {
ID uint `gorm:"column:id;primaryKey"`
ReadingID uint `gorm:"column:reading_id"`
UserID uint `gorm:"column:user_id"`
ReadAt time.Time `gorm:"column:read_at"`
}
// TableName is the plugin table.
func (ReadingProgress) TableName() string { return "jz_bm_reading_progress" }

28
models/tag.go Normal file
View File

@@ -0,0 +1,28 @@
// Code generated by summer make. DO NOT EDIT.
package models
import "time"
// Tag labels materials.
type Tag struct {
ID uint `gorm:"column:id;primaryKey"`
Name string `gorm:"column:name"`
Slug string `gorm:"column:slug"`
CreatedAt time.Time `gorm:"column:created_at"`
UpdatedAt time.Time `gorm:"column:updated_at"`
}
// TableName is the plugin table.
func (Tag) TableName() string { return "jz_bm_tags" }
// Fillable lists the columns mass assignment may set.
func (Tag) Fillable() []string { return []string{"name", "slug"} }
// Rules are checked by the admin API on every save.
func (Tag) Rules() map[string]string {
return map[string]string{
"name": "required|max:100",
"slug": "required|max:100|unique:jz_bm_tags",
}
}

186
models/temporal.go Normal file
View File

@@ -0,0 +1,186 @@
package models
import (
"database/sql/driver"
"fmt"
"strings"
"time"
_ "time/tzdata" // Europe/Warsaw must resolve on hosts without zoneinfo.
)
// Location is the time zone the programme runs in. Timestamps are stored in
// UTC (TIMESTAMPTZ) and shown in this zone; a date or time typed without an
// offset is read in it.
var Location = mustLoadLocation("Europe/Warsaw")
func mustLoadLocation(name string) *time.Location {
loc, err := time.LoadLocation(name)
if err != nil {
panic(fmt.Sprintf("jz.bm: load location %s: %v", name, err))
}
return loc
}
// DateLayout is the textual form of a Date.
const DateLayout = "2006-01-02"
// dateTimeLayouts are the forms ParseDateTime accepts. RFC 3339 carries its
// own offset; the others are wall-clock times in Location.
var dateTimeLayouts = []string{
"2006-01-02T15:04",
"2006-01-02 15:04",
"2006-01-02T15:04:05",
"2006-01-02 15:04:05",
}
// ParseDateTime reads an RFC 3339 timestamp, or a wall-clock time
// ("2026-10-03 08:00") in Location.
func ParseDateTime(s string) (time.Time, error) {
s = strings.TrimSpace(s)
if t, err := time.Parse(time.RFC3339, s); err == nil {
return t, nil
}
for _, layout := range dateTimeLayouts {
if t, err := time.ParseInLocation(layout, s, Location); err == nil {
return t, nil
}
}
return time.Time{}, fmt.Errorf("jz.bm: %q is not a date and time", s)
}
// ParseDate reads a YYYY-MM-DD calendar date.
func ParseDate(s string) (time.Time, error) {
t, err := time.ParseInLocation(DateLayout, strings.TrimSpace(s), Location)
if err != nil {
return time.Time{}, fmt.Errorf("jz.bm: %q is not a YYYY-MM-DD date", s)
}
return t, nil
}
// DateTime is a TIMESTAMPTZ column held as RFC 3339 text in Location
// ("2026-10-03T08:00:00+02:00").
//
// It is a string type rather than time.Time because the admin form has no
// date picker and lagoon.Fill cannot write text into a time.Time field, while
// cabana treats any struct-typed field as a relation. A string type is filled
// from the form's text input, and Value checks and normalises it.
// TODO: requires SummerCMS change (a datetime form field type and Fill
// support for time.Time) to use time.Time here.
type DateTime string
// NewDateTime returns t as a DateTime in Location.
func NewDateTime(t time.Time) DateTime {
return DateTime(t.In(Location).Format(time.RFC3339))
}
// Time parses the value.
func (d DateTime) Time() (time.Time, error) { return ParseDateTime(string(d)) }
// MustTime parses the value and returns the zero time when it does not parse.
// Values read from the database always parse.
func (d DateTime) MustTime() time.Time {
t, _ := d.Time()
return t
}
// Valid reports whether the value parses.
func (d DateTime) Valid() bool {
_, err := d.Time()
return err == nil
}
// Value stores the instant in UTC. An empty value is SQL NULL.
func (d DateTime) Value() (driver.Value, error) {
if strings.TrimSpace(string(d)) == "" {
return nil, nil
}
t, err := d.Time()
if err != nil {
return nil, err
}
return t.UTC(), nil
}
// Scan reads a TIMESTAMPTZ (or its text) and normalises it to Location.
func (d *DateTime) Scan(src any) error {
switch v := src.(type) {
case nil:
*d = ""
case time.Time:
*d = NewDateTime(v)
case string:
return d.scanText(v)
case []byte:
return d.scanText(string(v))
default:
return fmt.Errorf("jz.bm: cannot scan %T into DateTime", src)
}
return nil
}
func (d *DateTime) scanText(s string) error {
if strings.TrimSpace(s) == "" {
*d = ""
return nil
}
t, err := ParseDateTime(s)
if err != nil {
// Postgres text output: "2026-10-03 06:00:00+00".
t, err = time.Parse("2006-01-02 15:04:05-07", s)
if err != nil {
return err
}
}
*d = NewDateTime(t)
return nil
}
// Date is a DATE column held as YYYY-MM-DD text, for the same reason as
// DateTime.
type Date string
// NewDate returns the calendar date of t in Location.
func NewDate(t time.Time) Date { return Date(t.In(Location).Format(DateLayout)) }
// Time returns midnight of the date in Location.
func (d Date) Time() (time.Time, error) { return ParseDate(string(d)) }
// Valid reports whether the value is a YYYY-MM-DD date.
func (d Date) Valid() bool {
_, err := d.Time()
return err == nil
}
// Value stores the date as text; Postgres casts it to DATE. An empty value is
// SQL NULL.
func (d Date) Value() (driver.Value, error) {
if strings.TrimSpace(string(d)) == "" {
return nil, nil
}
t, err := d.Time()
if err != nil {
return nil, err
}
return t.Format(DateLayout), nil
}
// Scan reads a DATE. The driver hands it over as midnight UTC, so the
// calendar fields are taken as they are, without a zone conversion.
func (d *Date) Scan(src any) error {
switch v := src.(type) {
case nil:
*d = ""
case time.Time:
*d = Date(v.Format(DateLayout))
case string:
*d = Date(strings.TrimSpace(v))
if len(*d) > len(DateLayout) {
*d = (*d)[:len(DateLayout)]
}
case []byte:
return d.Scan(string(v))
default:
return fmt.Errorf("jz.bm: cannot scan %T into Date", src)
}
return nil
}

136
models/temporal_test.go Normal file
View File

@@ -0,0 +1,136 @@
package models_test
import (
"testing"
"time"
"git.golem15.com/jakub/sm-bm-plugin/models"
)
func TestParseDateTimeForms(t *testing.T) {
want := time.Date(2026, 10, 3, 6, 0, 0, 0, time.UTC)
for _, in := range []string{
"2026-10-03T08:00:00+02:00",
"2026-10-03T06:00:00Z",
"2026-10-03 08:00",
"2026-10-03T08:00",
" 2026-10-03 08:00:00 ",
} {
got, err := models.ParseDateTime(in)
if err != nil {
t.Errorf("ParseDateTime(%q): %v", in, err)
continue
}
if !got.Equal(want) {
t.Errorf("ParseDateTime(%q) = %v, want %v", in, got, want)
}
}
// Winter time: a wall-clock time in December is UTC+1.
got, err := models.ParseDateTime("2026-12-01 08:00")
if err != nil || !got.Equal(time.Date(2026, 12, 1, 7, 0, 0, 0, time.UTC)) {
t.Errorf("winter wall clock = %v, %v", got, err)
}
for _, bad := range []string{"", "tomorrow", "2026-13-01 08:00", "2026-10-03"} {
if _, err := models.ParseDateTime(bad); err == nil {
t.Errorf("ParseDateTime(%q) accepted", bad)
}
}
}
func TestDateTimeRoundTrip(t *testing.T) {
d := models.DateTime("2026-10-03 08:00")
v, err := d.Value()
if err != nil {
t.Fatal(err)
}
stored, ok := v.(time.Time)
if !ok || stored.Location() != time.UTC || stored.Hour() != 6 {
t.Fatalf("Value = %#v, want 06:00 UTC", v)
}
var read models.DateTime
if err := read.Scan(stored); err != nil {
t.Fatal(err)
}
if read != "2026-10-03T08:00:00+02:00" {
t.Errorf("Scan = %q, want the Warsaw RFC 3339 form", read)
}
if err := read.Scan("2026-10-03 06:00:00+00"); err != nil || read != "2026-10-03T08:00:00+02:00" {
t.Errorf("Scan(postgres text) = %q, %v", read, err)
}
if err := read.Scan(nil); err != nil || read != "" {
t.Errorf("Scan(nil) = %q, %v", read, err)
}
if v, err := models.DateTime("").Value(); err != nil || v != nil {
t.Errorf("empty Value = %v, %v; want NULL", v, err)
}
if _, err := models.DateTime("soon").Value(); err == nil {
t.Error("an unparsable DateTime was stored")
}
if err := read.Scan(42); err == nil {
t.Error("Scan(int) succeeded")
}
}
func TestDateRoundTrip(t *testing.T) {
var d models.Date
if err := d.Scan(time.Date(2026, 10, 3, 0, 0, 0, 0, time.UTC)); err != nil || d != "2026-10-03" {
t.Errorf("Scan(time) = %q, %v", d, err)
}
if err := d.Scan([]byte("2026-10-04")); err != nil || d != "2026-10-04" {
t.Errorf("Scan(bytes) = %q, %v", d, err)
}
if v, err := models.Date("2026-10-03").Value(); err != nil || v != "2026-10-03" {
t.Errorf("Value = %v, %v", v, err)
}
if _, err := models.Date("03.10.2026").Value(); err == nil {
t.Error("a non-ISO date was stored")
}
if models.NewDate(time.Date(2026, 10, 2, 23, 30, 0, 0, time.UTC)) != "2026-10-03" {
t.Error("NewDate does not use the Warsaw calendar day")
}
}
func TestTemporalErrors(t *testing.T) {
s := &models.ClassSession{StartsAt: "2026-10-03 10:00", EndsAt: "2026-10-03 09:00"}
if errs := s.TemporalErrors(); len(errs["ends_at"]) != 1 {
t.Errorf("end before start: %v", errs)
}
s = &models.ClassSession{StartsAt: "bad"}
errs := s.TemporalErrors()
if len(errs["starts_at"]) != 1 || len(errs["ends_at"]) != 1 {
t.Errorf("bad and missing: %v", errs)
}
m := &models.Meeting{StartsOn: "2026-10-04", EndsOn: "2026-10-03"}
if errs := m.TemporalErrors(); len(errs["ends_on"]) != 1 {
t.Errorf("meeting order: %v", errs)
}
ok := &models.Meeting{StartsOn: "2026-10-03", EndsOn: "2026-10-04"}
if errs := ok.TemporalErrors(); errs != nil {
t.Errorf("valid meeting: %v", errs)
}
draft := &models.Material{}
if errs := draft.TemporalErrors(); errs != nil {
t.Errorf("draft material: %v", errs)
}
bad := models.DateTime("yesterday")
if errs := (&models.Material{PublishedAt: &bad}).TemporalErrors(); len(errs["published_at"]) != 1 {
t.Errorf("bad published_at: %v", errs)
}
}
func TestBeforeSaveBlanksOptionalText(t *testing.T) {
empty, space := "", " "
c := &models.Course{Code: &empty, Lecturer: &space}
if err := c.BeforeSave(nil); err != nil {
t.Fatal(err)
}
if c.Code != nil || c.Lecturer != nil {
t.Errorf("blank text kept: %v %v", c.Code, c.Lecturer)
}
pub := models.DateTime("")
m := &models.Material{PublishedAt: &pub}
_ = m.BeforeSave(nil)
if m.PublishedAt != nil {
t.Error("an empty published_at was not cleared to a draft")
}
}

View File

@@ -0,0 +1,31 @@
// Code generated by summer make. DO NOT EDIT.
package updates
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// CreateMeetings returns the 20261002120154_create_jz_bm_meetings gormigrate entry:
// the zjazdy.
func CreateMeetings() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "20261002120154_create_jz_bm_meetings",
Migrate: func(tx *gorm.DB) error {
return execAll(tx, `CREATE TABLE jz_bm_meetings (
id BIGSERIAL PRIMARY KEY,
number INTEGER NOT NULL UNIQUE CHECK (number > 0),
starts_on DATE NOT NULL,
ends_on DATE NOT NULL,
notes TEXT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CHECK (ends_on >= starts_on)
)`)
},
Rollback: func(tx *gorm.DB) error {
return tx.Exec("DROP TABLE IF EXISTS jz_bm_meetings").Error
},
}
}

View File

@@ -0,0 +1,38 @@
// Code generated by summer make. DO NOT EDIT.
package updates
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// CreateCourses returns the 20261002120203_create_jz_bm_courses gormigrate entry:
// the przedmioty, one row per course and form.
func CreateCourses() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "20261002120203_create_jz_bm_courses",
Migrate: func(tx *gorm.DB) error {
return execAll(tx, `CREATE TABLE jz_bm_courses (
id BIGSERIAL PRIMARY KEY,
code VARCHAR(50) NULL,
name VARCHAR(255) NOT NULL,
form VARCHAR(20) NOT NULL CHECK (form IN ('wyklad', 'cwiczenia', 'konwersatorium', 'seminarium', 'inne')),
lecturer VARCHAR(255) NULL,
semester INTEGER NOT NULL CHECK (semester > 0),
ects NUMERIC(4,1) NULL,
assessment VARCHAR(100) NULL,
description TEXT NULL,
syllabus_url VARCHAR(2048) NULL,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (name, form, semester)
);
CREATE INDEX jz_bm_courses_semester_sort_index ON jz_bm_courses (semester, sort_order)`)
},
Rollback: func(tx *gorm.DB) error {
return tx.Exec("DROP TABLE IF EXISTS jz_bm_courses").Error
},
}
}

View File

@@ -0,0 +1,38 @@
// Code generated by summer make. DO NOT EDIT.
package updates
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// CreateClassSessions returns the 20261002120204_create_jz_bm_class_sessions gormigrate entry:
// the zajęcia. Deleting a course deletes its sessions; deleting a meeting keeps them.
func CreateClassSessions() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "20261002120204_create_jz_bm_class_sessions",
Migrate: func(tx *gorm.DB) error {
return execAll(tx, `CREATE TABLE jz_bm_class_sessions (
id BIGSERIAL PRIMARY KEY,
course_id BIGINT NOT NULL REFERENCES jz_bm_courses (id) ON DELETE CASCADE,
meeting_id BIGINT NULL REFERENCES jz_bm_meetings (id) ON DELETE SET NULL,
starts_at TIMESTAMPTZ NOT NULL,
ends_at TIMESTAMPTZ NOT NULL,
room VARCHAR(100) NULL,
kind VARCHAR(100) NULL,
notes TEXT NULL,
is_online BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CHECK (ends_at > starts_at),
UNIQUE (course_id, starts_at)
);
CREATE INDEX jz_bm_class_sessions_starts_at_index ON jz_bm_class_sessions (starts_at);
CREATE INDEX jz_bm_class_sessions_meeting_id_index ON jz_bm_class_sessions (meeting_id)`)
},
Rollback: func(tx *gorm.DB) error {
return tx.Exec("DROP TABLE IF EXISTS jz_bm_class_sessions").Error
},
}
}

View File

@@ -0,0 +1,33 @@
// Code generated by summer make. DO NOT EDIT.
package updates
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// CreateDeadlines returns the 20261002120205_create_jz_bm_deadlines gormigrate entry:
// the terminarz. Deleting a course keeps its deadlines, unlinked.
func CreateDeadlines() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "20261002120205_create_jz_bm_deadlines",
Migrate: func(tx *gorm.DB) error {
return execAll(tx, `CREATE TABLE jz_bm_deadlines (
id BIGSERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
due_at TIMESTAMPTZ NOT NULL,
course_id BIGINT NULL REFERENCES jz_bm_courses (id) ON DELETE SET NULL,
kind VARCHAR(20) NOT NULL CHECK (kind IN ('zaliczenie', 'egzamin', 'praca', 'organizacyjne', 'inne')),
description TEXT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX jz_bm_deadlines_due_at_index ON jz_bm_deadlines (due_at);
CREATE INDEX jz_bm_deadlines_course_id_index ON jz_bm_deadlines (course_id)`)
},
Rollback: func(tx *gorm.DB) error {
return tx.Exec("DROP TABLE IF EXISTS jz_bm_deadlines").Error
},
}
}

View File

@@ -0,0 +1,34 @@
// Code generated by summer make. DO NOT EDIT.
package updates
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// CreateReadings returns the 20261002120206_create_jz_bm_readings gormigrate entry:
// the lektury. Deleting a course deletes its readings.
func CreateReadings() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "20261002120206_create_jz_bm_readings",
Migrate: func(tx *gorm.DB) error {
return execAll(tx, `CREATE TABLE jz_bm_readings (
id BIGSERIAL PRIMARY KEY,
course_id BIGINT NOT NULL REFERENCES jz_bm_courses (id) ON DELETE CASCADE,
title VARCHAR(500) NOT NULL,
author VARCHAR(255) NULL,
source VARCHAR(500) NULL,
url VARCHAR(2048) NULL,
is_required BOOLEAN NOT NULL DEFAULT FALSE,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (course_id, title)
)`)
},
Rollback: func(tx *gorm.DB) error {
return tx.Exec("DROP TABLE IF EXISTS jz_bm_readings").Error
},
}
}

View File

@@ -0,0 +1,29 @@
// Code generated by summer make. DO NOT EDIT.
package updates
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// CreateReadingProgress returns the 20261002120207_create_jz_bm_reading_progress gormigrate entry:
// who has read which reading. The rows go with the reading or the golem15.user user.
func CreateReadingProgress() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "20261002120207_create_jz_bm_reading_progress",
Migrate: func(tx *gorm.DB) error {
return execAll(tx, `CREATE TABLE jz_bm_reading_progress (
id BIGSERIAL PRIMARY KEY,
reading_id BIGINT NOT NULL REFERENCES jz_bm_readings (id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
read_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (reading_id, user_id)
);
CREATE INDEX jz_bm_reading_progress_user_id_index ON jz_bm_reading_progress (user_id)`)
},
Rollback: func(tx *gorm.DB) error {
return tx.Exec("DROP TABLE IF EXISTS jz_bm_reading_progress").Error
},
}
}

View File

@@ -0,0 +1,28 @@
// Code generated by summer make. DO NOT EDIT.
package updates
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// CreateTags returns the 20261002120208_create_jz_bm_tags gormigrate entry:
// the material tags.
func CreateTags() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "20261002120208_create_jz_bm_tags",
Migrate: func(tx *gorm.DB) error {
return execAll(tx, `CREATE TABLE jz_bm_tags (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
slug VARCHAR(100) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)`)
},
Rollback: func(tx *gorm.DB) error {
return tx.Exec("DROP TABLE IF EXISTS jz_bm_tags").Error
},
}
}

View File

@@ -0,0 +1,35 @@
// Code generated by summer make. DO NOT EDIT.
package updates
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// CreateMaterials returns the 20261002120209_create_jz_bm_materials gormigrate entry:
// the study materials; a NULL published_at is a draft. Deleting a course keeps its materials, unlinked.
func CreateMaterials() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "20261002120209_create_jz_bm_materials",
Migrate: func(tx *gorm.DB) error {
return execAll(tx, `CREATE TABLE jz_bm_materials (
id BIGSERIAL PRIMARY KEY,
course_id BIGINT NULL REFERENCES jz_bm_courses (id) ON DELETE SET NULL,
title VARCHAR(255) NOT NULL,
type VARCHAR(20) NOT NULL CHECK (type IN ('pdf', 'notatka', 'link', 'inne')),
body TEXT NULL,
source VARCHAR(500) NULL,
url VARCHAR(2048) NULL,
published_at TIMESTAMPTZ NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX jz_bm_materials_published_at_index ON jz_bm_materials (published_at);
CREATE INDEX jz_bm_materials_course_id_index ON jz_bm_materials (course_id)`)
},
Rollback: func(tx *gorm.DB) error {
return tx.Exec("DROP TABLE IF EXISTS jz_bm_materials").Error
},
}
}

View File

@@ -0,0 +1,27 @@
// Code generated by summer make. DO NOT EDIT.
package updates
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// CreateMaterialTag returns the 20261002120210_create_jz_bm_material_tag gormigrate entry:
// the material-tag pivot.
func CreateMaterialTag() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "20261002120210_create_jz_bm_material_tag",
Migrate: func(tx *gorm.DB) error {
return execAll(tx, `CREATE TABLE jz_bm_material_tag (
material_id BIGINT NOT NULL REFERENCES jz_bm_materials (id) ON DELETE CASCADE,
tag_id BIGINT NOT NULL REFERENCES jz_bm_tags (id) ON DELETE CASCADE,
PRIMARY KEY (material_id, tag_id)
);
CREATE INDEX jz_bm_material_tag_tag_id_index ON jz_bm_material_tag (tag_id)`)
},
Rollback: func(tx *gorm.DB) error {
return tx.Exec("DROP TABLE IF EXISTS jz_bm_material_tag").Error
},
}
}

View File

@@ -0,0 +1,22 @@
// Code generated by summer make. DO NOT EDIT.
package updates
import (
"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)
// AddElectiveGroupToCourses returns the 20261002121227_add_elective_group_to_courses gormigrate entry:
// courses that share an elective_group are alternatives ("do wyboru").
func AddElectiveGroupToCourses() *gormigrate.Migration {
return &gormigrate.Migration{
ID: "20261002121227_add_elective_group_to_courses",
Migrate: func(tx *gorm.DB) error {
return tx.Exec("ALTER TABLE jz_bm_courses ADD COLUMN elective_group VARCHAR(100) NULL").Error
},
Rollback: func(tx *gorm.DB) error {
return tx.Exec("ALTER TABLE jz_bm_courses DROP COLUMN IF EXISTS elective_group").Error
},
}
}

21
updates/exec.go Normal file
View File

@@ -0,0 +1,21 @@
package updates
import (
"strings"
"gorm.io/gorm"
)
// execAll runs each semicolon-separated statement of sql in order. The
// statements hold no semicolons of their own.
func execAll(tx *gorm.DB, sql string) error {
for _, stmt := range strings.Split(sql, ";") {
if strings.TrimSpace(stmt) == "" {
continue
}
if err := tx.Exec(stmt).Error; err != nil {
return err
}
}
return nil
}