Skip to content

Commit 1dd55c4

Browse files
feat: Add User DAO
This change introduces a Data Access Object (DAO) for the User model. This is the first step in a larger effort to abstract the database layer and eventually migrate to PostgreSQL. The new UserDAO encapsulates all database interactions for the User model, and the GraphQL logic for users has been refactored to use this new DAO. Unit tests for the DAO have also been added.
1 parent 1016d72 commit 1dd55c4

4 files changed

Lines changed: 265 additions & 28 deletions

File tree

apps/web/dao/user.dao.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { UserDao } from "./user.dao";
2+
import UserModel from "@models/User";
3+
import mongoose from "mongoose";
4+
5+
afterEach(async () => {
6+
await UserModel.deleteMany({});
7+
});
8+
9+
describe("UserDao", () => {
10+
let userDao: UserDao;
11+
12+
beforeEach(() => {
13+
userDao = new UserDao();
14+
});
15+
16+
it("should create a user", async () => {
17+
const userData = {
18+
email: "test@example.com",
19+
name: "Test User",
20+
userId: "123",
21+
unsubscribeToken: "token",
22+
domain: new mongoose.Types.ObjectId().toString(),
23+
};
24+
const user = await userDao.createUser(userData);
25+
expect(user).toBeDefined();
26+
expect(user.email).toBe(userData.email);
27+
});
28+
29+
it("should get a user by id", async () => {
30+
const userData = {
31+
email: "test@example.com",
32+
name: "Test User",
33+
userId: "123",
34+
unsubscribeToken: "token",
35+
domain: new mongoose.Types.ObjectId().toString(),
36+
};
37+
await userDao.createUser(userData);
38+
const user = await userDao.getUserById("123", userData.domain);
39+
expect(user).toBeDefined();
40+
expect(user!.email).toBe(userData.email);
41+
});
42+
43+
it("should get a user by email", async () => {
44+
const userData = {
45+
email: "test@example.com",
46+
name: "Test User",
47+
userId: "123",
48+
unsubscribeToken: "token",
49+
domain: new mongoose.Types.ObjectId().toString(),
50+
};
51+
await userDao.createUser(userData);
52+
const user = await userDao.getUserByEmail("test@example.com", userData.domain);
53+
expect(user).toBeDefined();
54+
expect(user!.email).toBe(userData.email);
55+
});
56+
57+
it("should update a user", async () => {
58+
const userData = {
59+
email: "test@example.com",
60+
name: "Test User",
61+
userId: "123",
62+
unsubscribeToken: "token",
63+
domain: new mongoose.Types.ObjectId().toString(),
64+
};
65+
await userDao.createUser(userData);
66+
const updatedUser = await userDao.updateUser("123", userData.domain, {
67+
name: "Updated Test User",
68+
});
69+
expect(updatedUser).toBeDefined();
70+
expect(updatedUser!.name).toBe("Updated Test User");
71+
});
72+
});

apps/web/dao/user.dao.ts

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { User } from "@courselit/common-models";
2+
import UserModel from "@models/User";
3+
import { makeModelTextSearchable } from "@/lib/graphql";
4+
5+
export interface IUserDao {
6+
getUserById(userId: string, domainId: string): Promise<User | null>;
7+
getUserByEmail(email: string, domainId: string): Promise<User | null>;
8+
createUser(userData: Partial<User>): Promise<User>;
9+
updateUser(
10+
userId: string,
11+
domainId: string,
12+
userData: Partial<User>,
13+
): Promise<User | null>;
14+
find(query: any): Promise<User[]>;
15+
findOne(query: any): Promise<User | null>;
16+
findOneAndUpdate(
17+
query: any,
18+
update: any,
19+
options: any,
20+
): Promise<User | null>;
21+
countDocuments(query: any): Promise<number>;
22+
updateMany(query: any, update: any): Promise<any>;
23+
aggregate(pipeline: any[]): Promise<any[]>;
24+
deleteUser(userId: string, domainId: string): Promise<any>;
25+
search(
26+
{
27+
offset,
28+
query,
29+
graphQLContext,
30+
}: { offset: number; query: any; graphQLContext: any },
31+
{
32+
itemsPerPage,
33+
sortByColumn,
34+
sortOrder,
35+
}: {
36+
itemsPerPage: number;
37+
sortByColumn: string;
38+
sortOrder: number;
39+
},
40+
): Promise<User[]>;
41+
}
42+
43+
export class UserDao implements IUserDao {
44+
public async getUserById(
45+
userId: string,
46+
domainId: string,
47+
): Promise<User | null> {
48+
const user = await UserModel.findOne({
49+
userId,
50+
domain: domainId,
51+
}).lean();
52+
return user as User;
53+
}
54+
55+
public async getUserByEmail(
56+
email: string,
57+
domainId: string,
58+
): Promise<User | null> {
59+
const user = await UserModel.findOne({
60+
email,
61+
domain: domainId,
62+
}).lean();
63+
return user as User;
64+
}
65+
66+
public async createUser(userData: Partial<User>): Promise<User> {
67+
const user = new UserModel(userData);
68+
const newUser = await user.save();
69+
return newUser.toObject();
70+
}
71+
72+
public async updateUser(
73+
userId: string,
74+
domainId: string,
75+
userData: Partial<User>,
76+
): Promise<User | null> {
77+
const user = await UserModel.findOneAndUpdate(
78+
{ userId, domain: domainId },
79+
{ $set: userData },
80+
{ new: true },
81+
).lean();
82+
return user as User;
83+
}
84+
85+
public async find(query: any): Promise<User[]> {
86+
const users = await UserModel.find(query).lean();
87+
return users as User[];
88+
}
89+
90+
public async findOne(query: any): Promise<User | null> {
91+
const user = await UserModel.findOne(query).lean();
92+
return user as User;
93+
}
94+
95+
public async findOneAndUpdate(
96+
query: any,
97+
update: any,
98+
options: any,
99+
): Promise<User | null> {
100+
const user = await UserModel.findOneAndUpdate(
101+
query,
102+
update,
103+
options,
104+
).lean();
105+
return user as User;
106+
}
107+
108+
public async countDocuments(query: any): Promise<number> {
109+
return await UserModel.countDocuments(query);
110+
}
111+
112+
public async updateMany(query: any, update: any): Promise<any> {
113+
return await UserModel.updateMany(query, update);
114+
}
115+
116+
public async aggregate(pipeline: any[]): Promise<any[]> {
117+
return await UserModel.aggregate(pipeline);
118+
}
119+
120+
public async deleteUser(userId: string, domainId: string): Promise<any> {
121+
return await UserModel.deleteOne({ userId, domain: domainId });
122+
}
123+
124+
public async search(
125+
{
126+
offset,
127+
query,
128+
graphQLContext,
129+
}: { offset: number; query: any; graphQLContext: any },
130+
{
131+
itemsPerPage,
132+
sortByColumn,
133+
sortOrder,
134+
}: {
135+
itemsPerPage: number;
136+
sortByColumn: string;
137+
sortOrder: number;
138+
},
139+
): Promise<User[]> {
140+
const searchUsers = makeModelTextSearchable(UserModel);
141+
const users = await searchUsers(
142+
{
143+
offset,
144+
query,
145+
graphQLContext,
146+
},
147+
{
148+
itemsPerPage,
149+
sortByColumn,
150+
sortOrder,
151+
},
152+
);
153+
return users.map((user: any) => user.toObject());
154+
}
155+
}

0 commit comments

Comments
 (0)