package main import ( "context" "errors" "fmt" "time" "github.com/google/uuid" ) func (a *app) resolveCollection(name string) (*pgCollection, error) { switch name { case "pages", "authoring_runs", "datasources", "scorm_courses", "scorm_exports", "users", "enrollments", "consent_receipts", "data_subject_requests", "webhooks", "forms", "form_submissions", "sitemap_menus", "audit_events", "assets", "asset_access_events", "asset_claims", "notification_templates", "notification_preferences", "notification_deliveries", "notification_queue", "notification_delivery_attempts", "notification_dead_letters", "notification_rate_limit_policies", "notification_provider_configs", "notifications", "blog_posts", "blog_categories", "blog_tags", "blog_authors", "entitlements", "live_courses", "live_course_cohorts", "live_course_sessions", "live_course_enrollments", "live_course_attendance", "communities", "community_channels", "community_memberships", "community_posts", "community_replies", "keycloak_themes", "keycloak_theme_policy", "keycloak_theme_exports", "keycloak_theme_deployments_current", "keycloak_theme_deployments_audit", "platform_settings", "admin_settings", "billing_customers", "billing_events", "billing_price_policies", "tenants": return a.db.Collection(name), nil default: return nil, errors.New("invalid collection") } } func (a *app) list(collection string, filter Doc, sort Sort) ([]Doc, error) { col, err := a.resolveCollection(collection) if err != nil { return nil, err } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() opts := NewFindOptions() if len(sort) > 0 { opts.SetSort(sort) } cursor, err := col.Find(ctx, filter, opts) if err != nil { return nil, err } defer cursor.Close(ctx) var docs []Doc if err := cursor.All(ctx, &docs); err != nil { return nil, err } if docs == nil { docs = []Doc{} } for _, doc := range docs { normalizeDocument(doc) } return docs, nil } func (a *app) getByID(collection, id string) (Doc, error) { return a.getByIDScoped(collection, id, "", false) } func (a *app) getByIDScoped(collection, id, tenantID string, enforceTenant bool) (Doc, error) { col, err := a.resolveCollection(collection) if err != nil { return nil, err } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() filter := Doc{"id": id} if enforceTenant { filter["tenantId"] = tenantID } var doc Doc err = col.FindOne(ctx, filter).Decode(&doc) if err != nil { return nil, err } normalizeDocument(doc) return doc, nil } func (a *app) create(collection string, payload Doc) (Doc, error) { col, err := a.resolveCollection(collection) if err != nil { return nil, err } now := time.Now().UnixMilli() if _, ok := payload["id"]; !ok { payload["id"] = uuid.NewString() } if _, ok := payload["createdAt"]; !ok { payload["createdAt"] = now } payload["updatedAt"] = now ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() _, err = col.InsertOne(ctx, payload) if err != nil { return nil, err } normalizeDocument(payload) return payload, nil } func (a *app) patch(collection, id string, patch Doc) (Doc, error) { return a.patchWithUnset(collection, id, patch, nil) } func (a *app) patchWithUnset(collection, id string, patch Doc, unset Doc) (Doc, error) { return a.patchWithUnsetScoped(collection, id, patch, unset, "", false) } func (a *app) patchWithUnsetScoped(collection, id string, patch Doc, unset Doc, tenantID string, enforceTenant bool) (Doc, error) { col, err := a.resolveCollection(collection) if err != nil { return nil, err } delete(patch, "id") delete(patch, "createdAt") patch["updatedAt"] = time.Now().UnixMilli() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() update := Doc{"$set": patch} if len(unset) > 0 { update["$unset"] = unset } result := col.FindOneAndUpdate( ctx, func() Doc { filter := Doc{"id": id} if enforceTenant { filter["tenantId"] = tenantID } return filter }(), update, NewFindOneAndUpdateOptions().SetReturnDocument(ReturnAfter), ) var updated Doc if err := result.Decode(&updated); err != nil { return nil, err } normalizeDocument(updated) return updated, nil } func (a *app) deleteByID(collection, id string) error { return a.deleteByIDScoped(collection, id, "", false) } func (a *app) deleteByIDScoped(collection, id, tenantID string, enforceTenant bool) error { col, err := a.resolveCollection(collection) if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() filter := Doc{"id": id} if enforceTenant { filter["tenantId"] = tenantID } res, err := col.DeleteOne(ctx, filter) if err != nil { return err } if res.DeletedCount == 0 { return ErrNotFound } return nil } func (a *app) createScormExport(courseID, version string) (Doc, error) { export := Doc{ "id": uuid.NewString(), "courseId": courseID, "version": version, "storageKey": fmt.Sprintf("exports/%s/%d.zip", courseID, time.Now().UnixNano()), "createdAt": time.Now().UnixMilli(), } return a.create("scorm_exports", export) } func normalizeDocument(doc Doc) { delete(doc, "_id") }