249 lines
5.3 KiB
Go

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")
}