feat: add image management functionality

- Implemented image folder repository with CRUD operations.
- Added image reference repository for managing image references to articles.
- Created image repository for handling image assets, including listing, inserting, updating, and deleting images.
- Introduced image usage repository to track storage usage and quotas for tenants.
- Added SQL queries for image assets, folders, references, and usage.
- Developed image handler for HTTP endpoints to manage images and folders.
- Created database migration scripts for image-related tables and structures.
This commit is contained in:
2026-04-16 20:40:41 +08:00
parent 0a3558fc51
commit 27389164b0
57 changed files with 8063 additions and 153 deletions
@@ -0,0 +1,81 @@
-- name: ListImageAssets :many
SELECT id, tenant_id, folder_id, object_key, name, size_bytes, status, created_by, created_at, updated_at
FROM image_assets
WHERE tenant_id = sqlc.arg(tenant_id)
AND status = 'active'
AND deleted_at IS NULL
AND (
sqlc.narg(folder_id)::BIGINT IS NULL
OR (sqlc.narg(folder_id)::BIGINT = 0 AND folder_id IS NULL)
OR folder_id = sqlc.narg(folder_id)::BIGINT
)
AND (sqlc.narg(search_query)::TEXT IS NULL OR name ILIKE '%' || sqlc.narg(search_query)::TEXT || '%')
ORDER BY created_at DESC
LIMIT sqlc.arg(page_size) OFFSET sqlc.arg(page_offset);
-- name: CountImageAssets :one
SELECT COUNT(*)::INT AS total
FROM image_assets
WHERE tenant_id = sqlc.arg(tenant_id)
AND status = 'active'
AND deleted_at IS NULL
AND (
sqlc.narg(folder_id)::BIGINT IS NULL
OR (sqlc.narg(folder_id)::BIGINT = 0 AND folder_id IS NULL)
OR folder_id = sqlc.narg(folder_id)::BIGINT
)
AND (sqlc.narg(search_query)::TEXT IS NULL OR name ILIKE '%' || sqlc.narg(search_query)::TEXT || '%');
-- name: GetImageAsset :one
SELECT id, tenant_id, folder_id, object_key, name, size_bytes, status, created_by, created_at, updated_at
FROM image_assets
WHERE id = sqlc.arg(id) AND tenant_id = sqlc.arg(tenant_id);
-- name: InsertImageAsset :one
INSERT INTO image_assets (tenant_id, folder_id, object_key, name, size_bytes, status, created_by)
VALUES (sqlc.arg(tenant_id), sqlc.narg(folder_id), sqlc.arg(object_key), sqlc.arg(name), sqlc.arg(size_bytes), sqlc.arg(status), sqlc.arg(created_by))
RETURNING id;
-- name: UpdateImageAssetStatus :exec
UPDATE image_assets
SET status = sqlc.arg(status), updated_at = NOW()
WHERE id = sqlc.arg(id) AND tenant_id = sqlc.arg(tenant_id);
-- name: UpdateImageAssetDeleted :exec
UPDATE image_assets
SET status = 'deleted', deleted_at = NOW(), updated_at = NOW()
WHERE id = sqlc.arg(id) AND tenant_id = sqlc.arg(tenant_id);
-- name: UpdateImageAssetName :exec
UPDATE image_assets
SET name = sqlc.arg(name), updated_at = NOW()
WHERE id = sqlc.arg(id) AND tenant_id = sqlc.arg(tenant_id);
-- name: UpdateImageAssetFolder :exec
UPDATE image_assets
SET folder_id = sqlc.narg(folder_id), updated_at = NOW()
WHERE id = sqlc.arg(id) AND tenant_id = sqlc.arg(tenant_id);
-- name: ListActiveImagesByFolder :many
SELECT id, object_key, size_bytes
FROM image_assets
WHERE tenant_id = sqlc.arg(tenant_id)
AND folder_id = sqlc.arg(folder_id)
AND status = 'active'
AND deleted_at IS NULL;
-- name: MarkImagesInFolderPendingDelete :exec
UPDATE image_assets
SET status = 'pending_delete', folder_id = NULL, updated_at = NOW()
WHERE tenant_id = sqlc.arg(tenant_id)
AND folder_id = sqlc.arg(folder_id)
AND status = 'active'
AND deleted_at IS NULL;
-- name: CountUncategorizedImages :one
SELECT COUNT(*)::INT AS count
FROM image_assets
WHERE tenant_id = sqlc.arg(tenant_id)
AND folder_id IS NULL
AND status = 'active'
AND deleted_at IS NULL;
@@ -0,0 +1,35 @@
-- name: ListImageFolders :many
SELECT f.id, f.tenant_id, f.name, f.created_at, f.updated_at,
COUNT(ia.id) FILTER (WHERE ia.status = 'active' AND ia.deleted_at IS NULL)::INT AS image_count
FROM image_folders f
LEFT JOIN image_assets ia ON ia.tenant_id = f.tenant_id AND ia.folder_id = f.id
WHERE f.tenant_id = sqlc.arg(tenant_id)
GROUP BY f.id
ORDER BY f.name;
-- name: CreateImageFolder :one
INSERT INTO image_folders (tenant_id, name)
VALUES (sqlc.arg(tenant_id), sqlc.arg(name))
RETURNING id;
-- name: UpdateImageFolder :exec
UPDATE image_folders
SET name = sqlc.arg(name), updated_at = NOW()
WHERE id = sqlc.arg(id) AND tenant_id = sqlc.arg(tenant_id);
-- name: GetImageFolder :one
SELECT id, tenant_id, name, created_at, updated_at
FROM image_folders
WHERE id = sqlc.arg(id) AND tenant_id = sqlc.arg(tenant_id);
-- name: DeleteImageFolder :exec
DELETE FROM image_folders
WHERE id = sqlc.arg(id) AND tenant_id = sqlc.arg(tenant_id);
-- name: CountActiveImagesInFolder :one
SELECT COUNT(*)::INT AS count
FROM image_assets
WHERE tenant_id = sqlc.arg(tenant_id)
AND folder_id = sqlc.arg(folder_id)
AND status = 'active'
AND deleted_at IS NULL;
@@ -0,0 +1,27 @@
-- name: ListImageReferences :many
SELECT r.id, r.tenant_id, r.image_asset_id, r.article_id, r.ref_scope, r.created_at
FROM image_asset_references r
WHERE r.tenant_id = sqlc.arg(tenant_id) AND r.image_asset_id = sqlc.arg(image_asset_id);
-- name: CountImageReferences :one
SELECT COUNT(*)::INT AS total
FROM image_asset_references
WHERE tenant_id = sqlc.arg(tenant_id) AND image_asset_id = sqlc.arg(image_asset_id);
-- name: UpsertImageReference :exec
INSERT INTO image_asset_references (tenant_id, image_asset_id, article_id, ref_scope)
VALUES (sqlc.arg(tenant_id), sqlc.arg(image_asset_id), sqlc.arg(article_id), sqlc.arg(ref_scope))
ON CONFLICT (image_asset_id, article_id, ref_scope) DO UPDATE SET updated_at = NOW();
-- name: DeleteImageReferencesByArticle :exec
DELETE FROM image_asset_references
WHERE tenant_id = sqlc.arg(tenant_id) AND article_id = sqlc.arg(article_id);
-- name: DeleteImageReferencesByArticleScope :exec
DELETE FROM image_asset_references
WHERE tenant_id = sqlc.arg(tenant_id) AND article_id = sqlc.arg(article_id) AND ref_scope = sqlc.arg(ref_scope);
-- name: ListImageReferenceArticles :many
SELECT DISTINCT r.article_id, r.ref_scope
FROM image_asset_references r
WHERE r.tenant_id = sqlc.arg(tenant_id) AND r.image_asset_id = sqlc.arg(image_asset_id);
@@ -0,0 +1,15 @@
-- name: EnsureImageStorageUsageRow :exec
INSERT INTO tenant_image_storage_usage (tenant_id, used_bytes)
VALUES (sqlc.arg(tenant_id), 0)
ON CONFLICT (tenant_id) DO NOTHING;
-- name: GetImageStorageUsage :one
SELECT used_bytes, updated_at
FROM tenant_image_storage_usage
WHERE tenant_id = sqlc.arg(tenant_id);
-- name: ReleaseImageStorageQuota :exec
UPDATE tenant_image_storage_usage
SET used_bytes = GREATEST(0, used_bytes - sqlc.arg(release_bytes)),
updated_at = NOW()
WHERE tenant_id = sqlc.arg(tenant_id);