Asset Content Category Implementation Plan¶
Overview¶
Add a content_category field to assets to distinguish storage mechanisms:
- binary: Files stored in S3 (current behavior)
- text: Content stored directly in the database
This is extensible for future types (e.g., url, embed).
Files to Modify¶
Backend¶
| File | Changes |
|---|---|
backend/internal/db/migrations/005_asset_content_category.sql |
New migration (create) |
backend/internal/models/asset.go |
Add fields + new request type |
backend/internal/db/asset_repository.go |
Update interface |
backend/internal/db/asset.go |
Update SQL queries |
backend/internal/services/asset.go |
Add CreateTextAsset, modify existing methods |
backend/internal/handlers/asset.go |
Add text asset endpoints |
API¶
| File | Changes |
|---|---|
api/components/schemas/assets.yaml |
Add ContentCategory enum, update Asset schema |
api/paths/assets.yaml |
Add text asset endpoints |
Frontend¶
| File | Changes |
|---|---|
frontend/src/pages/dashboard/assets/asset-upload.tsx |
Add category selector + text input UI |
frontend/src/pages/dashboard/assets/asset-list.tsx |
Display content category |
Implementation Steps¶
1. Database Migration (005_asset_content_category.sql)¶
-- Add content_category column (default 'binary' for existing assets)
ALTER TABLE assets
ADD COLUMN content_category VARCHAR(20) NOT NULL DEFAULT 'binary'
CHECK (content_category IN ('binary', 'text'));
-- Add text_content column for storing text directly
ALTER TABLE assets ADD COLUMN text_content TEXT;
-- Make storage_key nullable (not needed for text assets)
ALTER TABLE assets ALTER COLUMN storage_key DROP NOT NULL;
-- Add consistency constraint
ALTER TABLE assets ADD CONSTRAINT assets_content_consistency CHECK (
(content_category = 'binary' AND storage_key IS NOT NULL AND storage_key != '') OR
(content_category = 'text' AND text_content IS NOT NULL)
);
-- Index for filtering
CREATE INDEX IF NOT EXISTS idx_assets_content_category ON assets(content_category);
2. Backend Model (models/asset.go)¶
Add to Asset struct:
- ContentCategory string - 'binary' or 'text'
- StorageKey *string - make nullable (pointer)
- TextContent *string - content for text assets
Add constants:
const (
ContentCategoryBinary = "binary"
ContentCategoryText = "text"
MaxTextContentBytes = 1 * 1024 * 1024 // 1 MB
)
Add new request type:
type CreateTextAssetRequest struct {
OrgID string
Name string
Description string
ContentType string // e.g., text/plain, text/markdown
TextContent string
}
3. Repository Updates (db/asset.go)¶
Update all queries to include content_category and text_content columns.
Use sql.NullString for nullable storage_key and text_content.
4. Service Layer (services/asset.go)¶
Add CreateTextAsset() method:
- Validate org exists
- Check license quotas (count text bytes as storage)
- Enforce 1 MB max for text content
- Create asset with content_category='text'
- Store content in text_content column
- No S3 interaction
Add UpdateTextContent() method:
- Allow editing text content after creation
- Recalculate size_bytes on update
- Enforce 1 MB max limit
Modify DeleteAsset():
- Only call S3 delete for binary assets
Modify GetDownloadURL():
- Return error for text assets (use content endpoint instead)
5. Handler Updates (handlers/asset.go)¶
Add new endpoints:
- POST /orgs/{org_id}/assets/text - Create text asset
- GET /orgs/{org_id}/assets/{id}/content - Get text content
- PUT /orgs/{org_id}/assets/{id}/content - Update text content
6. API Schema Updates¶
Add to assets.yaml:
ContentCategory:
type: string
enum: [binary, text]
CreateTextAssetRequest:
type: object
required: [name, content_type, text_content]
properties:
name: {type: string}
description: {type: string}
content_type: {type: string}
text_content: {type: string}
Update Asset schema:
- Add content_category (required)
- Make storage_key nullable
- Add text_content (nullable)
7. Frontend Updates¶
asset-upload.tsx:
- Add radio/toggle: "Binary (File)" vs "Text"
- When Text selected:
- Hide file dropzone
- Show content type dropdown (text/plain, text/markdown, text/html, application/json)
- Show textarea for content
- Call new createTextAsset API
asset-list.tsx: - Add "Category" column with badge (binary/text) - For text assets: show "View" instead of download
Verification¶
- Run migration and verify existing assets have
content_category='binary' - Create binary asset - should work as before with S3 upload
- Create text asset - should save to DB without S3 interaction
- Create text asset > 1 MB - should fail with size limit error
- List assets - both types should appear with category badge
- Download binary asset - returns presigned S3 URL
- Get text asset content - returns text from database
- Update text asset content - should update content and recalculate size
- Delete both types - binary deletes from S3, text only from DB
- Quota enforcement - text content bytes count toward storage limit
Future Extensibility¶
To add new categories (e.g., 'url', 'embed'):
1. Add to migration CHECK constraint
2. Add column for new data (e.g., url_reference)
3. Add new request type and service method
4. Add new API endpoint
5. Update frontend UI mode