249 lines
5.3 KiB
Go
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")
|
|
}
|