| 34 | } |
| 35 | |
| 36 | func (s *Store) CreateCollection(workspaceID string, input models.CollectionCreate) (*models.Collection, error) { |
| 37 | id := newID() |
| 38 | ts := now() |
| 39 | |
| 40 | schema := input.Schema |
| 41 | if schema == "" { |
| 42 | schema = `{"fields":[]}` |
| 43 | } |
| 44 | settings := input.Settings |
| 45 | if settings == "" { |
| 46 | settings = "{}" |
| 47 | } |
| 48 | icon := input.Icon |
| 49 | description := input.Description |
| 50 | |
| 51 | prefix := input.Prefix |
| 52 | if prefix == "" { |
| 53 | prefix = collections.DerivePrefix(input.Name) |
| 54 | } |
| 55 | if prefix == "" { |
| 56 | prefix = "ITEM" |
| 57 | } |
| 58 | |
| 59 | baseSlug := input.Slug |
| 60 | if baseSlug == "" { |
| 61 | baseSlug = slugify(input.Name) |
| 62 | } |
| 63 | if baseSlug == "" { |
| 64 | baseSlug = "collection" |
| 65 | } |
| 66 | // Avoid slugs that collide with workspace-level UI routes |
| 67 | if isReservedCollectionSlug(baseSlug) { |
| 68 | baseSlug = baseSlug + "-collection" |
| 69 | } |
| 70 | slug, err := s.uniqueSlug("collections", "workspace_id", workspaceID, baseSlug) |
| 71 | if err != nil { |
| 72 | return nil, fmt.Errorf("unique slug: %w", err) |
| 73 | } |
| 74 | |
| 75 | _, err = s.db.Exec(s.q(` |
| 76 | INSERT INTO collections (id, workspace_id, name, slug, prefix, icon, description, schema, settings, sort_order, is_default, is_system, created_at, updated_at) |
| 77 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
| 78 | `), id, workspaceID, input.Name, slug, prefix, icon, description, schema, settings, 0, s.dialect.BoolToInt(input.IsDefault), s.dialect.BoolToInt(input.IsSystem), ts, ts) |
| 79 | if err != nil { |
| 80 | return nil, fmt.Errorf("insert collection: %w", err) |
| 81 | } |
| 82 | |
| 83 | return s.GetCollection(id) |
| 84 | } |
| 85 | |
| 86 | // collectionColumns is the ONE full-row projection of the collections table. |
| 87 | // Every accessor that hydrates a COMPLETE models.Collection from a single row |