aboutsummaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/db/drizzle/0030_blue_synch.sql12
-rw-r--r--packages/db/drizzle/meta/0030_snapshot.json1222
-rw-r--r--packages/db/drizzle/meta/_journal.json7
-rw-r--r--packages/db/schema.ts18
-rw-r--r--packages/shared/assetdb.ts12
-rw-r--r--packages/shared/types/bookmarks.ts2
-rw-r--r--packages/trpc/lib/attachments.ts19
-rw-r--r--packages/trpc/routers/bookmarks.test.ts20
-rw-r--r--packages/trpc/routers/bookmarks.ts156
-rw-r--r--packages/trpc/testUtils.ts5
10 files changed, 1414 insertions, 59 deletions
diff --git a/packages/db/drizzle/0030_blue_synch.sql b/packages/db/drizzle/0030_blue_synch.sql
new file mode 100644
index 00000000..9b1bac33
--- /dev/null
+++ b/packages/db/drizzle/0030_blue_synch.sql
@@ -0,0 +1,12 @@
+ALTER TABLE `assets` ADD `size` integer DEFAULT 0 NOT NULL;--> statement-breakpoint
+ALTER TABLE `assets` ADD `contentType` text;--> statement-breakpoint
+ALTER TABLE `assets` ADD `fileName` text;--> statement-breakpoint
+INSERT INTO `assets` (`id`, `assetType`, `bookmarkId`, `userId`, `fileName`)
+ SELECT
+ `bookmarkAssets`.`assetId`,
+ 'bookmarkAsset',
+ `bookmarkAssets`.`id`,
+ (SELECT `bookmarks`.`userId` FROM `bookmarks` WHERE `bookmarks`.`id` = `bookmarkAssets`.`id`),
+ `bookmarkAssets`.`fileName`
+ FROM `bookmarkAssets`;
+
diff --git a/packages/db/drizzle/meta/0030_snapshot.json b/packages/db/drizzle/meta/0030_snapshot.json
new file mode 100644
index 00000000..5286196b
--- /dev/null
+++ b/packages/db/drizzle/meta/0030_snapshot.json
@@ -0,0 +1,1222 @@
+{
+ "version": "6",
+ "dialect": "sqlite",
+ "id": "3188359f-c324-4fca-be40-4903de779a2d",
+ "prevId": "e64dea2c-6fd3-4d93-8258-b1f6babe4d07",
+ "tables": {
+ "account": {
+ "name": "account",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "session_state": {
+ "name": "session_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId_pk": {
+ "columns": [
+ "provider",
+ "providerAccountId"
+ ],
+ "name": "account_provider_providerAccountId_pk"
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "apiKey": {
+ "name": "apiKey",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "keyId": {
+ "name": "keyId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "keyHash": {
+ "name": "keyHash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "apiKey_keyId_unique": {
+ "name": "apiKey_keyId_unique",
+ "columns": [
+ "keyId"
+ ],
+ "isUnique": true
+ },
+ "apiKey_name_userId_unique": {
+ "name": "apiKey_name_userId_unique",
+ "columns": [
+ "name",
+ "userId"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "apiKey_userId_user_id_fk": {
+ "name": "apiKey_userId_user_id_fk",
+ "tableFrom": "apiKey",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "assets": {
+ "name": "assets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetType": {
+ "name": "assetType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "size": {
+ "name": "size",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 0
+ },
+ "contentType": {
+ "name": "contentType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "assets_bookmarkId_idx": {
+ "name": "assets_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "assets_assetType_idx": {
+ "name": "assets_assetType_idx",
+ "columns": [
+ "assetType"
+ ],
+ "isUnique": false
+ },
+ "assets_userId_idx": {
+ "name": "assets_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "assets_bookmarkId_bookmarks_id_fk": {
+ "name": "assets_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "assets",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "assets_userId_user_id_fk": {
+ "name": "assets_userId_user_id_fk",
+ "tableFrom": "assets",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "bookmarkAssets": {
+ "name": "bookmarkAssets",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetType": {
+ "name": "assetType",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "assetId": {
+ "name": "assetId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "fileName": {
+ "name": "fileName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sourceUrl": {
+ "name": "sourceUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bookmarkAssets_id_bookmarks_id_fk": {
+ "name": "bookmarkAssets_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkAssets",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "bookmarkLinks": {
+ "name": "bookmarkLinks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "imageUrl": {
+ "name": "imageUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "favicon": {
+ "name": "favicon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "htmlContent": {
+ "name": "htmlContent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "crawledAt": {
+ "name": "crawledAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "crawlStatus": {
+ "name": "crawlStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ }
+ },
+ "indexes": {
+ "bookmarkLinks_url_idx": {
+ "name": "bookmarkLinks_url_idx",
+ "columns": [
+ "url"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarkLinks_id_bookmarks_id_fk": {
+ "name": "bookmarkLinks_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkLinks",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "bookmarkLists": {
+ "name": "bookmarkLists",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "parentId": {
+ "name": "parentId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarkLists_userId_idx": {
+ "name": "bookmarkLists_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarkLists_userId_user_id_fk": {
+ "name": "bookmarkLists_userId_user_id_fk",
+ "tableFrom": "bookmarkLists",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bookmarkLists_parentId_bookmarkLists_id_fk": {
+ "name": "bookmarkLists_parentId_bookmarkLists_id_fk",
+ "tableFrom": "bookmarkLists",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "parentId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "bookmarkTags": {
+ "name": "bookmarkTags",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarkTags_name_idx": {
+ "name": "bookmarkTags_name_idx",
+ "columns": [
+ "name"
+ ],
+ "isUnique": false
+ },
+ "bookmarkTags_userId_idx": {
+ "name": "bookmarkTags_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarkTags_userId_name_unique": {
+ "name": "bookmarkTags_userId_name_unique",
+ "columns": [
+ "userId",
+ "name"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "bookmarkTags_userId_user_id_fk": {
+ "name": "bookmarkTags_userId_user_id_fk",
+ "tableFrom": "bookmarkTags",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "bookmarkTexts": {
+ "name": "bookmarkTexts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "sourceUrl": {
+ "name": "sourceUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bookmarkTexts_id_bookmarks_id_fk": {
+ "name": "bookmarkTexts_id_bookmarks_id_fk",
+ "tableFrom": "bookmarkTexts",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "bookmarks": {
+ "name": "bookmarks",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "archived": {
+ "name": "archived",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "favourited": {
+ "name": "favourited",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "taggingStatus": {
+ "name": "taggingStatus",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarks_userId_idx": {
+ "name": "bookmarks_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_archived_idx": {
+ "name": "bookmarks_archived_idx",
+ "columns": [
+ "archived"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_favourited_idx": {
+ "name": "bookmarks_favourited_idx",
+ "columns": [
+ "favourited"
+ ],
+ "isUnique": false
+ },
+ "bookmarks_createdAt_idx": {
+ "name": "bookmarks_createdAt_idx",
+ "columns": [
+ "createdAt"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarks_userId_user_id_fk": {
+ "name": "bookmarks_userId_user_id_fk",
+ "tableFrom": "bookmarks",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "bookmarksInLists": {
+ "name": "bookmarksInLists",
+ "columns": {
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "listId": {
+ "name": "listId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "addedAt": {
+ "name": "addedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "bookmarksInLists_bookmarkId_idx": {
+ "name": "bookmarksInLists_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "bookmarksInLists_listId_idx": {
+ "name": "bookmarksInLists_listId_idx",
+ "columns": [
+ "listId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "bookmarksInLists_bookmarkId_bookmarks_id_fk": {
+ "name": "bookmarksInLists_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "bookmarksInLists",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "bookmarksInLists_listId_bookmarkLists_id_fk": {
+ "name": "bookmarksInLists_listId_bookmarkLists_id_fk",
+ "tableFrom": "bookmarksInLists",
+ "tableTo": "bookmarkLists",
+ "columnsFrom": [
+ "listId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "bookmarksInLists_bookmarkId_listId_pk": {
+ "columns": [
+ "bookmarkId",
+ "listId"
+ ],
+ "name": "bookmarksInLists_bookmarkId_listId_pk"
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "config": {
+ "name": "config",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "customPrompts": {
+ "name": "customPrompts",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "text": {
+ "name": "text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "attachedBy": {
+ "name": "attachedBy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "customPrompts_userId_idx": {
+ "name": "customPrompts_userId_idx",
+ "columns": [
+ "userId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "customPrompts_userId_user_id_fk": {
+ "name": "customPrompts_userId_user_id_fk",
+ "tableFrom": "customPrompts",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "session": {
+ "name": "session",
+ "columns": {
+ "sessionToken": {
+ "name": "sessionToken",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_userId_user_id_fk": {
+ "name": "session_userId_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "tagsOnBookmarks": {
+ "name": "tagsOnBookmarks",
+ "columns": {
+ "bookmarkId": {
+ "name": "bookmarkId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tagId": {
+ "name": "tagId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "attachedAt": {
+ "name": "attachedAt",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "attachedBy": {
+ "name": "attachedBy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "tagsOnBookmarks_tagId_idx": {
+ "name": "tagsOnBookmarks_tagId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ },
+ "tagsOnBookmarks_bookmarkId_idx": {
+ "name": "tagsOnBookmarks_bookmarkId_idx",
+ "columns": [
+ "bookmarkId"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "tagsOnBookmarks_bookmarkId_bookmarks_id_fk": {
+ "name": "tagsOnBookmarks_bookmarkId_bookmarks_id_fk",
+ "tableFrom": "tagsOnBookmarks",
+ "tableTo": "bookmarks",
+ "columnsFrom": [
+ "bookmarkId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "tagsOnBookmarks_tagId_bookmarkTags_id_fk": {
+ "name": "tagsOnBookmarks_tagId_bookmarkTags_id_fk",
+ "tableFrom": "tagsOnBookmarks",
+ "tableTo": "bookmarkTags",
+ "columnsFrom": [
+ "tagId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "tagsOnBookmarks_bookmarkId_tagId_pk": {
+ "columns": [
+ "bookmarkId",
+ "tagId"
+ ],
+ "name": "tagsOnBookmarks_bookmarkId_tagId_pk"
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'user'"
+ }
+ },
+ "indexes": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "columns": [
+ "email"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "verificationToken": {
+ "name": "verificationToken",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires": {
+ "name": "expires",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token_pk": {
+ "columns": [
+ "identifier",
+ "token"
+ ],
+ "name": "verificationToken_identifier_token_pk"
+ }
+ },
+ "uniqueConstraints": {}
+ }
+ },
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "indexes": {}
+ }
+} \ No newline at end of file
diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json
index 4b29c07f..0a357e2d 100644
--- a/packages/db/drizzle/meta/_journal.json
+++ b/packages/db/drizzle/meta/_journal.json
@@ -211,6 +211,13 @@
"when": 1728214930701,
"tag": "0029_short_gunslinger",
"breakpoints": true
+ },
+ {
+ "idx": 30,
+ "version": "6",
+ "when": 1728220453621,
+ "tag": "0030_blue_synch",
+ "breakpoints": true
}
]
} \ No newline at end of file
diff --git a/packages/db/schema.ts b/packages/db/schema.ts
index c11f3b92..6098feb1 100644
--- a/packages/db/schema.ts
+++ b/packages/db/schema.ts
@@ -164,6 +164,8 @@ export const enum AssetTypes {
LINK_BANNER_IMAGE = "linkBannerImage",
LINK_SCREENSHOT = "linkScreenshot",
LINK_FULL_PAGE_ARCHIVE = "linkFullPageArchive",
+ BOOKMARK_ASSET = "bookmarkAsset",
+ UNKNOWN = "unknown",
}
export const assets = sqliteTable(
@@ -176,11 +178,16 @@ export const assets = sqliteTable(
AssetTypes.LINK_BANNER_IMAGE,
AssetTypes.LINK_SCREENSHOT,
AssetTypes.LINK_FULL_PAGE_ARCHIVE,
+ AssetTypes.BOOKMARK_ASSET,
+ AssetTypes.UNKNOWN,
],
}).notNull(),
- bookmarkId: text("bookmarkId")
- .notNull()
- .references(() => bookmarks.id, { onDelete: "cascade" }),
+ size: integer("size").notNull().default(0),
+ contentType: text("contentType"),
+ fileName: text("fileName"),
+ bookmarkId: text("bookmarkId").references(() => bookmarks.id, {
+ onDelete: "cascade",
+ }),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
@@ -302,7 +309,6 @@ export const bookmarksInLists = sqliteTable(
}),
);
-
export const customPrompts = sqliteTable(
"customPrompts",
{
@@ -312,7 +318,9 @@ export const customPrompts = sqliteTable(
.$defaultFn(() => createId()),
text: text("text").notNull(),
enabled: integer("enabled", { mode: "boolean" }).notNull(),
- appliesTo: text("attachedBy", { enum: ["all", "text", "images"] }).notNull(),
+ appliesTo: text("attachedBy", {
+ enum: ["all", "text", "images"],
+ }).notNull(),
createdAt: createdAtField(),
userId: text("userId")
.notNull()
diff --git a/packages/shared/assetdb.ts b/packages/shared/assetdb.ts
index dd464139..4edfa1ec 100644
--- a/packages/shared/assetdb.ts
+++ b/packages/shared/assetdb.ts
@@ -120,6 +120,18 @@ export async function readAsset({
return { asset, metadata };
}
+export async function getAssetSize({
+ userId,
+ assetId,
+}: {
+ userId: string;
+ assetId: string;
+}) {
+ const assetDir = getAssetDir(userId, assetId);
+ const stat = await fs.promises.stat(path.join(assetDir, "asset.bin"));
+ return stat.size;
+}
+
export async function deleteAsset({
userId,
assetId,
diff --git a/packages/shared/types/bookmarks.ts b/packages/shared/types/bookmarks.ts
index 86bbbc1a..f4b4fd4a 100644
--- a/packages/shared/types/bookmarks.ts
+++ b/packages/shared/types/bookmarks.ts
@@ -15,6 +15,8 @@ export const zAssetTypesSchema = z.enum([
"screenshot",
"bannerImage",
"fullPageArchive",
+ "bookmarkAsset",
+ "unknown",
]);
export type ZAssetType = z.infer<typeof zAssetTypesSchema>;
diff --git a/packages/trpc/lib/attachments.ts b/packages/trpc/lib/attachments.ts
index 6fe1ef40..175947f8 100644
--- a/packages/trpc/lib/attachments.ts
+++ b/packages/trpc/lib/attachments.ts
@@ -8,6 +8,8 @@ export function mapDBAssetTypeToUserType(assetType: AssetTypes): ZAssetType {
[AssetTypes.LINK_SCREENSHOT]: "screenshot",
[AssetTypes.LINK_FULL_PAGE_ARCHIVE]: "fullPageArchive",
[AssetTypes.LINK_BANNER_IMAGE]: "bannerImage",
+ [AssetTypes.BOOKMARK_ASSET]: "bookmarkAsset",
+ [AssetTypes.UNKNOWN]: "bannerImage",
};
return map[assetType];
}
@@ -19,6 +21,8 @@ export function mapSchemaAssetTypeToDB(
screenshot: AssetTypes.LINK_SCREENSHOT,
fullPageArchive: AssetTypes.LINK_FULL_PAGE_ARCHIVE,
bannerImage: AssetTypes.LINK_BANNER_IMAGE,
+ bookmarkAsset: AssetTypes.BOOKMARK_ASSET,
+ unknown: AssetTypes.UNKNOWN,
};
return map[assetType];
}
@@ -28,6 +32,8 @@ export function humanFriendlyNameForAssertType(type: ZAssetType) {
screenshot: "Screenshot",
fullPageArchive: "Full Page Archive",
bannerImage: "Banner Image",
+ bookmarkAsset: "Bookmark Asset",
+ unknown: "Unknown",
};
return map[type];
}
@@ -37,6 +43,19 @@ export function isAllowedToAttachAsset(type: ZAssetType) {
screenshot: true,
fullPageArchive: false,
bannerImage: true,
+ bookmarkAsset: false,
+ unknown: false,
+ };
+ return map[type];
+}
+
+export function isAllowedToDetachAsset(type: ZAssetType) {
+ const map: Record<ZAssetType, boolean> = {
+ screenshot: true,
+ fullPageArchive: true,
+ bannerImage: true,
+ bookmarkAsset: false,
+ unknown: false,
};
return map[type];
}
diff --git a/packages/trpc/routers/bookmarks.test.ts b/packages/trpc/routers/bookmarks.test.ts
index d6a7bc27..d2944c40 100644
--- a/packages/trpc/routers/bookmarks.test.ts
+++ b/packages/trpc/routers/bookmarks.test.ts
@@ -369,6 +369,24 @@ describe("Bookmark Routes", () => {
bookmarkId: bookmark.id,
userId,
}),
+ db.insert(assets).values({
+ id: "asset4",
+ assetType: AssetTypes.UNKNOWN,
+ bookmarkId: null,
+ userId,
+ }),
+ db.insert(assets).values({
+ id: "asset5",
+ assetType: AssetTypes.UNKNOWN,
+ bookmarkId: null,
+ userId,
+ }),
+ db.insert(assets).values({
+ id: "asset6",
+ assetType: AssetTypes.UNKNOWN,
+ bookmarkId: null,
+ userId,
+ }),
]);
const validateAssets = async (
@@ -424,7 +442,7 @@ describe("Bookmark Routes", () => {
await api.replaceAsset({
bookmarkId: bookmark.id,
oldAssetId: "asset3",
- newAssetId: "asset4",
+ newAssetId: "asset6",
}),
).rejects.toThrow(/You can't attach this type of asset/);
await expect(
diff --git a/packages/trpc/routers/bookmarks.ts b/packages/trpc/routers/bookmarks.ts
index b1491a61..f272433a 100644
--- a/packages/trpc/routers/bookmarks.ts
+++ b/packages/trpc/routers/bookmarks.ts
@@ -44,6 +44,7 @@ import type { AuthedContext, Context } from "../index";
import { authedProcedure, router } from "../index";
import {
isAllowedToAttachAsset,
+ isAllowedToDetachAsset,
mapDBAssetTypeToUserType,
mapSchemaAssetTypeToDB,
} from "../lib/attachments";
@@ -80,23 +81,35 @@ export const ensureBookmarkOwnership = experimental_trpcMiddleware<{
return opts.next();
});
-interface Asset {
- id: string;
- assetType: AssetTypes;
-}
-
-function mapAssetsToBookmarkFields(assets: Asset | Asset[] = []) {
- const ASSET_TYE_MAPPING: Record<AssetTypes, string> = {
- [AssetTypes.LINK_SCREENSHOT]: "screenshotAssetId",
- [AssetTypes.LINK_FULL_PAGE_ARCHIVE]: "fullPageArchiveAssetId",
- [AssetTypes.LINK_BANNER_IMAGE]: "imageAssetId",
- };
- const assetsArray = Array.isArray(assets) ? assets : [assets];
- return assetsArray.reduce((result: Record<string, string>, asset: Asset) => {
- result[ASSET_TYE_MAPPING[asset.assetType]] = asset.id;
- return result;
- }, {});
-}
+export const ensureAssetOwnership = async (opts: {
+ ctx: Context;
+ assetId: string;
+}) => {
+ const asset = await opts.ctx.db.query.assets.findFirst({
+ where: eq(bookmarks.id, opts.assetId),
+ columns: {
+ userId: true,
+ },
+ });
+ if (!opts.ctx.user) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "User is not authorized",
+ });
+ }
+ if (!asset) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Asset not found",
+ });
+ }
+ if (asset.userId != opts.ctx.user.id) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "User is not allowed to access resource",
+ });
+ }
+};
async function getBookmark(ctx: AuthedContext, bookmarkId: string) {
const bookmark = await ctx.db.query.bookmarks.findFirst({
@@ -189,7 +202,15 @@ function toZodSchema(bookmark: BookmarkQueryReturnType): ZBookmark {
case BookmarkTypes.LINK:
content = {
type: bookmark.type,
- ...mapAssetsToBookmarkFields(assets),
+ screenshotAssetId: assets.find(
+ (a) => a.assetType == AssetTypes.LINK_SCREENSHOT,
+ )?.id,
+ fullPageArchiveAssetId: assets.find(
+ (a) => a.assetType == AssetTypes.LINK_FULL_PAGE_ARCHIVE,
+ )?.id,
+ imageAssetId: assets.find(
+ (a) => a.assetType == AssetTypes.LINK_BANNER_IMAGE,
+ )?.id,
...link,
};
break;
@@ -307,6 +328,19 @@ export const bookmarksAppRouter = router({
sourceUrl: null,
})
.returning();
+ await ensureAssetOwnership({ ctx, assetId: input.assetId });
+ await tx
+ .update(assets)
+ .set({
+ bookmarkId: bookmark.id,
+ assetType: AssetTypes.BOOKMARK_ASSET,
+ })
+ .where(
+ and(
+ eq(assets.id, input.assetId),
+ eq(assets.userId, ctx.user.id),
+ ),
+ );
content = {
type: BookmarkTypes.ASSET,
assetType: asset.assetType,
@@ -647,10 +681,20 @@ export const bookmarksAppRouter = router({
row.assets &&
!acc[bookmarkId].assets.some((a) => a.id == row.assets!.id)
) {
- acc[bookmarkId].content = {
- ...acc[bookmarkId].content,
- ...mapAssetsToBookmarkFields(row.assets),
- };
+ if (acc[bookmarkId].content.type == BookmarkTypes.LINK) {
+ const content = acc[bookmarkId].content;
+ invariant(content.type == BookmarkTypes.LINK);
+ if (row.assets.assetType == AssetTypes.LINK_SCREENSHOT) {
+ content.screenshotAssetId = row.assets.id;
+ }
+ if (row.assets.assetType == AssetTypes.LINK_FULL_PAGE_ARCHIVE) {
+ content.fullPageArchiveAssetId = row.assets.id;
+ }
+ if (row.assets.assetType == AssetTypes.LINK_BANNER_IMAGE) {
+ content.imageAssetId = row.assets.id;
+ }
+ acc[bookmarkId].content = content;
+ }
acc[bookmarkId].assets.push({
id: row.assets.id,
assetType: mapDBAssetTypeToUserType(row.assets.assetType),
@@ -841,6 +885,7 @@ export const bookmarksAppRouter = router({
.output(zAssetSchema)
.use(ensureBookmarkOwnership)
.mutation(async ({ input, ctx }) => {
+ await ensureAssetOwnership({ ctx, assetId: input.asset.id });
if (!isAllowedToAttachAsset(input.asset.assetType)) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -848,14 +893,14 @@ export const bookmarksAppRouter = router({
});
}
await ctx.db
- .insert(assets)
- .values({
- id: input.asset.id,
+ .update(assets)
+ .set({
assetType: mapSchemaAssetTypeToDB(input.asset.assetType),
bookmarkId: input.bookmarkId,
- userId: ctx.user.id,
})
- .returning();
+ .where(
+ and(eq(assets.id, input.asset.id), eq(assets.userId, ctx.user.id)),
+ );
return input.asset;
}),
replaceAsset: authedProcedure
@@ -869,21 +914,19 @@ export const bookmarksAppRouter = router({
.output(z.void())
.use(ensureBookmarkOwnership)
.mutation(async ({ input, ctx }) => {
- const oldAsset = await ctx.db
+ await Promise.all([
+ ensureAssetOwnership({ ctx, assetId: input.oldAssetId }),
+ ensureAssetOwnership({ ctx, assetId: input.newAssetId }),
+ ]);
+ const [oldAsset] = await ctx.db
.select()
.from(assets)
.where(
- and(
- eq(assets.id, input.oldAssetId),
- eq(assets.bookmarkId, input.bookmarkId),
- ),
+ and(eq(assets.id, input.oldAssetId), eq(assets.userId, ctx.user.id)),
)
.limit(1);
- if (!oldAsset.length) {
- throw new TRPCError({ code: "NOT_FOUND" });
- }
if (
- !isAllowedToAttachAsset(mapDBAssetTypeToUserType(oldAsset[0].assetType))
+ !isAllowedToAttachAsset(mapDBAssetTypeToUserType(oldAsset.assetType))
) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -891,21 +934,17 @@ export const bookmarksAppRouter = router({
});
}
- const result = await ctx.db
- .update(assets)
- .set({
- id: input.newAssetId,
- bookmarkId: input.bookmarkId,
- })
- .where(
- and(
- eq(assets.id, input.oldAssetId),
- eq(assets.bookmarkId, input.bookmarkId),
- ),
- );
- if (result.changes == 0) {
- throw new TRPCError({ code: "NOT_FOUND" });
- }
+ await ctx.db.transaction(async (tx) => {
+ await tx.delete(assets).where(eq(assets.id, input.oldAssetId));
+ await tx
+ .update(assets)
+ .set({
+ bookmarkId: input.bookmarkId,
+ assetType: oldAsset.assetType,
+ })
+ .where(eq(assets.id, input.newAssetId));
+ });
+
await deleteAsset({
userId: ctx.user.id,
assetId: input.oldAssetId,
@@ -921,6 +960,21 @@ export const bookmarksAppRouter = router({
.output(z.void())
.use(ensureBookmarkOwnership)
.mutation(async ({ input, ctx }) => {
+ await ensureAssetOwnership({ ctx, assetId: input.assetId });
+ const [oldAsset] = await ctx.db
+ .select()
+ .from(assets)
+ .where(
+ and(eq(assets.id, input.assetId), eq(assets.userId, ctx.user.id)),
+ );
+ if (
+ !isAllowedToDetachAsset(mapDBAssetTypeToUserType(oldAsset.assetType))
+ ) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "You can't deattach this type of asset",
+ });
+ }
const result = await ctx.db
.delete(assets)
.where(
diff --git a/packages/trpc/testUtils.ts b/packages/trpc/testUtils.ts
index 67fbddcc..04e6b0a3 100644
--- a/packages/trpc/testUtils.ts
+++ b/packages/trpc/testUtils.ts
@@ -26,12 +26,13 @@ export async function seedUsers(db: TestDB) {
.returning();
}
-export function getApiCaller(db: TestDB, userId?: string) {
+export function getApiCaller(db: TestDB, userId?: string, email?: string) {
const createCaller = createCallerFactory(appRouter);
return createCaller({
user: userId
? {
id: userId,
+ email,
role: "user",
}
: null,
@@ -55,7 +56,7 @@ export async function buildTestContext(
if (seedDB) {
users = await seedUsers(db);
}
- const callers = users.map((u) => getApiCaller(db, u.id));
+ const callers = users.map((u) => getApiCaller(db, u.id, u.email));
return {
apiCallers: callers,