Skip to content

Commit

Permalink
feat: support founder member share course pack
Browse files Browse the repository at this point in the history
  • Loading branch information
cuixiaorui committed Jul 10, 2024
1 parent c2d93ba commit f7b9f73
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 3 deletions.
35 changes: 33 additions & 2 deletions apps/api/src/course-pack/course-pack.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,45 @@ import { course, coursePack } from "@earthworm/schema";
import { CourseHistoryService } from "../course-history/course-history.service";
import { CourseService } from "../course/course.service";
import { DB, DbType } from "../global/providers/db.provider";
import { MembershipService } from "../membership/membership.service";

@Injectable()
export class CoursePackService {
constructor(
@Inject(DB) private db: DbType,
private readonly courseService: CourseService,
private readonly courseHistoryService: CourseHistoryService,
private readonly membershipService: MembershipService,
) {}

async findAll(userId?: string) {
const userIdOwnedCoursePacks = userId ? await this.findAllForUser(userId) : [];
let result = [];

const publicCoursePacks = await this.findAllPublicCoursePacks();
result.push(...publicCoursePacks);

if (userId) {
const userIdOwnedCoursePacks = await this.findAllForUser(userId);
result.push(...userIdOwnedCoursePacks);

// 看看是不是创始会员
// 是的话 需要去查所有课程包的 shareLevel 为 founder_only 的
if (await this.membershipService.checkFounderMembership(userId)) {
const founderOnlyCoursePacks = await this.findFounderOnly();
result.push(...founderOnlyCoursePacks);
}
}

return [...userIdOwnedCoursePacks, ...publicCoursePacks];
return result;
}

async findFounderOnly() {
const coursePacks = await this.db.query.coursePack.findMany({
orderBy: asc(coursePack.order),
where: and(eq(coursePack.shareLevel, "founder_only")), // TODO 缺一个 shareLevel 的枚举类型
});

return coursePacks;
}

async findAllForUser(userId: string) {
Expand Down Expand Up @@ -83,6 +108,12 @@ export class CoursePackService {
} else {
throw new NotFoundException(`CoursePack with ID ${coursePackId} not found`);
}
} else if (coursePackWithCourses.shareLevel === "founder_only") {
if (await this.membershipService.checkFounderMembership(userId)) {
return coursePackWithCourses;
} else {
throw new NotFoundException(`CoursePack with ID ${coursePackId} not found`);
}
} else {
return coursePackWithCourses;
}
Expand Down
54 changes: 54 additions & 0 deletions apps/api/src/course-pack/tests/course-pack.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { endDB } from "../../common/db";
import { CourseHistoryService } from "../../course-history/course-history.service";
import { CourseService } from "../../course/course.service";
import { DB } from "../../global/providers/db.provider";
import { MembershipService } from "../../membership/membership.service";
import { CoursePackService } from "../course-pack.service";

describe("CoursePackService", () => {
Expand Down Expand Up @@ -64,6 +65,24 @@ describe("CoursePackService", () => {

expect(result.length).toBe(2); // user1's private pack
});

it("should return all course packs including founder_only for a founder member", async () => {
await insertCoursePack(db, { creatorId: "founderUser", shareLevel: "private" });
await insertCoursePack(db, { creatorId: "admin", shareLevel: "founder_only" });

const result = await coursePackService.findAll("founderUser");

expect(result.length).toBe(2); // founderUser's private pack and founder_only pack
});

it("should return only private course packs for a non-founder member", async () => {
await insertCoursePack(db, { creatorId: "nonFounderUser", shareLevel: "private" });
await insertCoursePack(db, { creatorId: "admin", shareLevel: "founder_only" });

const result = await coursePackService.findAll("nonFounderUser");

expect(result.length).toBe(1); // nonFounderUser's private pack
});
});

describe("findOne", () => {
Expand Down Expand Up @@ -138,6 +157,33 @@ describe("CoursePackService", () => {
expect(result.courses.length).toBe(1);
expect(result.courses[0]).not.toHaveProperty("completionCount");
});

it("should return a course pack with courses and completion counts when userId is provided and user is a founder member", async () => {
const userId = "founderUser";
const coursePackEntity = await insertCoursePack(db, {
shareLevel: "founder_only",
creatorId: "another-user-id", // The creator can be different from the founder member
});
await insertCourse(db, coursePackEntity.id);

const result = await coursePackService.findOneWithCourses(userId, coursePackEntity.id);

expect(result.courses.length).toBe(1);
expect(result.courses[0]).toHaveProperty("completionCount");
});

it("should throw NotFoundException when course pack is founder_only and user is not a founder member", async () => {
const userId = "nonFounderUser";
const coursePackEntity = await insertCoursePack(db, {
shareLevel: "founder_only",
creatorId: "another-user-id", // The creator can be different from the non-founder member
});
await insertCourse(db, coursePackEntity.id);

await expect(
coursePackService.findOneWithCourses(userId, coursePackEntity.id),
).rejects.toThrow(NotFoundException);
});
});

describe("findCourse", () => {
Expand Down Expand Up @@ -188,6 +234,10 @@ async function setupTesting() {
findCompletionCount: jest.fn(() => 1),
};

const MockMembershipService = {
checkFounderMembership: jest.fn((userId) => userId === "founderUser"),
};

const moduleRef = await Test.createTestingModule({
imports: testImportModules,
providers: [
Expand All @@ -197,6 +247,10 @@ async function setupTesting() {
provide: CourseHistoryService,
useValue: MockCourseHistoryService,
},
{
provide: MembershipService,
useValue: MockMembershipService,
},
],
}).compile();

Expand Down
10 changes: 10 additions & 0 deletions apps/api/src/membership/membership.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@ export class MembershipService {
};
}

public async checkFounderMembership(userId: string) {
let result = await this.db
.select()
.from(membership)
.where(and(eq(membership.userId, userId), eq(membership.type, "founder")));
// 如果有会员记录并且type为"founder",则用户是创始会员
// 创始会员永久有效 所以不需要检查 active
return Boolean(result[0]);
}

@Cron("0 0 * * *")
async deactivateExpiredMemberships(currentDate: Date) {
this.logger.log("Running scheduled task to deactivate expired memberships");
Expand Down
38 changes: 38 additions & 0 deletions apps/api/src/membership/tests/membership.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,44 @@ describe("MembershipService", () => {

expect(result.isActive).toBe(false);
});

it("should return true if the user is a founder member", async () => {
const userId = "founderUser";
await db.insert(membership).values({
userId,
type: "founder",
start_date: new Date(),
end_date: new Date(),
isActive: true,
});

const result = await service.checkFounderMembership(userId);

expect(result).toBe(true);
});

it("should return false if the user is not a founder member", async () => {
const userId = "nonFounderUser";
await db.insert(membership).values({
userId,
type: "regular",
start_date: new Date(),
end_date: new Date(),
isActive: true,
});

const result = await service.checkFounderMembership(userId);

expect(result).toBe(false);
});

it("should return false if the user has no membership record", async () => {
const userId = "noMembershipUser";

const result = await service.checkFounderMembership(userId);

expect(result).toBe(false);
});
});

async function insertMembership(db: DbType, isActive: boolean) {
Expand Down
2 changes: 1 addition & 1 deletion packages/api-hub/src/routes/course-pack/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const coursePackSchema = {

export const updateCoursePackSchema = {
type: "object",
required: ["title", "description", "cover", "courses", "uId"],
required: ["title", "description", "cover", "courses", "uId", "shareLevel"],
properties: {
title: { type: "string" },
description: { type: "string" },
Expand Down
1 change: 1 addition & 0 deletions packages/api-hub/src/routes/course-pack/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export async function updateCoursePack(coursePackId: string, coursePackInfo: Upd
title: coursePackInfo.title,
description: coursePackInfo.description,
cover: coursePackInfo.cover,
shareLevel: coursePackInfo.shareLevel,
})
.where(eq(coursePackSchema.id, coursePackId));
}
Expand Down

0 comments on commit f7b9f73

Please sign in to comment.