diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-12-03 06:05:33 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-12-04 23:26:34 +0900 |
| commit | dbcfe8a98e46e15fe951e5e98c68fd6ac8bde1b3 (patch) | |
| tree | 5780b2d0b0f32735477ff8f1f3308720d059ce67 /pkgs/server/src/repositories | |
| parent | 20f159abf06599bc8902a445be21d4c085d82ede (diff) | |
| download | kioku-dbcfe8a98e46e15fe951e5e98c68fd6ac8bde1b3.tar.gz kioku-dbcfe8a98e46e15fe951e5e98c68fd6ac8bde1b3.tar.zst kioku-dbcfe8a98e46e15fe951e5e98c68fd6ac8bde1b3.zip | |
refactor(auth): introduce repository pattern for database access
Add repository types and implementations to abstract database operations,
improving testability and separation of concerns. The auth routes now use
dependency injection with UserRepository and RefreshTokenRepository
interfaces, making tests simpler by mocking interfaces instead of Drizzle
query builders.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'pkgs/server/src/repositories')
| -rw-r--r-- | pkgs/server/src/repositories/index.ts | 3 | ||||
| -rw-r--r-- | pkgs/server/src/repositories/refresh-token.ts | 35 | ||||
| -rw-r--r-- | pkgs/server/src/repositories/types.ts | 46 | ||||
| -rw-r--r-- | pkgs/server/src/repositories/user.ts | 55 |
4 files changed, 139 insertions, 0 deletions
diff --git a/pkgs/server/src/repositories/index.ts b/pkgs/server/src/repositories/index.ts new file mode 100644 index 0000000..f1bcfb1 --- /dev/null +++ b/pkgs/server/src/repositories/index.ts @@ -0,0 +1,3 @@ +export { refreshTokenRepository } from "./refresh-token"; +export * from "./types"; +export { userRepository } from "./user"; diff --git a/pkgs/server/src/repositories/refresh-token.ts b/pkgs/server/src/repositories/refresh-token.ts new file mode 100644 index 0000000..82302df --- /dev/null +++ b/pkgs/server/src/repositories/refresh-token.ts @@ -0,0 +1,35 @@ +import { and, eq, gt } from "drizzle-orm"; +import { db, refreshTokens } from "../db"; +import type { RefreshTokenRepository } from "./types"; + +export const refreshTokenRepository: RefreshTokenRepository = { + async findValidToken(tokenHash) { + const [token] = await db + .select({ + id: refreshTokens.id, + userId: refreshTokens.userId, + expiresAt: refreshTokens.expiresAt, + }) + .from(refreshTokens) + .where( + and( + eq(refreshTokens.tokenHash, tokenHash), + gt(refreshTokens.expiresAt, new Date()), + ), + ) + .limit(1); + return token; + }, + + async create(data) { + await db.insert(refreshTokens).values({ + userId: data.userId, + tokenHash: data.tokenHash, + expiresAt: data.expiresAt, + }); + }, + + async deleteById(id) { + await db.delete(refreshTokens).where(eq(refreshTokens.id, id)); + }, +}; diff --git a/pkgs/server/src/repositories/types.ts b/pkgs/server/src/repositories/types.ts new file mode 100644 index 0000000..1ab4bdc --- /dev/null +++ b/pkgs/server/src/repositories/types.ts @@ -0,0 +1,46 @@ +/** + * Repository types for abstracting database operations + */ + +export interface User { + id: string; + username: string; + passwordHash: string; + createdAt: Date; + updatedAt: Date; +} + +export interface UserPublic { + id: string; + username: string; + createdAt: Date; +} + +export interface RefreshToken { + id: string; + userId: string; + tokenHash: string; + expiresAt: Date; + createdAt: Date; +} + +export interface UserRepository { + findByUsername( + username: string, + ): Promise<Pick<User, "id" | "username" | "passwordHash"> | undefined>; + existsByUsername(username: string): Promise<boolean>; + create(data: { username: string; passwordHash: string }): Promise<UserPublic>; + findById(id: string): Promise<Pick<User, "id" | "username"> | undefined>; +} + +export interface RefreshTokenRepository { + findValidToken( + tokenHash: string, + ): Promise<Pick<RefreshToken, "id" | "userId" | "expiresAt"> | undefined>; + create(data: { + userId: string; + tokenHash: string; + expiresAt: Date; + }): Promise<void>; + deleteById(id: string): Promise<void>; +} diff --git a/pkgs/server/src/repositories/user.ts b/pkgs/server/src/repositories/user.ts new file mode 100644 index 0000000..7917632 --- /dev/null +++ b/pkgs/server/src/repositories/user.ts @@ -0,0 +1,55 @@ +import { eq } from "drizzle-orm"; +import { db, users } from "../db"; +import type { UserPublic, UserRepository } from "./types"; + +export const userRepository: UserRepository = { + async findByUsername(username) { + const [user] = await db + .select({ + id: users.id, + username: users.username, + passwordHash: users.passwordHash, + }) + .from(users) + .where(eq(users.username, username)) + .limit(1); + return user; + }, + + async existsByUsername(username) { + const [user] = await db + .select({ id: users.id }) + .from(users) + .where(eq(users.username, username)) + .limit(1); + return user !== undefined; + }, + + async create(data): Promise<UserPublic> { + const [newUser] = await db + .insert(users) + .values({ + username: data.username, + passwordHash: data.passwordHash, + }) + .returning({ + id: users.id, + username: users.username, + createdAt: users.createdAt, + }); + // Insert with returning should always return the created row + return newUser!; + }, + + async findById(id) { + const [user] = await db + .select({ + id: users.id, + username: users.username, + }) + .from(users) + .where(eq(users.id, id)) + .limit(1); + return user; + }, +}; |
