Skip to content

Commit

Permalink
feat: add missing thumbnail, refactor course responses and missing us…
Browse files Browse the repository at this point in the history
…er details (#387)
  • Loading branch information
wielopolski authored Jan 16, 2025
1 parent ca66409 commit 22cd07b
Show file tree
Hide file tree
Showing 21 changed files with 107 additions and 87 deletions.
4 changes: 2 additions & 2 deletions apps/api/src/courses/course.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { Roles } from "src/common/decorators/roles.decorator";
import { CurrentUser } from "src/common/decorators/user.decorator";
import { RolesGuard } from "src/common/guards/roles.guard";
import { CourseService } from "src/courses/course.service";
import { allCoursesSchema } from "src/courses/schemas/course.schema";
import { allCoursesForTeacherSchema } from "src/courses/schemas/course.schema";
import {
SortCourseFieldsOptions,
CourseEnrollmentScope,
Expand Down Expand Up @@ -174,7 +174,7 @@ export class CourseController {
},
{ type: "query", name: "excludeCourseId", schema: UUIDSchema },
],
response: baseResponse(allCoursesSchema),
response: baseResponse(allCoursesForTeacherSchema),
})
async getTeacherCourses(
@Query("authorId") authorId: UUIDType,
Expand Down
11 changes: 6 additions & 5 deletions apps/api/src/courses/course.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,10 +331,9 @@ export class CourseService {

const dataWithS3SignedUrls = await Promise.all(
data.map(async (item) => {
if (!item.thumbnailUrl) return item;

try {
const signedUrl = await this.fileService.getFileUrl(item.thumbnailUrl);

return { ...item, thumbnailUrl: signedUrl };
} catch (error) {
console.error(`Failed to get signed URL for ${item.thumbnailUrl}:`, error);
Expand Down Expand Up @@ -380,8 +379,11 @@ export class CourseService {
})
.from(courses)
.leftJoin(categories, eq(courses.categoryId, categories.id))
.leftJoin(studentCourses, eq(courses.id, studentCourses.courseId))
.where(and(eq(courses.id, id), eq(studentCourses.studentId, userId)));
.leftJoin(
studentCourses,
and(eq(courses.id, studentCourses.courseId), eq(studentCourses.studentId, userId)),
)
.where(eq(courses.id, id));

if (!course) throw new NotFoundException("Course not found");

Expand Down Expand Up @@ -1042,7 +1044,6 @@ export class CourseService {
excludeCourseId?: UUIDType,
) {
const conditions = [];

if (authorId) {
conditions.push(eq(courses.authorId, authorId));
}
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/file/file.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class FileService {
) {}

async getFileUrl(fileKey: string): Promise<string> {
if (!fileKey) return "https://app.lms.localhost/app/assets/placeholders/card-placeholder.jpg";
if (fileKey.startsWith("https://")) return fileKey;

try {
Expand Down
11 changes: 10 additions & 1 deletion apps/api/src/swagger/api-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3607,19 +3607,28 @@
},
"hasFreeChapters": {
"type": "boolean"
},
"completedChapterCount": {
"type": "number"
},
"enrolled": {
"type": "boolean"
}
},
"required": [
"id",
"title",
"thumbnailUrl",
"description",
"authorId",
"author",
"authorEmail",
"category",
"courseChapterCount",
"enrolledParticipantCount",
"priceInCents",
"currency"
"currency",
"completedChapterCount"
]
}
}
Expand Down
7 changes: 5 additions & 2 deletions apps/api/src/user/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,11 @@ export class UserController {
request: [{ type: "query", name: "userId", schema: UUIDSchema, required: true }],
response: baseResponse(userDetailsSchema),
})
async getUserDetails(@Query("userId") userId: UUIDType): Promise<BaseResponse<UserDetails>> {
const userDetails = await this.usersService.getUserDetails(userId);
async getUserDetails(
@Query("userId") userId: UUIDType,
@CurrentUser("role") role: UserRole,
): Promise<BaseResponse<UserDetails>> {
const userDetails = await this.usersService.getUserDetails(userId, role);
return new BaseResponse(userDetails);
}

Expand Down
29 changes: 26 additions & 3 deletions apps/api/src/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export class UserService {
return user;
}

public async getUserDetails(userId: UUIDType): Promise<UserDetails> {
public async getUserDetails(userId: UUIDType, userRole: UserRole): Promise<UserDetails> {
const [userBio]: UserDetails[] = await this.db
.select({
firstName: users.firstName,
Expand All @@ -105,8 +105,31 @@ export class UserService {
.leftJoin(users, eq(userDetails.userId, users.id))
.where(eq(userDetails.userId, userId));

if (!userBio) {
throw new NotFoundException("User details not found");
if (!userBio && (USER_ROLES.TEACHER === userRole || USER_ROLES.ADMIN === userRole)) {
// TODO: quick
// throw new NotFoundException("User details not found");
const [user] = await this.db
.select({
id: users.id,
email: users.email,
firstName: users.firstName,
lastName: users.lastName,
})
.from(users)
.where(eq(users.id, userId));

const [userBio] = await this.db
.insert(userDetails)
.values({ userId, contactEmail: user.email })
.returning({
id: userDetails.id,
description: userDetails.description,
contactEmail: userDetails.contactEmail,
contactPhone: userDetails.contactPhoneNumber,
jobTitle: userDetails.jobTitle,
});

return { firstName: user.firstName, lastName: user.lastName, ...userBio };
}

return userBio;
Expand Down
34 changes: 16 additions & 18 deletions apps/web/app/api/generated-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,6 @@ export interface GetAllCoursesResponse {
authorEmail?: string;
category: string;
courseChapterCount: number;
completedChapterCount: number;
enrolled?: boolean;
enrolledParticipantCount: number;
priceInCents: number;
currency: string;
Expand Down Expand Up @@ -361,14 +359,14 @@ export interface GetStudentCoursesResponse {
authorEmail?: string;
category: string;
courseChapterCount: number;
completedChapterCount: number;
enrolled?: boolean;
enrolledParticipantCount: number;
priceInCents: number;
currency: string;
isPublished?: boolean;
createdAt?: string;
hasFreeChapters?: boolean;
completedChapterCount: number;
enrolled?: boolean;
}[];
pagination: {
totalItems: number;
Expand All @@ -391,14 +389,14 @@ export interface GetAvailableCoursesResponse {
authorEmail?: string;
category: string;
courseChapterCount: number;
completedChapterCount: number;
enrolled?: boolean;
enrolledParticipantCount: number;
priceInCents: number;
currency: string;
isPublished?: boolean;
createdAt?: string;
hasFreeChapters?: boolean;
completedChapterCount: number;
enrolled?: boolean;
}[];
pagination: {
totalItems: number;
Expand All @@ -416,19 +414,19 @@ export interface GetTeacherCoursesResponse {
thumbnailUrl: string | null;
description: string;
/** @format uuid */
authorId?: string;
authorId: string;
author: string;
authorEmail?: string;
authorEmail: string;
category: string;
courseChapterCount: number;
completedChapterCount: number;
enrolled?: boolean;
enrolledParticipantCount: number;
priceInCents: number;
currency: string;
isPublished?: boolean;
createdAt?: string;
hasFreeChapters?: boolean;
completedChapterCount: number;
enrolled?: boolean;
}[];
}

Expand All @@ -451,12 +449,12 @@ export interface GetCourseResponse {
title: string;
type: "text" | "presentation" | "video" | "quiz";
displayOrder: number;
status: "completed" | "in_progress" | "not_started";
status: "not_started" | "in_progress" | "completed";
quizQuestionCount: number | null;
isExternal?: boolean;
}[];
completedLessonCount?: number;
chapterProgress?: "completed" | "in_progress" | "not_started";
chapterProgress?: "not_started" | "in_progress" | "completed";
isFreemium?: boolean;
enrolled?: boolean;
isSubmitted?: boolean;
Expand Down Expand Up @@ -539,7 +537,7 @@ export interface GetBetaCourseByIdResponse {
updatedAt?: string;
}[];
completedLessonCount?: number;
chapterProgress?: "completed" | "in_progress" | "not_started";
chapterProgress?: "not_started" | "in_progress" | "completed";
isFreemium?: boolean;
enrolled?: boolean;
isSubmitted?: boolean;
Expand Down Expand Up @@ -703,12 +701,12 @@ export interface GetChapterWithLessonResponse {
title: string;
type: "text" | "presentation" | "video" | "quiz";
displayOrder: number;
status: "completed" | "in_progress" | "not_started";
status: "not_started" | "in_progress" | "completed";
quizQuestionCount: number | null;
isExternal?: boolean;
}[];
completedLessonCount?: number;
chapterProgress?: "completed" | "in_progress" | "not_started";
chapterProgress?: "not_started" | "in_progress" | "completed";
isFreemium?: boolean;
enrolled?: boolean;
isSubmitted?: boolean;
Expand Down Expand Up @@ -765,7 +763,7 @@ export type BetaCreateChapterBody = {
}[];
updatedAt?: string;
}[];
chapterProgress?: "completed" | "in_progress" | "not_started";
chapterProgress?: "not_started" | "in_progress" | "completed";
isFreemium?: boolean;
enrolled?: boolean;
isSubmitted?: boolean;
Expand Down Expand Up @@ -831,7 +829,7 @@ export type UpdateChapterBody = {
}[];
updatedAt?: string;
}[];
chapterProgress?: "completed" | "in_progress" | "not_started";
chapterProgress?: "not_started" | "in_progress" | "completed";
isFreemium?: boolean;
enrolled?: boolean;
isSubmitted?: boolean;
Expand Down Expand Up @@ -1604,7 +1602,7 @@ export class API<SecurityDataType extends unknown> extends HttpClient<SecurityDa
userControllerGetUsers: (
query?: {
keyword?: string;
role?: string;
role?: "admin" | "student" | "teacher";
archived?: string;
/** @min 1 */
page?: number;
Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/api/queries/useAvailableCourses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useQuery, useSuspenseQuery } from "@tanstack/react-query";

import { ApiClient } from "../api-client";

import type { GetAllCoursesResponse } from "../generated-api";
import type { GetAvailableCoursesResponse } from "../generated-api";
import type { SortOption } from "~/types/sorting";

type CourseParams = {
Expand All @@ -27,7 +27,7 @@ export const availableCoursesQueryOptions = (searchParams?: CourseParams) => ({
});
return response.data;
},
select: (data: GetAllCoursesResponse) => data.data,
select: (data: GetAvailableCoursesResponse) => data.data,
});

export function useAvailableCourses(searchParams?: CourseParams) {
Expand Down
4 changes: 2 additions & 2 deletions apps/web/app/api/queries/useStudentCourses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useQuery, useSuspenseQuery } from "@tanstack/react-query";

import { ApiClient } from "../api-client";

import type { GetAllCoursesResponse } from "../generated-api";
import type { GetStudentCoursesResponse } from "../generated-api";
import type { SortOption } from "~/types/sorting";

type CourseParams = {
Expand All @@ -25,7 +25,7 @@ export const studentCoursesQueryOptions = (searchParams?: CourseParams) => ({
});
return response.data;
},
select: (data: GetAllCoursesResponse) => data.data,
select: (data: GetStudentCoursesResponse) => data.data,
});

export function useStudentCourses(searchParams?: CourseParams) {
Expand Down
3 changes: 2 additions & 1 deletion apps/web/app/api/queries/useUsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
import { ApiClient } from "../api-client";

import type { GetUsersResponse } from "../generated-api";
import type { UserRole } from "~/config/userRoles";

type UsersParams = {
keyword?: string;
role?: string;
role?: UserRole;
archived?: boolean;
sort?: string;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,9 @@ const QuizLessonForm = ({
}

if (!noOptionsRequiredTypes.includes(type)) {
return [{ sortableId: crypto.randomUUID(), optionText: "", isCorrect: false, displayOrder: 1 }];
return [
{ sortableId: crypto.randomUUID(), optionText: "", isCorrect: false, displayOrder: 1 },
];
}

return [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ const AnswerSelectQuestion = ({ form, questionIndex }: AnswerSelectQuestionProps
return (
<SortableList.Item id={item.sortableId}>
<div className="mt-2">
<div className="border border-neutral-200 p-2 pr-3 rounded-xl flex items-center space-x-2">
<div className="flex items-center space-x-2 rounded-xl border border-neutral-200 p-2 pr-3">
<SortableList.DragHandle>
<Icon name="DragAndDropIcon" className="cursor-move ml-4 mr-3" />
<Icon name="DragAndDropIcon" className="ml-4 mr-3 cursor-move" />
</SortableList.DragHandle>
<Input
type="text"
Expand All @@ -138,7 +138,7 @@ const AnswerSelectQuestion = ({ form, questionIndex }: AnswerSelectQuestionProps
name={`questions.${questionIndex}.options.${index}.isCorrect`}
checked={item.isCorrect === true}
onChange={() => handleOptionChange(index, "isCorrect", true)}
className="w-4 h-4 cursor-pointer"
className="h-4 w-4 cursor-pointer"
/>
) : (
<div className="cursor-pointer">
Expand All @@ -157,7 +157,7 @@ const AnswerSelectQuestion = ({ form, questionIndex }: AnswerSelectQuestionProps
onClick={() =>
handleOptionChange(index, "isCorrect", !item.isCorrect)
}
className="ml-2 body-sm align-middle text-neutral-950 cursor-pointer"
className="body-sm ml-2 cursor-pointer align-middle text-neutral-950"
>
{t("adminCourseView.curriculum.lesson.other.correct")}
</Label>
Expand All @@ -167,15 +167,15 @@ const AnswerSelectQuestion = ({ form, questionIndex }: AnswerSelectQuestionProps
<div className="group">
<Icon
name="TrashIcon"
className="text-error-500 bg-error-50 ml-3 cursor-pointer w-7 h-7 group-hover:text-white group-hover:bg-error-600 rounded-lg p-1"
className="text-error-500 bg-error-50 group-hover:bg-error-600 ml-3 h-7 w-7 cursor-pointer rounded-lg p-1 group-hover:text-white"
onClick={() => handleRemoveOption(index)}
/>
</div>
</TooltipTrigger>
<TooltipContent
side="top"
align="center"
className="bg-black ml-4 text-white text-sm rounded shadow-md"
className="ml-4 rounded bg-black text-sm text-white shadow-md"
>
{t("common.button.delete")}
</TooltipContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ const ScaleQuestion = ({ form, questionIndex }: ScaleQuestionProps) => {
className="grid grid-cols-1"
renderItem={(item, index: number) => (
<SortableList.Item id={item.sortableId}>
<div className="mt-2 flex items-center space-x-2 rounded-xl border border-neutral-200 p-2 pr-3">
<div className="mt-2 flex items-center space-x-2 rounded-xl border border-neutral-200 p-2 pr-3">
<SortableList.DragHandle>
<Icon name="DragAndDropIcon" className="ml-4 mr-3 cursor-move" />
</SortableList.DragHandle>
Expand Down
Loading

0 comments on commit 22cd07b

Please sign in to comment.