From 75e2892e36534cb7a77f08fb59f34c76f2eb16e1 Mon Sep 17 00:00:00 2001 From: Amir Husayn Panahifar Date: Fri, 16 Oct 2026 09:44:23 +0330 Subject: [PATCH] fix: release v0.2.1 lesson deletes and large uploads - Raise Traefik read timeout to 1h (compose dev/prod + k3s HelmChartConfig) so admin video uploads up to 200MB no longer 502 mid-upload. - Migration 0029: cascade watch/quiz history on lesson delete and unlink dependent lessons, so removing a lesson or chapter stops 500ing. - Add the Apollo cluster Helm overlay (values-apollo.yaml) for the deployed environment. - k3s Traefik chart config also enables access logs, cross-namespace IngressRoutes, and fixes the Prometheus scrape port. Bump appVersion/product.go/values tags to 0.2.1; chart version 0.2.3. --- README.md | 2 +- backend/internal/api/product.go | 4 +- .../0029_lesson_delete_cascades.sql | 26 ++++++ deploy/compose/compose.dev.yaml | 3 + deploy/compose/compose.prod.yaml | 3 + deploy/helm/pargar/Chart.yaml | 4 +- deploy/helm/pargar/values-apollo.yaml | 81 +++++++++++++++++++ deploy/helm/pargar/values-prod.yaml | 4 +- deploy/k3s/traefik-helmchartconfig.yaml | 43 ++++++++++ 9 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 backend/internal/db/migrations/0029_lesson_delete_cascades.sql create mode 100644 deploy/helm/pargar/values-apollo.yaml create mode 100644 deploy/k3s/traefik-helmchartconfig.yaml diff --git a/README.md b/README.md index c78c2ab..29b345f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@

- release + release AGPL-3.0

diff --git a/backend/internal/api/product.go b/backend/internal/api/product.go index 5d09040..d51e05a 100644 --- a/backend/internal/api/product.go +++ b/backend/internal/api/product.go @@ -583,7 +583,7 @@ func (s *Server) handleMeta(w http.ResponseWriter, r *http.Request) { } func appBuild() (version, built string) { - version = "0.2.0" + version = "0.2.1" built = "dev" info, ok := debug.ReadBuildInfo() if !ok { @@ -594,7 +594,7 @@ func appBuild() (version, built string) { built = setting.Value[:10] } if setting.Key == "vcs.revision" && len(setting.Value) >= 7 { - version = "0.2.0+" + setting.Value[:7] + version = "0.2.1+" + setting.Value[:7] } } return version, built diff --git a/backend/internal/db/migrations/0029_lesson_delete_cascades.sql b/backend/internal/db/migrations/0029_lesson_delete_cascades.sql new file mode 100644 index 0000000..11e4a35 --- /dev/null +++ b/backend/internal/db/migrations/0029_lesson_delete_cascades.sql @@ -0,0 +1,26 @@ +-- 0029_lesson_delete_cascades.sql +-- Make deleting a lesson (directly, or via its chapter) work cleanly. +-- +-- Previously only mcq_questions cascaded; watch/quiz history blocked the delete +-- with a 500. Progress/history rows now go away with the lesson, while lessons +-- that merely *required* the deleted one are unlinked (never deleted). + +ALTER TABLE watch_sessions DROP CONSTRAINT IF EXISTS watch_sessions_lesson_id_fkey; +ALTER TABLE watch_sessions ADD CONSTRAINT watch_sessions_lesson_id_fkey + FOREIGN KEY (lesson_id) REFERENCES lessons(id) ON DELETE CASCADE; + +ALTER TABLE watch_heartbeats DROP CONSTRAINT IF EXISTS watch_heartbeats_lesson_id_fkey; +ALTER TABLE watch_heartbeats ADD CONSTRAINT watch_heartbeats_lesson_id_fkey + FOREIGN KEY (lesson_id) REFERENCES lessons(id) ON DELETE CASCADE; + +ALTER TABLE lesson_progress DROP CONSTRAINT IF EXISTS lesson_progress_lesson_id_fkey; +ALTER TABLE lesson_progress ADD CONSTRAINT lesson_progress_lesson_id_fkey + FOREIGN KEY (lesson_id) REFERENCES lessons(id) ON DELETE CASCADE; + +ALTER TABLE quiz_attempts DROP CONSTRAINT IF EXISTS quiz_attempts_lesson_id_fkey; +ALTER TABLE quiz_attempts ADD CONSTRAINT quiz_attempts_lesson_id_fkey + FOREIGN KEY (lesson_id) REFERENCES lessons(id) ON DELETE CASCADE; + +ALTER TABLE lessons DROP CONSTRAINT IF EXISTS lessons_requires_lesson_id_fkey; +ALTER TABLE lessons ADD CONSTRAINT lessons_requires_lesson_id_fkey + FOREIGN KEY (requires_lesson_id) REFERENCES lessons(id) ON DELETE SET NULL; diff --git a/deploy/compose/compose.dev.yaml b/deploy/compose/compose.dev.yaml index a3ec447..701a709 100644 --- a/deploy/compose/compose.dev.yaml +++ b/deploy/compose/compose.dev.yaml @@ -63,6 +63,9 @@ services: - --entrypoints.web.http.redirections.entrypoint.permanent=true - --entrypoints.web.http.aliasHeadersStrategy=delete - --entrypoints.websecure.http.aliasHeadersStrategy=delete + # Allow slow, large uploads (admin video ≤ 200MB); default is 60s. + - --entrypoints.web.transport.respondingTimeouts.readTimeout=3600 + - --entrypoints.websecure.transport.respondingTimeouts.readTimeout=3600 # Dev: Traefik default certificate (browser warning expected on localhost). - --api.dashboard=true - --api.insecure=true diff --git a/deploy/compose/compose.prod.yaml b/deploy/compose/compose.prod.yaml index a3c60c2..db75170 100644 --- a/deploy/compose/compose.prod.yaml +++ b/deploy/compose/compose.prod.yaml @@ -71,6 +71,9 @@ services: - --entrypoints.web.http.redirections.entrypoint.permanent=true - --entrypoints.web.http.aliasHeadersStrategy=delete - --entrypoints.websecure.http.aliasHeadersStrategy=delete + # Allow slow, large uploads (admin video ≤ 200MB); default is 60s. + - --entrypoints.web.transport.respondingTimeouts.readTimeout=3600 + - --entrypoints.websecure.transport.respondingTimeouts.readTimeout=3600 - --certificatesresolvers.le.acme.email=${ACME_EMAIL:?ACME_EMAIL must be set} - --certificatesresolvers.le.acme.storage=/data/acme.json - --certificatesresolvers.le.acme.httpchallenge=true diff --git a/deploy/helm/pargar/Chart.yaml b/deploy/helm/pargar/Chart.yaml index 05d8efa..945b67d 100644 --- a/deploy/helm/pargar/Chart.yaml +++ b/deploy/helm/pargar/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: pargar description: Pargar gated LMS - Go API, Next.js frontend, Postgres, Redis, S3-compatible storage type: application -version: 0.2.2 -appVersion: "0.2.0" +version: 0.2.3 +appVersion: "0.2.1" keywords: - lms - pargar diff --git a/deploy/helm/pargar/values-apollo.yaml b/deploy/helm/pargar/values-apollo.yaml new file mode 100644 index 0000000..9f9b2de --- /dev/null +++ b/deploy/helm/pargar/values-apollo.yaml @@ -0,0 +1,81 @@ +backend: + autoscaling: + enabled: false + env: + devMode: "false" + donationEnabled: "true" + otelEndpoint: http://tempo.infra.svc.cluster.local:4318 + physicalCertEnabled: "false" + seedCurriculum: "false" + existingSecret: pargar-app + image: + pullPolicy: IfNotPresent + repository: pargar-backend + tag: 0.2.1 + metrics: + serviceMonitor: + enabled: true + interval: 30s + podDisruptionBudget: + enabled: true + minAvailable: 1 + replicaCount: 3 + resources: + limits: + cpu: "1" + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi +backup: + enabled: true + persistence: + size: 2Gi + retentionDays: 7 + schedule: 0 3 * * * +certManager: + enabled: false +frontend: + autoscaling: + enabled: false + image: + pullPolicy: IfNotPresent + repository: pargar-frontend + tag: 0.2.1 + podDisruptionBudget: + enabled: false + replicaCount: 2 + resources: + limits: + cpu: "1" + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi +global: + domain: pargar.panahifar.ir + frontendOrigins: https://pargar.panahifar.ir + imageRegistry: "" + publicUrl: https://pargar.panahifar.ir +imagePullSecrets: [] +ingress: + enabled: true + tls: + enabled: true + existingSecret: pargar-wildcard-tls + useIngressRoute: true +networkPolicy: + enabled: false +postgres: + enabled: true + persistence: + size: 10Gi +redis: + enabled: true + persistence: + size: 2Gi +rustfs: + consoleEnable: false + enabled: true + persistence: + size: 20Gi diff --git a/deploy/helm/pargar/values-prod.yaml b/deploy/helm/pargar/values-prod.yaml index b4afcf4..b38efc4 100644 --- a/deploy/helm/pargar/values-prod.yaml +++ b/deploy/helm/pargar/values-prod.yaml @@ -22,7 +22,7 @@ backend: replicaCount: 2 image: repository: backend - tag: "0.2.0" + tag: "0.2.1" pullPolicy: IfNotPresent existingSecret: pargar-app env: @@ -49,7 +49,7 @@ frontend: replicaCount: 2 image: repository: frontend - tag: "0.2.0" + tag: "0.2.1" pullPolicy: IfNotPresent resources: requests: diff --git a/deploy/k3s/traefik-helmchartconfig.yaml b/deploy/k3s/traefik-helmchartconfig.yaml new file mode 100644 index 0000000..27307ef --- /dev/null +++ b/deploy/k3s/traefik-helmchartconfig.yaml @@ -0,0 +1,43 @@ +# k3s Traefik customisation (HelmChartConfig for the built-in `traefik` chart). +# +# Apply with: kubectl apply -f deploy/k3s/traefik-helmchartconfig.yaml +# +# Why: Traefik's entrypoint `readTimeout` defaults to 60s and covers reading the +# *entire request including the body*. Admin video uploads (up to 200MB) that take +# longer than 60s were cut off with a 502 before reaching the backend. Raise it to +# 1 hour (use 0 to disable entirely). +apiVersion: helm.cattle.io/v1 +kind: HelmChartConfig +metadata: + name: traefik + namespace: kube-system +spec: + failurePolicy: reinstall + valuesContent: |- + # ACCESSLOG tile -> ON; access logs go to container stdout (kubectl logs). + # Key is logs.access.enabled on the chart k3s ships (40.1.5); + # the top-level accessLog.enabled only exists on newer chart versions. + logs: + access: + enabled: true + # Allow IngressRoutes to reference Services living in other namespaces, + # e.g. argocd-server in `argocd`. + providers: + kubernetesCRD: + allowCrossNamespace: true + deployment: + podAnnotations: + # fix: metrics entrypoint listens on :9100, annotation said 8082 + prometheus.io/port: "9100" + prometheus.io/scrape: "true" + # Allow slow, large uploads (admin video upload ≤ 200MB) to finish. + # Default is 60s; 3600s = 1h. Set to 0 to disable the cap. + ports: + web: + transport: + respondingTimeouts: + readTimeout: 3600 + websecure: + transport: + respondingTimeouts: + readTimeout: 3600