implement bookmark delete
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { type User, DEMO_USER } from "@markone/core/user"
|
||||
import { DEMO_USER, type User } from "@markone/core/user"
|
||||
import { type } from "arktype"
|
||||
import dayjs from "dayjs"
|
||||
import { ulid } from "ulid"
|
||||
@@ -6,6 +6,7 @@ import { db } from "~/database.ts"
|
||||
import { HttpError } from "~/error.ts"
|
||||
import { httpHandler } from "~/http-handler.ts"
|
||||
import { createUser, findUserById, findUserByUsername } from "~/user/user.ts"
|
||||
import { hashPassword, verifyPassword } from "./password.ts"
|
||||
import { createSessionForUser, extendSession, forgetAllSessions, saveSession, verifySession } from "./session.ts"
|
||||
|
||||
const SignUpRequest = type({
|
||||
@@ -18,20 +19,13 @@ const LoginRequest = type({
|
||||
password: "string",
|
||||
})
|
||||
|
||||
const createAuthTokenQuery = db.query(
|
||||
"INSERT INTO auth_tokens(id, token, user_id, expires_at_unix_ms) VALUES ($id, $token, $userId, $expiresAt)",
|
||||
)
|
||||
|
||||
const deleteAuthTokenQuery = db.query("DELETE FROM auth_tokens WHERE id = $id")
|
||||
|
||||
const deleteAllAuthTokensQuery = db.query("DELETE FROM auth_tokens WHERE user_id = $userId")
|
||||
|
||||
function authenticated<Route extends string>(
|
||||
handler: (request: Bun.BunRequest<Route>, user: User) => Promise<Response>,
|
||||
) {
|
||||
return httpHandler<Route>((request) => {
|
||||
const session = verifySession(request.cookies)
|
||||
if (!session) {
|
||||
console.log("session not found!")
|
||||
throw new HttpError(401)
|
||||
}
|
||||
|
||||
@@ -42,6 +36,8 @@ function authenticated<Route extends string>(
|
||||
|
||||
const authTokenCookie = request.cookies.get("auth-token")
|
||||
if (authTokenCookie) {
|
||||
const deleteAuthTokenQuery = db.query("DELETE FROM auth_tokens WHERE id = $id")
|
||||
|
||||
// biome-ignore lint/style/noNonNullAssertion: the cookie has already been verified by verifySession previously, therefore the cookie must be in the correct format <token-id>:<token-signature>
|
||||
const tokenId = authTokenCookie.split(":")[0]!
|
||||
deleteAuthTokenQuery.run({ id: tokenId })
|
||||
@@ -69,6 +65,10 @@ function rememberLoginForUser(user: User, cookies: Bun.CookieMap) {
|
||||
|
||||
const expiryDate = dayjs().add(1, "month")
|
||||
|
||||
const createAuthTokenQuery = db.query(
|
||||
"INSERT INTO auth_tokens(id, token, user_id, expires_at_unix_ms) VALUES ($id, $token, $userId, $expiresAt)",
|
||||
)
|
||||
|
||||
createAuthTokenQuery.run({
|
||||
id: tokenId,
|
||||
token: hashedToken,
|
||||
@@ -78,6 +78,7 @@ function rememberLoginForUser(user: User, cookies: Bun.CookieMap) {
|
||||
|
||||
cookies.set("auth-token", `${tokenId}:${authToken.toBase64({ alphabet: "base64url" })}`, {
|
||||
maxAge: 30 * 24 * 60 * 60 * 1000,
|
||||
path: "/api",
|
||||
httpOnly: true,
|
||||
})
|
||||
}
|
||||
@@ -93,7 +94,7 @@ async function signUp(request: Bun.BunRequest<"/api/sign-up">) {
|
||||
}
|
||||
|
||||
const { username, password } = signUpRequest
|
||||
const hashedPassword = await Bun.password.hash(password, "argon2id")
|
||||
const hashedPassword = await hashPassword(password)
|
||||
const user = createUser(username, hashedPassword)
|
||||
|
||||
await createSessionForUser(user, request.cookies)
|
||||
@@ -116,10 +117,10 @@ async function login(request: Bun.BunRequest<"/api/login">) {
|
||||
password: true,
|
||||
})
|
||||
if (!foundUser) {
|
||||
throw new HttpError(400)
|
||||
throw new HttpError(401)
|
||||
}
|
||||
|
||||
const ok = await Bun.password.verify(loginRequest.password, foundUser.password, "argon2id").catch(() => {
|
||||
const ok = await verifyPassword(loginRequest.password, foundUser.password).catch(() => {
|
||||
throw new HttpError(401)
|
||||
})
|
||||
if (!ok) {
|
||||
@@ -131,8 +132,8 @@ async function login(request: Bun.BunRequest<"/api/login">) {
|
||||
username: foundUser.username,
|
||||
}
|
||||
|
||||
if (user.id === DEMO_USER.id) {
|
||||
await createSessionForUser(user, request.cookies)
|
||||
await createSessionForUser(user, request.cookies)
|
||||
if (user.id !== DEMO_USER.id) {
|
||||
rememberLoginForUser(user, request.cookies)
|
||||
}
|
||||
|
||||
@@ -140,6 +141,8 @@ async function login(request: Bun.BunRequest<"/api/login">) {
|
||||
}
|
||||
|
||||
async function logout(request: Bun.BunRequest<"/api/logout">, user: User): Promise<Response> {
|
||||
const deleteAllAuthTokensQuery = db.query("DELETE FROM auth_tokens WHERE user_id = $userId")
|
||||
|
||||
forgetAllSessions(user)
|
||||
deleteAllAuthTokensQuery.run({ userId: user.id })
|
||||
return new Response(undefined, { status: 200 })
|
||||
|
11
packages/server/src/auth/password.ts
Normal file
11
packages/server/src/auth/password.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
const HASH_ALGORITHM = "argon2id"
|
||||
|
||||
async function hashPassword(input: string): Promise<string> {
|
||||
return await Bun.password.hash(input, HASH_ALGORITHM)
|
||||
}
|
||||
|
||||
async function verifyPassword(input: string, hash: string) {
|
||||
return await Bun.password.verify(input, hash, HASH_ALGORITHM)
|
||||
}
|
||||
|
||||
export { hashPassword, verifyPassword }
|
@@ -15,21 +15,8 @@ const SESSION_ID_BYTE_LENGTH = 24
|
||||
const SESSION_ID_COOKIE_NAME = "session-id"
|
||||
const SESSION_DURATION_MS = 30 * 60 * 1000
|
||||
|
||||
const findSessionQuery = db.query("SELECT user_id, expires_at_unix FROM sessions WHERE session_id = $sessionId")
|
||||
|
||||
const deleteSessionQuery = db.query("DELETE FROM sessions WHERE session_id = $sessionId")
|
||||
const forgetAllSessionsQuery = db.query("DELETE FROM sessions WHERE user_id = $userId")
|
||||
const deleteExpiredSessionsQuery = db.query("DELETE FROM sessions WHERE expires_at_unix_ms < $time")
|
||||
|
||||
const saveSessionQuery = db.query(
|
||||
"INSERT INTO sessions(session_id, user_id, expires_at_unix_ms) VALUES ($sessionId, $userId, $expiresAt)",
|
||||
)
|
||||
|
||||
const extendSessionQuery = db.query(
|
||||
"UPDATE sessions SET expires_at_unix_ms = $newExpiryDate WHERE session_id = $session_id",
|
||||
)
|
||||
|
||||
function startBackgroundSessionCleanup() {
|
||||
const deleteExpiredSessionsQuery = db.query("DELETE FROM sessions WHERE expires_at_unix_ms < $time")
|
||||
setInterval(() => {
|
||||
deleteExpiredSessionsQuery.run({ time: dayjs().valueOf() })
|
||||
}, 5000)
|
||||
@@ -48,9 +35,11 @@ function signSessionId(sessionId: string): string {
|
||||
async function createSessionForUser(user: User, cookies: Bun.CookieMap) {
|
||||
const sessionId = await newSessionId()
|
||||
const signedSessionId = signSessionId(sessionId)
|
||||
|
||||
const expiryDate = dayjs().add(30, "minutes").valueOf()
|
||||
|
||||
const saveSessionQuery = db.query(
|
||||
"INSERT INTO sessions (session_id, user_id, expires_at_unix_ms) VALUES ($sessionId, $userId, $expiresAt)",
|
||||
)
|
||||
saveSessionQuery.run({
|
||||
sessionId,
|
||||
userId: user.id,
|
||||
@@ -59,13 +48,17 @@ async function createSessionForUser(user: User, cookies: Bun.CookieMap) {
|
||||
|
||||
cookies.set(SESSION_ID_COOKIE_NAME, signedSessionId, {
|
||||
maxAge: user.id === DEMO_USER.id ? undefined : SESSION_DURATION_MS,
|
||||
path: "/api",
|
||||
httpOnly: true,
|
||||
})
|
||||
|
||||
console.log("session created for user", user.id)
|
||||
}
|
||||
|
||||
async function saveSession(session: Session, cookies: Bun.CookieMap) {
|
||||
cookies.set(SESSION_ID_COOKIE_NAME, session.signedId, {
|
||||
maxAge: SESSION_DURATION_MS,
|
||||
path: "/api",
|
||||
httpOnly: true,
|
||||
})
|
||||
}
|
||||
@@ -73,6 +66,7 @@ async function saveSession(session: Session, cookies: Bun.CookieMap) {
|
||||
function verifySession(cookie: Bun.CookieMap): Session | null {
|
||||
const signedSessionId = cookie.get(SESSION_ID_COOKIE_NAME)
|
||||
if (!signedSessionId) {
|
||||
console.log("no cookie")
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -84,18 +78,23 @@ function verifySession(cookie: Bun.CookieMap): Session | null {
|
||||
|
||||
const isEqual = a.length === b.length && crypto.timingSafeEqual(a, b)
|
||||
if (!isEqual) {
|
||||
console.log("not equal")
|
||||
return null
|
||||
}
|
||||
|
||||
const findSessionQuery = db.query("SELECT user_id, expires_at_unix_ms FROM sessions WHERE session_id = $sessionId")
|
||||
const row = findSessionQuery.get({ sessionId: value })
|
||||
if (!row) {
|
||||
console.log("no row")
|
||||
return null
|
||||
}
|
||||
const foundSession = row as { user_id: string; expires_at_unix_ms: number }
|
||||
|
||||
const now = dayjs().valueOf()
|
||||
if (now > foundSession.expires_at_unix_ms) {
|
||||
const deleteSessionQuery = db.query("DELETE FROM sessions WHERE session_id = $sessionId")
|
||||
deleteSessionQuery.run({ sessionId: value })
|
||||
console.log("session expired!")
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -110,6 +109,9 @@ function verifySession(cookie: Bun.CookieMap): Session | null {
|
||||
|
||||
function extendSession(session: Session): Session {
|
||||
const newExpiryDate = dayjs().add(30, "minutes").valueOf()
|
||||
const extendSessionQuery = db.query(
|
||||
"UPDATE sessions SET expires_at_unix_ms = $newExpiryDate WHERE session_id = $session_id",
|
||||
)
|
||||
extendSessionQuery.run({
|
||||
sessionId: session.id,
|
||||
newExpiryDate,
|
||||
@@ -121,6 +123,7 @@ function extendSession(session: Session): Session {
|
||||
}
|
||||
|
||||
function forgetAllSessions(user: User) {
|
||||
const forgetAllSessionsQuery = db.query("DELETE FROM sessions WHERE user_id = $userId")
|
||||
forgetAllSessionsQuery.run({ userId: user.id })
|
||||
}
|
||||
|
||||
|
24
packages/server/src/bookmark/bookmark.ts
Normal file
24
packages/server/src/bookmark/bookmark.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { User } from "@markone/core/user"
|
||||
import { db } from "~/database.ts"
|
||||
import { DEMO_BOOKMARKS } from "./demo-bookmarks.ts"
|
||||
|
||||
function insertDemoBookmarks(user: User) {
|
||||
const query = db.query(`
|
||||
INSERT OR IGNORE INTO bookmarks (id, user_id, kind, title, url)
|
||||
VALUES ($id, $userId, $kind, $title, $url)
|
||||
`)
|
||||
const insert = db.transaction((bookmarks) => {
|
||||
for (const bookmark of bookmarks) {
|
||||
query.run({
|
||||
id: bookmark.id,
|
||||
userId: user.id,
|
||||
kind: bookmark.kind,
|
||||
title: bookmark.title,
|
||||
url: bookmark.url,
|
||||
})
|
||||
}
|
||||
})
|
||||
insert(DEMO_BOOKMARKS)
|
||||
}
|
||||
|
||||
export { insertDemoBookmarks }
|
66
packages/server/src/bookmark/demo-bookmarks.ts
Normal file
66
packages/server/src/bookmark/demo-bookmarks.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import type { Bookmark } from "@markone/core/bookmark"
|
||||
|
||||
const DEMO_BOOKMARKS: Bookmark[] = [
|
||||
{
|
||||
kind: "link",
|
||||
id: "01JTKBKVMWRRDR20PY9X4HGPVY",
|
||||
title: "ULID Specification",
|
||||
url: "https://github.com/ulid/spec",
|
||||
},
|
||||
{
|
||||
kind: "link",
|
||||
id: "01JTKBKVMW8392A9361PFGZEGM",
|
||||
title: "Another Example Site",
|
||||
url: "https://www.example.net",
|
||||
},
|
||||
{
|
||||
kind: "link",
|
||||
id: "01JTKBKVMW3YKY4CMANEK7502N",
|
||||
title: "Documentation Hub",
|
||||
url: "https://docs.example.com",
|
||||
},
|
||||
{
|
||||
kind: "link",
|
||||
id: "01JTKBKVMWHH7QG0CFQ6Q40E2G",
|
||||
title: "API Reference",
|
||||
url: "https://api.example.com/docs",
|
||||
},
|
||||
{
|
||||
kind: "link",
|
||||
id: "01JTKBKVMWVR4T3KXXB3PHZD60",
|
||||
title: "Blog Posts",
|
||||
url: "https://blog.example.com",
|
||||
},
|
||||
{
|
||||
kind: "link",
|
||||
id: "01JTKBKVMWAP0GGKWBN7Z3CYAM",
|
||||
title: "Support Forum",
|
||||
url: "https://forum.example.com",
|
||||
},
|
||||
{
|
||||
kind: "link",
|
||||
id: "01JTKBKVMW1YJB63F2YCMZNGFE",
|
||||
title: "Tutorials",
|
||||
url: "https://tutorials.example.com",
|
||||
},
|
||||
{
|
||||
kind: "link",
|
||||
id: "01JTKBKVMW93QYS2EK983RQS40",
|
||||
title: "Resource Library",
|
||||
url: "https://resources.example.com",
|
||||
},
|
||||
{
|
||||
kind: "link",
|
||||
id: "01JTKBKVMW6NG61138T8SCRFYT",
|
||||
title: "Community Page",
|
||||
url: "https://community.example.com",
|
||||
},
|
||||
{
|
||||
kind: "link",
|
||||
id: "01JTKBKVMWDBKSXYHGFXC0HEZB",
|
||||
title: "Project Repository",
|
||||
url: "https://github.com/example/project",
|
||||
},
|
||||
]
|
||||
|
||||
export { DEMO_BOOKMARKS }
|
@@ -1,3 +1,4 @@
|
||||
import { DEMO_USER } from "@markone/core/user"
|
||||
import { type } from "arktype"
|
||||
import { db } from "~/database.ts"
|
||||
import { HttpError } from "~/error.ts"
|
||||
@@ -10,16 +11,16 @@ const ListUserBookmarksParams = type({
|
||||
skip: ["number", "=", 5],
|
||||
})
|
||||
|
||||
const listBookmarksQuery = db.query(
|
||||
"SELECT id, kind, title, url FROM bookmarks WHERE user_id = $userId LIMIT $limit OFFSET $skip",
|
||||
)
|
||||
|
||||
async function listUserBookmarks(request: Bun.BunRequest<"/api/bookmarks">, user: User) {
|
||||
const queryParams = ListUserBookmarksParams(request.params)
|
||||
if (queryParams instanceof type.errors) {
|
||||
throw new HttpError(400, queryParams.summary)
|
||||
}
|
||||
|
||||
const listBookmarksQuery = db.query(
|
||||
"SELECT id, kind, title, url FROM bookmarks WHERE user_id = $userId ORDER BY id LIMIT $limit OFFSET $skip",
|
||||
)
|
||||
|
||||
const results = listBookmarksQuery.all({
|
||||
userId: user.id,
|
||||
limit: queryParams.limit,
|
||||
@@ -29,4 +30,16 @@ async function listUserBookmarks(request: Bun.BunRequest<"/api/bookmarks">, user
|
||||
return Response.json(results, { status: 200 })
|
||||
}
|
||||
|
||||
export { listUserBookmarks }
|
||||
async function deleteUserBookmark(request: Bun.BunRequest<"/api/bookmark/:id">, user: User) {
|
||||
console.log("askldjlskajdkl")
|
||||
if (user.id !== DEMO_USER.id) {
|
||||
const deleteBookmarkQuery = db.query("DELETE FROM bookmarks WHERE user_id = $userId AND id = $id")
|
||||
const tx = db.transaction(() => {
|
||||
deleteBookmarkQuery.run({ userId: user.id, id: request.params.id })
|
||||
})
|
||||
tx()
|
||||
}
|
||||
return Response.json(undefined, { status: 204 })
|
||||
}
|
||||
|
||||
export { listUserBookmarks, deleteUserBookmark }
|
||||
|
@@ -2,7 +2,7 @@ import { Database } from "bun:sqlite"
|
||||
|
||||
const SCHEMA_VERSION = 0
|
||||
|
||||
const db = new Database("data.sqlite")
|
||||
const db = new Database("data.sqlite", { strict: true })
|
||||
|
||||
const createMetadataTableQuery = db.query(`
|
||||
CREATE TABLE IF NOT EXISTS metadata(
|
||||
@@ -12,15 +12,12 @@ const createMetadataTableQuery = db.query(`
|
||||
);
|
||||
`)
|
||||
|
||||
const schemaVersionQuery = db.query("SELECT version FROM metadata WHERE key = 'schema_version'")
|
||||
const setSchemaVersionQuery = db.query("UPDATE metadata SET value = $schemaVersion WHERE key = 'schema_version'")
|
||||
|
||||
const migrations = [
|
||||
`
|
||||
CREATE TABLE IF NOT EXISTS users(
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
password TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bookmarks(
|
||||
@@ -47,17 +44,22 @@ CREATE TABLE IF NOT EXISTS auth_tokens(
|
||||
`,
|
||||
]
|
||||
|
||||
const executeMigrations = db.transaction((migration) => {
|
||||
db.run(migration)
|
||||
const executeMigrations = db.transaction((migrations) => {
|
||||
for (const migration of migrations) {
|
||||
db.run(migration)
|
||||
}
|
||||
})
|
||||
|
||||
function migrateDatabase() {
|
||||
createMetadataTableQuery.run()
|
||||
|
||||
const schemaVersionQuery = db.query("SELECT value FROM metadata WHERE key = 'schema_version'")
|
||||
const setSchemaVersionQuery = db.query("UPDATE metadata SET value = $schemaVersion WHERE key = 'schema_version'")
|
||||
|
||||
const row = schemaVersionQuery.get()
|
||||
let currentVersion: number
|
||||
if (row) {
|
||||
currentVersion = (row as { version: number }).version
|
||||
currentVersion = (row as { value: number }).value
|
||||
console.log(`Migrating database from version ${currentVersion} to version ${SCHEMA_VERSION}...`)
|
||||
} else {
|
||||
currentVersion = -1
|
||||
|
@@ -1,22 +1,46 @@
|
||||
import { HttpError } from "./error.ts"
|
||||
|
||||
type HttpMethod = "GET" | "POST" | "DELETE" | "PUT" | "OPTIONS" | "PATCH"
|
||||
|
||||
const ALLOWED_ORIGINS = ["http://localhost:5173"]
|
||||
|
||||
function httpHandler<Route extends string>(
|
||||
handler: (request: Bun.BunRequest<Route>) => Promise<Response>,
|
||||
): (request: Bun.BunRequest<Route>) => Promise<Response> {
|
||||
return async (request) => {
|
||||
let response: Response
|
||||
try {
|
||||
const response = await handler(request)
|
||||
return response
|
||||
response = await handler(request)
|
||||
} catch (error) {
|
||||
if (error instanceof HttpError) {
|
||||
if (error.message) {
|
||||
return Response.json({ message: error.message }, { status: error.status })
|
||||
response = Response.json({ message: error.message }, { status: error.status })
|
||||
} else {
|
||||
response = new Response(undefined, { status: error.status })
|
||||
}
|
||||
return new Response(undefined, { status: error.status })
|
||||
} else {
|
||||
console.error(error)
|
||||
response = new Response(undefined, { status: 500 })
|
||||
}
|
||||
return new Response(undefined, { status: 500 })
|
||||
}
|
||||
for (const origin of ALLOWED_ORIGINS) {
|
||||
response.headers.set("Access-Control-Allow-Origin", origin)
|
||||
}
|
||||
response.headers.set("Access-Control-Allow-Credentials", "true")
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
export { httpHandler }
|
||||
function preflightHandler<Route extends string>({ allowedMethods }: { allowedMethods: HttpMethod[] }) {
|
||||
return async (request: Bun.BunRequest<Route>) =>
|
||||
new Response(undefined, {
|
||||
status: 200,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": ALLOWED_ORIGINS,
|
||||
"Access-Control-Allow-Methods": allowedMethods.join(", "),
|
||||
"Access-Control-Allow-Credentials": "true",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export { httpHandler, preflightHandler }
|
||||
|
@@ -1,13 +1,15 @@
|
||||
import { authenticated, login, logout, signUp } from "./auth/auth.ts"
|
||||
import { startBackgroundSessionCleanup } from "./auth/session.ts"
|
||||
import { listUserBookmarks } from "./bookmark/handlers.ts"
|
||||
import { insertDemoBookmarks } from "./bookmark/bookmark.ts"
|
||||
import { listUserBookmarks, deleteUserBookmark } from "./bookmark/handlers.ts"
|
||||
import { migrateDatabase } from "./database.ts"
|
||||
import { httpHandler } from "./http-handler.ts"
|
||||
import { httpHandler, preflightHandler } from "./http-handler.ts"
|
||||
import { createDemoUser } from "./user/user.ts"
|
||||
|
||||
function main() {
|
||||
async function main() {
|
||||
migrateDatabase()
|
||||
createDemoUser()
|
||||
const user = await createDemoUser()
|
||||
insertDemoBookmarks(user)
|
||||
startBackgroundSessionCleanup()
|
||||
|
||||
Bun.serve({
|
||||
@@ -24,7 +26,15 @@ function main() {
|
||||
"/api/bookmarks": {
|
||||
GET: authenticated(listUserBookmarks),
|
||||
},
|
||||
"/api/bookmark/:id": {
|
||||
DELETE: authenticated(deleteUserBookmark),
|
||||
OPTIONS: preflightHandler({
|
||||
allowedMethods: ["GET", "POST", "DELETE", "OPTIONS"],
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
port: 8080,
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -1,20 +1,17 @@
|
||||
import { type User, DEMO_USER } from "@markone/core/user"
|
||||
import { DEMO_USER, type User } from "@markone/core/user"
|
||||
import { ulid } from "ulid"
|
||||
import { db } from "~/database.ts"
|
||||
import { DEMO_BOOKMARKS } from "~/bookmark/demo-bookmarks.ts"
|
||||
|
||||
interface UserWithPassword extends User {
|
||||
password: string
|
||||
}
|
||||
|
||||
const findUserByIdQuery = db.query("SELECT id, username FROM users WHERE id = $userId")
|
||||
|
||||
const findUserByUsernameQuery = db.query("SELECT id, username FROM users WHERE username = $username")
|
||||
const findUserByUsernameWithPwQuery = db.query("SELECT id, username, password FROM users WHERE username = $username")
|
||||
|
||||
const createUserQuery = db.query("INSERT INTO users(id, username, password) VALUES ($id, $username, $password)")
|
||||
|
||||
function findUserByUsername(username: string, opts: { password: true }): UserWithPassword | null
|
||||
function findUserByUsername(username: string, { password }: { password?: boolean }): User | UserWithPassword | null {
|
||||
const findUserByUsernameQuery = db.query("SELECT id, username FROM users WHERE username = $username")
|
||||
const findUserByUsernameWithPwQuery = db.query("SELECT id, username, password FROM users WHERE username = $username")
|
||||
|
||||
const row = (password ? findUserByUsernameWithPwQuery : findUserByUsernameQuery).get({ username })
|
||||
if (!row) {
|
||||
return null
|
||||
@@ -23,6 +20,7 @@ function findUserByUsername(username: string, { password }: { password?: boolean
|
||||
}
|
||||
|
||||
function findUserById(userId: string): User | null {
|
||||
const findUserByIdQuery = db.query("SELECT id, username FROM users WHERE id = $userId")
|
||||
const row = findUserByIdQuery.get({ userId })
|
||||
if (!row) {
|
||||
return null
|
||||
@@ -31,6 +29,7 @@ function findUserById(userId: string): User | null {
|
||||
}
|
||||
|
||||
function createUser(username: string, password: string): User {
|
||||
const createUserQuery = db.query("INSERT INTO users(id, username, password) VALUES ($id, $username, $password)")
|
||||
const userId = ulid()
|
||||
createUserQuery.run({
|
||||
id: userId,
|
||||
@@ -43,19 +42,17 @@ function createUser(username: string, password: string): User {
|
||||
}
|
||||
}
|
||||
|
||||
async function createDemoUser() {
|
||||
const row = findUserByUsernameQuery.get({ username: DEMO_USER.username })
|
||||
if (row) {
|
||||
return
|
||||
}
|
||||
|
||||
async function createDemoUser(): Promise<User> {
|
||||
const createUserQuery = db.query(
|
||||
"INSERT OR REPLACE INTO users(id, username, password) VALUES ($id, $username, $password)",
|
||||
)
|
||||
const hashedPassword = await Bun.password.hash(DEMO_USER.unhashedPassword, "argon2id")
|
||||
|
||||
createUserQuery.run({
|
||||
id: DEMO_USER.id,
|
||||
username: DEMO_USER.username,
|
||||
password: hashedPassword,
|
||||
})
|
||||
return DEMO_USER
|
||||
}
|
||||
|
||||
export type { User }
|
||||
|
Reference in New Issue
Block a user