implement collections page
This commit is contained in:
59
packages/server/src/collection/collection.ts
Normal file
59
packages/server/src/collection/collection.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { User } from "@markone/core"
|
||||
import type { Collection } from "@markone/core"
|
||||
import { db } from "~/database.js"
|
||||
|
||||
function findCollections(user: User): Collection[] {
|
||||
const collectionsQuery = db.query<Collection, { userId: string }>(`
|
||||
SELECT id, name, description
|
||||
FROM collections
|
||||
WHERE user_id = $userId
|
||||
`)
|
||||
return collectionsQuery.all({ userId: user.id })
|
||||
}
|
||||
|
||||
function insertCollection(collection: Collection, user: User): void {
|
||||
const query = db.query(`
|
||||
INSERT INTO collections (id, user_id, name, description)
|
||||
VALUES ($id, $userId, $name, $description)
|
||||
`)
|
||||
|
||||
query.run({
|
||||
id: collection.id,
|
||||
userId: user.id,
|
||||
name: collection.name,
|
||||
description: collection.description,
|
||||
})
|
||||
}
|
||||
|
||||
function updateCollection(collection: Collection, user: User): void {
|
||||
db.query(`
|
||||
UPDATE collections
|
||||
SET name = $name, description = $description
|
||||
WHERE id = $id AND user_id = $userId
|
||||
`).run({
|
||||
id: collection.id,
|
||||
userId: user.id,
|
||||
name: collection.name,
|
||||
description: collection.description,
|
||||
})
|
||||
}
|
||||
|
||||
function deleteCollection(collectionId: string, user: User): void {
|
||||
db.query(`
|
||||
DELETE FROM collections
|
||||
WHERE id = $id AND user_id = $userId
|
||||
`).run({
|
||||
id: collectionId,
|
||||
userId: user.id,
|
||||
})
|
||||
}
|
||||
|
||||
function findCollectionById(collectionId: string, user: User): Collection | null {
|
||||
return db.query<Collection, { id: string; userId: string }>(`
|
||||
SELECT *
|
||||
FROM collections
|
||||
WHERE id = $id AND user_id = $userId
|
||||
`).get({ id: collectionId, userId: user.id })
|
||||
}
|
||||
|
||||
export { findCollections, insertCollection, updateCollection, deleteCollection, findCollectionById }
|
82
packages/server/src/collection/handlers.ts
Normal file
82
packages/server/src/collection/handlers.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { type Collection, DEMO_USER, type User } from "@markone/core"
|
||||
import { type } from "arktype"
|
||||
import { ulid } from "ulid"
|
||||
import { HttpError } from "~/error.ts"
|
||||
import { findCollections, insertCollection, deleteCollection, updateCollection, findCollectionById } from "./collection.ts"
|
||||
|
||||
const AddCollectionRequestBody = type({
|
||||
title: "string",
|
||||
description: "string",
|
||||
})
|
||||
|
||||
const UpdateCollectionRequestBody = type({
|
||||
title: "string",
|
||||
description: "string",
|
||||
})
|
||||
|
||||
async function createCollection(request: Bun.BunRequest<"/api/collections">, user: User) {
|
||||
if (user.id === DEMO_USER.id) {
|
||||
return Response.json(undefined, { status: 204 })
|
||||
}
|
||||
|
||||
const body = AddCollectionRequestBody(
|
||||
await request.json().catch(() => {
|
||||
throw new HttpError(400)
|
||||
}),
|
||||
)
|
||||
|
||||
if (body instanceof type.errors) {
|
||||
throw new HttpError(400, "", body.summary)
|
||||
}
|
||||
|
||||
const collection: Collection = {
|
||||
id: ulid(),
|
||||
name: body.title,
|
||||
description: body.description,
|
||||
bookmarks: [],
|
||||
}
|
||||
|
||||
insertCollection(collection, user)
|
||||
|
||||
return Response.json(collection, { status: 200 })
|
||||
}
|
||||
|
||||
async function listUserCollections(request: Bun.BunRequest<"/api/collections">, user: User) {
|
||||
const collections = findCollections(user)
|
||||
return Response.json(collections, { status: 200 })
|
||||
}
|
||||
|
||||
async function deleteUserCollection(request: Bun.BunRequest<"/api/collections/:id">, user: User) {
|
||||
if (user.id !== DEMO_USER.id) {
|
||||
deleteCollection(request.params.id, user)
|
||||
}
|
||||
return Response.json(undefined, { status: 204 })
|
||||
}
|
||||
|
||||
async function updateUserCollection(request: Bun.BunRequest<"/api/collections/:id">, user: User) {
|
||||
if (user.id === DEMO_USER.id) {
|
||||
return Response.json(undefined, { status: 204 })
|
||||
}
|
||||
|
||||
const bodyJson = await request.json().catch(() => {
|
||||
throw new HttpError(400)
|
||||
})
|
||||
const body = UpdateCollectionRequestBody(bodyJson)
|
||||
if (body instanceof type.errors) {
|
||||
throw new HttpError(400, "", body.summary)
|
||||
}
|
||||
|
||||
const existingCollection = findCollectionById(request.params.id, user)
|
||||
if (!existingCollection) {
|
||||
throw new HttpError(404)
|
||||
}
|
||||
|
||||
existingCollection.name = body.title
|
||||
existingCollection.description = body.description
|
||||
|
||||
updateCollection(existingCollection, user)
|
||||
|
||||
return Response.json(existingCollection, { status: 200 })
|
||||
}
|
||||
|
||||
export { createCollection, listUserCollections, deleteUserCollection, updateUserCollection }
|
@@ -54,6 +54,19 @@ CREATE TABLE IF NOT EXISTS auth_tokens(
|
||||
user_id TEXT NOT NULL,
|
||||
expires_at_unix_ms INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS collections(
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
description TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bookmark_collections(
|
||||
collection_id TEXT NOT NULL,
|
||||
bookmark_id TEXT NOT NULL,
|
||||
PRIMARY KEY (collection_id, bookmark_id)
|
||||
);
|
||||
`,
|
||||
]
|
||||
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { createCollection, deleteUserCollection, listUserCollections } from "~/collection/handlers.js"
|
||||
import { authenticated, login, logout, signUp, startBackgroundAuthTokenCleanup } from "./auth/auth.ts"
|
||||
import { startBackgroundSessionCleanup } from "./auth/session.ts"
|
||||
import { insertDemoBookmarks } from "./bookmark/bookmark.ts"
|
||||
@@ -53,6 +54,17 @@ async function main() {
|
||||
GET: authenticated(listUserTags),
|
||||
POST: authenticated(createUserTag),
|
||||
},
|
||||
"/api/collections": {
|
||||
GET: authenticated(listUserCollections),
|
||||
POST: authenticated(createCollection),
|
||||
},
|
||||
"/api/collections/:id": {
|
||||
DELETE: authenticated(deleteUserCollection),
|
||||
OPTIONS: preflightHandler({
|
||||
allowedMethods: ["GET", "POST", "DELETE", "PATCH", "OPTIONS"],
|
||||
allowedHeaders: ["Accept"],
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
port: 8080,
|
||||
|
Reference in New Issue
Block a user