add auth token cleanup logic

This commit is contained in:
2025-05-21 23:27:17 +01:00
parent 2c292db0fc
commit 255acfcb32
5 changed files with 69 additions and 29 deletions

View File

@@ -119,6 +119,13 @@ async function verifyAuthToken(cookies: Bun.CookieMap): Promise<User | null> {
return user return user
} }
function startBackgroundAuthTokenCleanup() {
const query = db.query("DELETE FROM auth_tokens WHERE expires_at_unix_ms < $time")
setInterval(() => {
query.run({ time: new Date().valueOf() })
}, 5000)
}
async function signUp(request: Bun.BunRequest<"/api/sign-up">) { async function signUp(request: Bun.BunRequest<"/api/sign-up">) {
const body = await request.json().catch(() => { const body = await request.json().catch(() => {
throw new HttpError(500) throw new HttpError(500)
@@ -199,4 +206,4 @@ async function logout(request: Bun.BunRequest<"/api/logout">, user: User): Promi
return new Response(undefined, { status: 204 }) return new Response(undefined, { status: 204 })
} }
export { authenticated, signUp, login, logout } export { authenticated, signUp, login, logout, startBackgroundAuthTokenCleanup }

View File

@@ -1,4 +1,4 @@
import type { Bookmark, BookmarkTag } from "@markone/core/bookmark" import type { Bookmark, BookmarkId, BookmarkTag } from "@markone/core/bookmark"
import type { User } from "@markone/core/user" import type { User } from "@markone/core/user"
import { Readability } from "@mozilla/readability" import { Readability } from "@mozilla/readability"
import { JSDOM } from "jsdom" import { JSDOM } from "jsdom"
@@ -72,6 +72,14 @@ function findBookmark(id: string, user: User): Bookmark | null {
return bookmark return bookmark
} }
function deleteBookmark(id: BookmarkId, user: User) {
db.query("DELETE FROM bookmarks WHERE user_id = $userId AND id = $id").run({
id,
userId: user.id,
})
db.query("DELETE FROM bookmark_tags WHERE bookmark_id = ?").run(id)
}
async function cacheContent(url: string): Promise<CachedContent | null> { async function cacheContent(url: string): Promise<CachedContent | null> {
const res = await fetch(url).catch(() => { const res = await fetch(url).catch(() => {
throw new LinkUnreachable() throw new LinkUnreachable()
@@ -162,6 +170,7 @@ function findBookmarkTags(bookmark: Bookmark): BookmarkTag[] {
export { export {
insertDemoBookmarks, insertDemoBookmarks,
insertBookmark, insertBookmark,
deleteBookmark,
findBookmark, findBookmark,
findBookmarkCachedContent, findBookmarkCachedContent,
cacheContent, cacheContent,

View File

@@ -10,6 +10,7 @@ import {
UnsupportedLink, UnsupportedLink,
assignTagsToBookmark, assignTagsToBookmark,
cacheContent, cacheContent,
deleteBookmark,
findBookmark, findBookmark,
findBookmarkCachedContent, findBookmarkCachedContent,
findBookmarkTags, findBookmarkTags,
@@ -22,6 +23,7 @@ const BOOKMARK_PAGINATION_LIMIT = 100
const ListUserBookmarksParams = type({ const ListUserBookmarksParams = type({
limit: ["number", "=", BOOKMARK_PAGINATION_LIMIT], limit: ["number", "=", BOOKMARK_PAGINATION_LIMIT],
skip: ["number", "=", 0], skip: ["number", "=", 0],
"tags?": "string",
}) })
const AddBookmarkRequestBody = type({ const AddBookmarkRequestBody = type({
@@ -36,36 +38,53 @@ const AddTagRequestBody = type({
}) })
async function listUserBookmarks(request: Bun.BunRequest<"/api/bookmarks">, user: User) { async function listUserBookmarks(request: Bun.BunRequest<"/api/bookmarks">, user: User) {
const queryParams = ListUserBookmarksParams(request.params) const { searchParams } = new URL(request.url)
const queryParams = ListUserBookmarksParams(Object.fromEntries(searchParams))
if (queryParams instanceof type.errors) { if (queryParams instanceof type.errors) {
throw new HttpError(400, "", queryParams.summary) throw new HttpError(400, "", queryParams.summary)
} }
const listBookmarksQuery = db.query( let results: Bookmark[]
` if (queryParams.tags) {
SELECT bookmarks.id, bookmarks.title, bookmarks.url FROM bookmarks const tagNames = queryParams.tags.split(",")
WHERE bookmarks.user_id = $userId
ORDER BY bookmarks.id DESC
LIMIT $limit OFFSET $skip
`,
)
const results = listBookmarksQuery.all({ const tagIdsQuery = db.query<{ id: string }, string[]>(
userId: user.id, `SELECT id FROM tags WHERE name IN (${Array(tagNames.length).fill("?").join(",")})`,
limit: queryParams.limit, )
skip: queryParams.skip,
}) const tagIds = tagIdsQuery.all(...tagNames).map(({ id }) => id)
const query = db.query(`
SELECT bookmarks.id, bookmarks.title, bookmarks.url FROM bookmarks
INNER JOIN bookmark_tags
ON bookmark_tags.bookmark_id = bookmarks.id
WHERE bookmarks.user_id = ? AND bookmark_tags.tag_id IN (${Array(tagIds.length).fill("?").join(",")})
ORDER BY bookmarks.id DESC
LIMIT ? OFFSET ?
`)
results = query.all(...[user.id, ...tagIds, queryParams.limit, queryParams.skip]) as Bookmark[]
} else {
const query = db.query(`
SELECT bookmarks.id, bookmarks.title, bookmarks.url FROM bookmarks
WHERE bookmarks.user_id = $userId
ORDER BY bookmarks.id DESC
LIMIT $limit OFFSET $skip
`)
results = query.all({
userId: user.id,
limit: queryParams.limit,
skip: queryParams.skip,
}) as Bookmark[]
}
return Response.json(results, { status: 200 }) return Response.json(results, { status: 200 })
} }
async function deleteUserBookmark(request: Bun.BunRequest<"/api/bookmarks/:id">, user: User) { async function deleteUserBookmark(request: Bun.BunRequest<"/api/bookmarks/:id">, user: User) {
if (user.id !== DEMO_USER.id) { if (user.id !== DEMO_USER.id) {
const deleteBookmarkQuery = db.query("DELETE FROM bookmarks WHERE user_id = $userId AND id = $id") deleteBookmark(request.params.id, user)
const tx = db.transaction(() => {
deleteBookmarkQuery.run({ userId: user.id, id: request.params.id })
})
tx()
} }
return Response.json(undefined, { status: 204 }) return Response.json(undefined, { status: 204 })
} }
@@ -94,7 +113,7 @@ async function addBookmark(request: Bun.BunRequest<"/api/bookmarks">, user: User
const tagNames = new Set(body.tags) const tagNames = new Set(body.tags)
for (const tag of tagNames) { for (const tag of tagNames) {
if (/[\s#]/g.test(tag)) { if (tag.length === 0 || /[\s#]/g.test(tag)) {
throw new HttpError(400, "InvalidTag", "Tags cannot contain '#' or whitespaces") throw new HttpError(400, "InvalidTag", "Tags cannot contain '#' or whitespaces")
} }
} }
@@ -116,9 +135,9 @@ async function addBookmark(request: Bun.BunRequest<"/api/bookmarks">, user: User
if (tagNames.size > 0) { if (tagNames.size > 0) {
const tagQuery = db.query<Partial<BookmarkTag>, string[]>(` const tagQuery = db.query<Partial<BookmarkTag>, string[]>(`
SELECT id, name FROM tags SELECT id, name FROM tags
WHERE user_id = ? AND name IN (${Array(tagNames.size).fill("?").join(",")}) WHERE user_id = ? AND name IN (${Array(tagNames.size).fill("?").join(",")})
`) `)
const tags = tagQuery.all(user.id, ...tagNames) const tags = tagQuery.all(user.id, ...tagNames)
for (const tag of tags) { for (const tag of tags) {
@@ -128,8 +147,12 @@ async function addBookmark(request: Bun.BunRequest<"/api/bookmarks">, user: User
} }
} }
const createdTags = insertTags([...tagNames], user) if (tagNames.size > 0) {
assignTagsToBookmark(createdTags, bookmark) const createdTags = insertTags([...tagNames], user)
bookmark.tags.push(...createdTags)
}
assignTagsToBookmark(bookmark.tags, bookmark)
} }
return Response.json(bookmark, { status: 200 }) return Response.json(bookmark, { status: 200 })

View File

@@ -4,7 +4,7 @@ import { ulid } from "ulid"
import { db } from "~/database.ts" import { db } from "~/database.ts"
function insertTags(names: string[], user: User): BookmarkTag[] { function insertTags(names: string[], user: User): BookmarkTag[] {
console.log(names) console.log("======== insert tags", names)
const insertTags = db.query(` const insertTags = db.query(`
INSERT INTO tags (id, name, user_id) INSERT INTO tags (id, name, user_id)
VALUES ${Array(names.length).fill("(?,?,?)").join(",")} VALUES ${Array(names.length).fill("(?,?,?)").join(",")}

View File

@@ -1,4 +1,4 @@
import { authenticated, login, logout, signUp } from "./auth/auth.ts" import { authenticated, login, logout, signUp, startBackgroundAuthTokenCleanup } from "./auth/auth.ts"
import { startBackgroundSessionCleanup } from "./auth/session.ts" import { startBackgroundSessionCleanup } from "./auth/session.ts"
import { insertDemoBookmarks } from "./bookmark/bookmark.ts" import { insertDemoBookmarks } from "./bookmark/bookmark.ts"
import { import {
@@ -19,6 +19,7 @@ async function main() {
const user = await createDemoUser() const user = await createDemoUser()
insertDemoBookmarks(user) insertDemoBookmarks(user)
startBackgroundSessionCleanup() startBackgroundSessionCleanup()
startBackgroundAuthTokenCleanup()
Bun.serve({ Bun.serve({
routes: { routes: {