diff --git a/packages/drizzle-pkg/database/sqlite/schema/content.ts b/packages/drizzle-pkg/database/sqlite/schema/content.ts
index 41db3a3..2c825e6 100644
--- a/packages/drizzle-pkg/database/sqlite/schema/content.ts
+++ b/packages/drizzle-pkg/database/sqlite/schema/content.ts
@@ -61,15 +61,14 @@ export const mediaAssets = sqliteTable(
export const mediaRefs = sqliteTable(
"media_refs",
{
- postId: integer("post_id")
- .notNull()
- .references(() => posts.id, { onDelete: "cascade" }),
+ ownerType: text("owner_type").notNull(),
+ ownerId: integer("owner_id").notNull(),
assetId: integer("asset_id")
.notNull()
.references(() => mediaAssets.id, { onDelete: "cascade" }),
},
(table) => [
- primaryKey({ columns: [table.postId, table.assetId] }),
+ primaryKey({ columns: [table.ownerType, table.ownerId, table.assetId] }),
index("media_refs_asset_id_idx").on(table.assetId),
],
);
diff --git a/packages/drizzle-pkg/db.sqlite b/packages/drizzle-pkg/db.sqlite
index 1ee67bc8a976faaa50288b289e430dffa13a8f7c..15b52ad2201665bb3423535fe4c564a18b5f99aa 100644
GIT binary patch
delta 549
zcmZozz|ru4V}i7xJ_7@TG7u{OF%uB0P1G@F)Zdt}gkOM-&xnD4Gk+StB;OgndOoAg
ziUM+cKHhAs42`_3Y01WxNvRfwre?{8CML<|riRHDiN;12$)-jY7KUlY<_1P-M#+g5
zmWd{omc|yz#s&svi6%y-riSK87O5uYjB{5{YTsNZ*TyKo#{Zas{}2Cn{!jdG`Csrq
z-Ygh!o}ZP4nSqh5`Gx-W7y6763mCb!GhSf)FE0ReA_y=7u{;pVZ5C{J%|HG3e@1Sx
zzYP2>{ER$zdAzwVb1mg6;QY(k!`TCLwHb#_lerwbxTYp!i)Kk;Qch}aYD#8ed{Jsz
zaeQ8CIVw+SdZR6)h%ihtK|zCyGa2@7XSbN
delta 259
zcmZp8z|pXPV}i7x9s>h|A`ruX(nK9&M!k&*OZfR%`RW$#XDr=PZB#_gB6nRHnIa|=je
diff --git a/packages/drizzle-pkg/migrations/0005_media_refs_owner_type.sql b/packages/drizzle-pkg/migrations/0005_media_refs_owner_type.sql
new file mode 100644
index 0000000..56b1f98
--- /dev/null
+++ b/packages/drizzle-pkg/migrations/0005_media_refs_owner_type.sql
@@ -0,0 +1,16 @@
+CREATE TABLE `media_refs_new` (
+ `owner_type` text NOT NULL,
+ `owner_id` integer NOT NULL,
+ `asset_id` integer NOT NULL,
+ PRIMARY KEY(`owner_type`, `owner_id`, `asset_id`),
+ FOREIGN KEY (`asset_id`) REFERENCES `media_assets`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+INSERT INTO `media_refs_new` (`owner_type`, `owner_id`, `asset_id`)
+SELECT 'post', `post_id`, `asset_id` FROM `media_refs`;
+--> statement-breakpoint
+DROP TABLE `media_refs`;
+--> statement-breakpoint
+ALTER TABLE `media_refs_new` RENAME TO `media_refs`;
+--> statement-breakpoint
+CREATE INDEX `media_refs_asset_id_idx` ON `media_refs` (`asset_id`);
diff --git a/packages/drizzle-pkg/migrations/meta/0005_snapshot.json b/packages/drizzle-pkg/migrations/meta/0005_snapshot.json
new file mode 100644
index 0000000..2562696
--- /dev/null
+++ b/packages/drizzle-pkg/migrations/meta/0005_snapshot.json
@@ -0,0 +1,1130 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "a1b2c3d4-e5f6-4789-a012-3456789abcde",
+ "prevId": "2b8a9201-fbd7-4993-871d-a351e56f204a",
+ "tables": {
+ "sessions": {
+ "name": "sessions",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "sessions_user_id_idx": {
+ "name": "sessions_user_id_idx",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "nickname": {
+ "name": "nickname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "avatar": {
+ "name": "avatar",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'user'"
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'active'"
+ },
+ "public_slug": {
+ "name": "public_slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "bio_markdown": {
+ "name": "bio_markdown",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "bio_visibility": {
+ "name": "bio_visibility",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'private'"
+ },
+ "social_links_json": {
+ "name": "social_links_json",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'[]'"
+ },
+ "avatar_visibility": {
+ "name": "avatar_visibility",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'private'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "users_username_unique": {
+ "name": "users_username_unique",
+ "columns": [
+ "username"
+ ],
+ "isUnique": true
+ },
+ "users_public_slug_unique": {
+ "name": "users_public_slug_unique",
+ "columns": [
+ "public_slug"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "app_configs": {
+ "name": "app_configs",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value_type": {
+ "name": "value_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "user_configs": {
+ "name": "user_configs",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value_type": {
+ "name": "value_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "user_configs_user_id_idx": {
+ "name": "user_configs_user_id_idx",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "user_configs_user_id_users_id_fk": {
+ "name": "user_configs_user_id_users_id_fk",
+ "tableFrom": "user_configs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "user_configs_user_id_key_pk": {
+ "columns": [
+ "user_id",
+ "key"
+ ],
+ "name": "user_configs_user_id_key_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "media_assets": {
+ "name": "media_assets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "storage_key": {
+ "name": "storage_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "mime": {
+ "name": "mime",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size_bytes": {
+ "name": "size_bytes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "sha256": {
+ "name": "sha256",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "variants_json": {
+ "name": "variants_json",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'ready'"
+ },
+ "first_referenced_at": {
+ "name": "first_referenced_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dereferenced_at": {
+ "name": "dereferenced_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "media_assets_storage_key_unique": {
+ "name": "media_assets_storage_key_unique",
+ "columns": [
+ "storage_key"
+ ],
+ "isUnique": true
+ },
+ "media_assets_user_id_idx": {
+ "name": "media_assets_user_id_idx",
+ "columns": [
+ "user_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "media_assets_user_id_users_id_fk": {
+ "name": "media_assets_user_id_users_id_fk",
+ "tableFrom": "media_assets",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "post_comments": {
+ "name": "post_comments",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "post_id": {
+ "name": "post_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "author_user_id": {
+ "name": "author_user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "guest_display_name": {
+ "name": "guest_display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "body": {
+ "name": "body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "deleted_by_user_id": {
+ "name": "deleted_by_user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "post_comments_post_id_idx": {
+ "name": "post_comments_post_id_idx",
+ "columns": [
+ "post_id"
+ ],
+ "isUnique": false
+ },
+ "post_comments_parent_id_idx": {
+ "name": "post_comments_parent_id_idx",
+ "columns": [
+ "parent_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "post_comments_post_id_posts_id_fk": {
+ "name": "post_comments_post_id_posts_id_fk",
+ "tableFrom": "post_comments",
+ "tableTo": "posts",
+ "columnsFrom": [
+ "post_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "post_comments_parent_id_post_comments_id_fk": {
+ "name": "post_comments_parent_id_post_comments_id_fk",
+ "tableFrom": "post_comments",
+ "tableTo": "post_comments",
+ "columnsFrom": [
+ "parent_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "post_comments_author_user_id_users_id_fk": {
+ "name": "post_comments_author_user_id_users_id_fk",
+ "tableFrom": "post_comments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "author_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "post_comments_deleted_by_user_id_users_id_fk": {
+ "name": "post_comments_deleted_by_user_id_users_id_fk",
+ "tableFrom": "post_comments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "deleted_by_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "posts": {
+ "name": "posts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "body_markdown": {
+ "name": "body_markdown",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "excerpt": {
+ "name": "excerpt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "cover_url": {
+ "name": "cover_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "tags_json": {
+ "name": "tags_json",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'[]'"
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "visibility": {
+ "name": "visibility",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'private'"
+ },
+ "share_token": {
+ "name": "share_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "posts_user_id_slug_unique": {
+ "name": "posts_user_id_slug_unique",
+ "columns": [
+ "user_id",
+ "slug"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "posts_user_id_users_id_fk": {
+ "name": "posts_user_id_users_id_fk",
+ "tableFrom": "posts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "timeline_events": {
+ "name": "timeline_events",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "occurred_on": {
+ "name": "occurred_on",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "body_markdown": {
+ "name": "body_markdown",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "link_url": {
+ "name": "link_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "visibility": {
+ "name": "visibility",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'private'"
+ },
+ "share_token": {
+ "name": "share_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "timeline_events_user_id_users_id_fk": {
+ "name": "timeline_events_user_id_users_id_fk",
+ "tableFrom": "timeline_events",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "rss_feeds": {
+ "name": "rss_feeds",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "feed_url": {
+ "name": "feed_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "site_url": {
+ "name": "site_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_fetched_at": {
+ "name": "last_fetched_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "poll_interval_minutes": {
+ "name": "poll_interval_minutes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "rss_feeds_user_id_feed_url_unique": {
+ "name": "rss_feeds_user_id_feed_url_unique",
+ "columns": [
+ "user_id",
+ "feed_url"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "rss_feeds_user_id_users_id_fk": {
+ "name": "rss_feeds_user_id_users_id_fk",
+ "tableFrom": "rss_feeds",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "rss_items": {
+ "name": "rss_items",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "feed_id": {
+ "name": "feed_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "guid": {
+ "name": "guid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "canonical_url": {
+ "name": "canonical_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "content_snippet": {
+ "name": "content_snippet",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "author": {
+ "name": "author",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "published_at": {
+ "name": "published_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "visibility": {
+ "name": "visibility",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'private'"
+ },
+ "share_token": {
+ "name": "share_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(cast((julianday('now') - 2440587.5)*86400000 as integer))"
+ }
+ },
+ "indexes": {
+ "rss_items_feed_id_guid_unique": {
+ "name": "rss_items_feed_id_guid_unique",
+ "columns": [
+ "feed_id",
+ "guid"
+ ],
+ "isUnique": true,
+ "where": "\"rss_items\".\"guid\" IS NOT NULL"
+ }
+ },
+ "foreignKeys": {
+ "rss_items_user_id_users_id_fk": {
+ "name": "rss_items_user_id_users_id_fk",
+ "tableFrom": "rss_items",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "rss_items_feed_id_rss_feeds_id_fk": {
+ "name": "rss_items_feed_id_rss_feeds_id_fk",
+ "tableFrom": "rss_items",
+ "tableTo": "rss_feeds",
+ "columnsFrom": [
+ "feed_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ },
+ "media_refs": {
+ "name": "media_refs",
+ "columns": {
+ "owner_type": {
+ "name": "owner_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "owner_id": {
+ "name": "owner_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "asset_id": {
+ "name": "asset_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "media_refs_asset_id_idx": {
+ "name": "media_refs_asset_id_idx",
+ "columns": [
+ "asset_id"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "media_refs_asset_id_media_assets_id_fk": {
+ "name": "media_refs_asset_id_media_assets_id_fk",
+ "tableFrom": "media_refs",
+ "tableTo": "media_assets",
+ "columnsFrom": [
+ "asset_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "media_refs_owner_type_owner_id_asset_id_pk": {
+ "columns": [
+ "owner_type",
+ "owner_id",
+ "asset_id"
+ ],
+ "name": "media_refs_owner_type_owner_id_asset_id_pk"
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraints": {}
+ }
+ },
+ "views": {},
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+}
diff --git a/packages/drizzle-pkg/migrations/meta/_journal.json b/packages/drizzle-pkg/migrations/meta/_journal.json
index daf2816..d083481 100644
--- a/packages/drizzle-pkg/migrations/meta/_journal.json
+++ b/packages/drizzle-pkg/migrations/meta/_journal.json
@@ -36,6 +36,13 @@
"when": 1776600000000,
"tag": "0004_rename_post_media_refs_to_media_refs",
"breakpoints": true
+ },
+ {
+ "idx": 5,
+ "version": "6",
+ "when": 1776700000000,
+ "tag": "0005_media_refs_owner_type",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/server/api/public/profile/[publicSlug]/about.get.ts b/server/api/public/profile/[publicSlug]/about.get.ts
new file mode 100644
index 0000000..1dcf622
--- /dev/null
+++ b/server/api/public/profile/[publicSlug]/about.get.ts
@@ -0,0 +1,34 @@
+import { dbGlobal } from "drizzle-pkg/lib/db";
+import { users } from "drizzle-pkg/lib/schema/auth";
+import { and, eq } from "drizzle-orm";
+
+export default defineEventHandler(async (event) => {
+ const publicSlug = event.context.params?.publicSlug;
+ if (!publicSlug || typeof publicSlug !== "string") {
+ throw createError({ statusCode: 400, statusMessage: "无效主页" });
+ }
+
+ const [owner] = await dbGlobal
+ .select()
+ .from(users)
+ .where(and(eq(users.publicSlug, publicSlug), eq(users.status, "active")))
+ .limit(1);
+
+ if (!owner) {
+ throw createError({ statusCode: 404, statusMessage: "未找到" });
+ }
+
+ const bioOk = owner.bioVisibility === "public" && Boolean(owner.bioMarkdown?.trim());
+ if (!bioOk) {
+ throw createError({ statusCode: 404, statusMessage: "未找到" });
+ }
+
+ return R.success({
+ user: {
+ publicSlug: owner.publicSlug,
+ nickname: owner.nickname,
+ avatar: owner.avatarVisibility === "public" ? owner.avatar : null,
+ },
+ bio: { markdown: owner.bioMarkdown as string },
+ });
+});
diff --git a/server/constants/media-refs.ts b/server/constants/media-refs.ts
new file mode 100644
index 0000000..1d94f75
--- /dev/null
+++ b/server/constants/media-refs.ts
@@ -0,0 +1,3 @@
+export const MEDIA_REF_OWNER_POST = "post" as const;
+export const MEDIA_REF_OWNER_PROFILE = "profile" as const;
+export type MediaRefOwnerType = typeof MEDIA_REF_OWNER_POST | typeof MEDIA_REF_OWNER_PROFILE;
diff --git a/server/service/media/index.ts b/server/service/media/index.ts
index 11387f3..0cfe854 100644
--- a/server/service/media/index.ts
+++ b/server/service/media/index.ts
@@ -9,7 +9,8 @@ import {
POST_MEDIA_PUBLIC_PREFIX,
RELATIVE_ASSETS_DIR,
} from "#server/constants/media";
-import { mergePostMediaUrls, publicAssetUrlToStorageKey } from "#server/utils/post-media-urls";
+import { MEDIA_REF_OWNER_POST, MEDIA_REF_OWNER_PROFILE } from "#server/constants/media-refs";
+import { mergePostMediaUrls, mergeProfileMediaUrls, publicAssetUrlToStorageKey } from "#server/utils/post-media-urls";
import { nextIntegerId } from "#server/utils/sqlite-id";
const NEVER_REF_MS = MEDIA_ORPHAN_GRACE_HOURS_NEVER_REF * 3600 * 1000;
@@ -188,13 +189,11 @@ export async function syncPostMediaRefs(
bodyMarkdown: string,
coverUrl: string | null,
): Promise {
- const beforeRows = await dbGlobal
- .select({ assetId: mediaRefs.assetId })
- .from(mediaRefs)
- .where(eq(mediaRefs.postId, postId));
+ const postRef = and(eq(mediaRefs.ownerType, MEDIA_REF_OWNER_POST), eq(mediaRefs.ownerId, postId));
+ const beforeRows = await dbGlobal.select({ assetId: mediaRefs.assetId }).from(mediaRefs).where(postRef);
const beforeIds = beforeRows.map((r) => r.assetId);
- await dbGlobal.delete(mediaRefs).where(eq(mediaRefs.postId, postId));
+ await dbGlobal.delete(mediaRefs).where(postRef);
const urls = mergePostMediaUrls(bodyMarkdown, coverUrl);
const keys = [...new Set(urls.map((u) => publicAssetUrlToStorageKey(u)).filter((k): k is string => k != null))];
@@ -209,7 +208,51 @@ export async function syncPostMediaRefs(
if (afterIds.length > 0) {
await dbGlobal
.insert(mediaRefs)
- .values(afterIds.map((assetId) => ({ postId, assetId })))
+ .values(
+ afterIds.map((assetId) => ({
+ ownerType: MEDIA_REF_OWNER_POST,
+ ownerId: postId,
+ assetId,
+ })),
+ )
+ .onConflictDoNothing();
+ }
+ }
+
+ await reconcileAssetTimestampsAfterRefChange([...new Set([...beforeIds, ...afterIds])]);
+}
+
+export async function syncProfileMediaRefs(
+ userId: number,
+ bioMarkdown: string | null,
+ avatar: string | null,
+): Promise {
+ const profileRef = and(eq(mediaRefs.ownerType, MEDIA_REF_OWNER_PROFILE), eq(mediaRefs.ownerId, userId));
+ const beforeRows = await dbGlobal.select({ assetId: mediaRefs.assetId }).from(mediaRefs).where(profileRef);
+ const beforeIds = beforeRows.map((r) => r.assetId);
+
+ await dbGlobal.delete(mediaRefs).where(profileRef);
+
+ const urls = mergeProfileMediaUrls(bioMarkdown, avatar);
+ const keys = [...new Set(urls.map((u) => publicAssetUrlToStorageKey(u)).filter((k): k is string => k != null))];
+
+ let afterIds: number[] = [];
+ if (keys.length > 0) {
+ const assetRows = await dbGlobal
+ .select({ id: mediaAssets.id })
+ .from(mediaAssets)
+ .where(and(eq(mediaAssets.userId, userId), inArray(mediaAssets.storageKey, keys)));
+ afterIds = assetRows.map((r) => r.id);
+ if (afterIds.length > 0) {
+ await dbGlobal
+ .insert(mediaRefs)
+ .values(
+ afterIds.map((assetId) => ({
+ ownerType: MEDIA_REF_OWNER_PROFILE,
+ ownerId: userId,
+ assetId,
+ })),
+ )
.onConflictDoNothing();
}
}
@@ -288,7 +331,7 @@ async function assertAssetDeletableOrThrow(row: typeof mediaAssets.$inferSelect)
.from(mediaRefs)
.where(eq(mediaRefs.assetId, row.id));
if (c > 0) {
- throw createError({ statusCode: 400, statusMessage: "资源仍被文章引用,无法删除" });
+ throw createError({ statusCode: 400, statusMessage: "资源仍被引用,无法删除" });
}
if (!isAssetDeletable(row)) {
throw createError({ statusCode: 400, statusMessage: "资源尚在宽限期,暂不可删除" });
diff --git a/server/service/posts/index.ts b/server/service/posts/index.ts
index ca16b61..5a3a895 100644
--- a/server/service/posts/index.ts
+++ b/server/service/posts/index.ts
@@ -3,6 +3,7 @@ import { mediaRefs, posts } from "drizzle-pkg/lib/schema/content";
import { reconcileAssetTimestampsAfterRefChange, syncPostMediaRefs } from "#server/service/media";
import { users } from "drizzle-pkg/lib/schema/auth";
import { and, count, desc, eq } from "drizzle-orm";
+import { MEDIA_REF_OWNER_POST } from "#server/constants/media-refs";
import { PUBLIC_LIST_PAGE_SIZE, PUBLIC_PREVIEW_LIMIT } from "#server/constants/public-profile-lists";
import { visibilitySchema, type Visibility } from "#server/constants/visibility";
import { normalizePublicListPage } from "#server/utils/public-pagination";
@@ -120,11 +121,10 @@ export async function deletePost(userId: number, id: number) {
if (!existing) {
return false;
}
- const refRows = await dbGlobal
- .select({ assetId: mediaRefs.assetId })
- .from(mediaRefs)
- .where(eq(mediaRefs.postId, id));
+ const postRef = and(eq(mediaRefs.ownerType, MEDIA_REF_OWNER_POST), eq(mediaRefs.ownerId, id));
+ const refRows = await dbGlobal.select({ assetId: mediaRefs.assetId }).from(mediaRefs).where(postRef);
const touched = refRows.map((r) => r.assetId);
+ await dbGlobal.delete(mediaRefs).where(postRef);
await dbGlobal.delete(posts).where(and(eq(posts.id, id), eq(posts.userId, userId)));
await reconcileAssetTimestampsAfterRefChange(touched);
return true;
diff --git a/server/service/profile/index.ts b/server/service/profile/index.ts
index 8329370..5ef4a39 100644
--- a/server/service/profile/index.ts
+++ b/server/service/profile/index.ts
@@ -2,6 +2,7 @@ import { dbGlobal } from "drizzle-pkg/lib/db";
import { users } from "drizzle-pkg/lib/schema/auth";
import { eq } from "drizzle-orm";
import { visibilitySchema, type Visibility } from "#server/constants/visibility";
+import { syncProfileMediaRefs } from "#server/service/media";
import { z } from "zod";
const publicSlugValue = z
@@ -68,8 +69,14 @@ export async function updateProfile(
return getProfileRow(userId);
}
+ const syncMedia = patch.bioMarkdown !== undefined || patch.avatar !== undefined;
+
await dbGlobal.update(users).set(updates as never).where(eq(users.id, userId));
- return getProfileRow(userId);
+ const row = await getProfileRow(userId);
+ if (row && syncMedia) {
+ await syncProfileMediaRefs(userId, row.bioMarkdown ?? null, row.avatar ?? null);
+ }
+ return row;
}
export function parseSocialLinksJson(raw: string | null | undefined): SocialLinkItem[] {
diff --git a/server/utils/post-media-urls.ts b/server/utils/post-media-urls.ts
index 52e27e4..c7a529f 100644
--- a/server/utils/post-media-urls.ts
+++ b/server/utils/post-media-urls.ts
@@ -47,6 +47,10 @@ export function mergePostMediaUrls(bodyMarkdown: string, coverUrl: string | null
return a;
}
+export function mergeProfileMediaUrls(bioMarkdown: string | null | undefined, avatar: string | null | undefined): string[] {
+ return mergePostMediaUrls(bioMarkdown ?? "", avatar ?? null);
+}
+
/** `/public/assets/foo.webp` → `foo.webp` */
export function publicAssetUrlToStorageKey(url: string): string | null {
const t = url.trim();