aboutsummaryrefslogtreecommitdiffhomepage
path: root/pkgs/server/src/repositories
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-12-03 06:05:33 +0900
committernsfisis <nsfisis@gmail.com>2025-12-04 23:26:34 +0900
commitdbcfe8a98e46e15fe951e5e98c68fd6ac8bde1b3 (patch)
tree5780b2d0b0f32735477ff8f1f3308720d059ce67 /pkgs/server/src/repositories
parent20f159abf06599bc8902a445be21d4c085d82ede (diff)
downloadkioku-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.ts3
-rw-r--r--pkgs/server/src/repositories/refresh-token.ts35
-rw-r--r--pkgs/server/src/repositories/types.ts46
-rw-r--r--pkgs/server/src/repositories/user.ts55
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;
+ },
+};