Skip to content

Commit

Permalink
feat: quiz access
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinand11 committed Dec 3, 2023
1 parent a091ac0 commit b9b56a3
Show file tree
Hide file tree
Showing 16 changed files with 205 additions and 101 deletions.
19 changes: 19 additions & 0 deletions services/api/src/application/controllers/study/quizzes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,23 @@ export class QuizController {
if (updatedQuiz) return updatedQuiz
throw new NotAuthorizedError()
}

static async requestAccess (req: Request) {
const { add } = validate({ add: Schema.boolean() }, req.body)

const updatedQuiz = await QuizzesUseCases.requestAccess({ id: req.params.id, userId: req.authUser!.id, add })
if (updatedQuiz) return updatedQuiz
throw new NotAuthorizedError()
}

static async grantAccess (req: Request) {
const { userId, grant } = validate({
userId: Schema.string().min(1),
grant: Schema.boolean()
}, req.body)

const updatedQuiz = await QuizzesUseCases.grantAccess({ id: req.params.id, ownerId: req.authUser!.id, userId, grant })
if (updatedQuiz) return updatedQuiz
throw new NotAuthorizedError()
}
}
24 changes: 24 additions & 0 deletions services/api/src/application/routes/study/quizzes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,29 @@ export const quizzesRoutes = groupRoutes('/quizzes', [
}
})
]
}, {
path: '/:id/access/request',
method: 'post',
controllers: [
isAuthenticated,
makeController(async (req) => {
return {
status: StatusCodes.Ok,
result: await QuizController.requestAccess(req)
}
})
]
}, {
path: '/:id/access/grant',
method: 'post',
controllers: [
isAuthenticated,
makeController(async (req) => {
return {
status: StatusCodes.Ok,
result: await QuizController.grantAccess(req)
}
})
]
}
])
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export enum NotificationType {
WalletFundSuccessful = 'WalletFundSuccessful',
SubscriptionSuccessful = 'SubscriptionSuccessful',
SubscriptionFailed = 'SubscriptionFailed',
NewQuizAccessRequest = 'NewQuizAccessRequest',
QuizAccessRequestGranted = 'QuizAccessRequestGranted',
QuizAccessRequestRejected = 'QuizAccessRequestRejected',
}

export type NotificationData =
Expand All @@ -29,4 +32,7 @@ export type NotificationData =
| { type: NotificationType.WithdrawalFailed, withdrawalId: string, amount: number, currency: string }
| { type: NotificationType.WalletFundSuccessful, amount: number, currency: string }
| { type: NotificationType.SubscriptionSuccessful, planId: string }
| { type: NotificationType.SubscriptionFailed, planId: string }
| { type: NotificationType.SubscriptionFailed, planId: string }
| { type: NotificationType.NewQuizAccessRequest, userIds: string[] }
| { type: NotificationType.QuizAccessRequestGranted, quizId: string }
| { type: NotificationType.QuizAccessRequestRejected, quizId: string }
7 changes: 5 additions & 2 deletions services/api/src/modules/study/data/mappers/quizzes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import { QuizFromModel, QuizToModel } from '../models/quizzes'
export class QuizMapper extends BaseMapper<QuizFromModel, QuizToModel, QuizEntity> {
mapFrom (model: QuizFromModel | null) {
if (!model) return null
const { _id, title, description, photo, questions, courseId, user, topicId, tagIds, ratings, status, meta, isForTutors, createdAt, updatedAt } = model
const {
_id, title, description, photo, questions, courseId, user, topicId, tagIds,
access, ratings, status, meta, isForTutors, createdAt, updatedAt
} = model
return new QuizEntity({
id: _id.toString(), title, description, photo, questions, courseId, ratings,
user, topicId, tagIds, status, meta, isForTutors, createdAt, updatedAt
access, user, topicId, tagIds, status, meta, isForTutors, createdAt, updatedAt
})
}

Expand Down
3 changes: 2 additions & 1 deletion services/api/src/modules/study/data/models/quizzes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { CoursableData, QuizMeta } from '../../domain/types'
import { CoursableData, QuizAccess, QuizMeta } from '../../domain/types'

export interface QuizFromModel extends QuizToModel {
_id: string
questions: string[]
access: QuizAccess
ratings: CoursableData['ratings']
meta: Record<QuizMeta, number>
createdAt: number
Expand Down
12 changes: 12 additions & 0 deletions services/api/src/modules/study/data/mongooseModels/quizzes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ const Schema = new appInstance.dbs.mongo.Schema<QuizFromModel>({
type: [String],
required: true
},
access: {
members: {
type: [String],
required: false,
default: () => []
},
requests: {
type: [String],
required: false,
default: () => []
}
},
meta: Object.fromEntries(
Object.values(QuizMeta).map((meta) => [meta, {
type: Number,
Expand Down
19 changes: 19 additions & 0 deletions services/api/src/modules/study/data/repositories/quizzes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,23 @@ export class QuizRepository implements IQuizRepository {
})
return res
}

async requestAccess(id: string, userId: string, add: boolean) {
const quiz = await Quiz.findOneAndUpdate({
_id: id, 'access.requests': { [add ? '$nin' : '$in']: userId }, 'user.id': { $ne: userId }
}, {
[add ? '$addToSet' : '$pull']: { 'access.requests': userId }
})
return !!quiz
}

async grantAccess(id: string, ownerId: string, userId: string, grant: boolean) {
const quiz = await Quiz.findByIdAndUpdate({
_id: id, 'user.id': ownerId, 'access.requests': { $in: userId }
}, {
$pull: { 'access.requests': userId },
...(grant ? { $addToSet: { 'access.members': userId } } : {})
})
return !!quiz
}
}
47 changes: 47 additions & 0 deletions services/api/src/modules/study/domain/entities/coursables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { generateDefaultUser } from '@modules/users'
import { BaseEntity } from 'equipped'
import { CoursableData, Publishable } from '../types'

export class PublishableEntity extends BaseEntity implements Publishable {
public readonly id: string
public readonly title: Publishable['title']
public readonly description: Publishable['description']
public readonly photo: Publishable['photo']
public readonly user: Publishable['user']
public readonly topicId: Publishable['topicId']
public readonly tagIds: Publishable['tagIds']
public readonly status: Publishable['status']
public readonly ratings: Publishable['ratings']
public readonly createdAt: number
public readonly updatedAt: number

constructor ({ id, title, description, photo, user, topicId, tagIds, ratings, status, createdAt, updatedAt }: PublishableConstructorArgs) {
super()
this.id = id
this.title = title
this.description = description
this.photo = photo
this.user = generateDefaultUser(user)
this.topicId = topicId
this.tagIds = tagIds
this.ratings = ratings
this.status = status
this.createdAt = createdAt
this.updatedAt = updatedAt
}
}

type PublishableConstructorArgs = Publishable & {
id: string
createdAt: number
updatedAt: number
}

export class CoursableEntity extends PublishableEntity implements CoursableData {
public readonly courseId: string | null

constructor (data: PublishableConstructorArgs & { courseId: string | null }) {
super(data)
this.courseId = data.courseId
}
}
41 changes: 9 additions & 32 deletions services/api/src/modules/study/domain/entities/courses.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,20 @@
import { generateDefaultUser } from '@modules/users'
import { BaseEntity } from 'equipped'
import { Coursable, CourseMeta, CourseSections, Publishable, Saleable } from '../types'
import { PublishableEntity } from './coursables'

export class CourseEntity extends BaseEntity implements Publishable, Saleable {
public readonly id: string
export class CourseEntity extends PublishableEntity implements Publishable, Saleable {
public readonly coursables: { id: string, type: Coursable }[]
public readonly sections: CourseSections
public readonly title: Publishable['title']
public readonly description: Publishable['description']
public readonly photo: Publishable['photo']
public readonly user: Publishable['user']
public readonly topicId: Publishable['topicId']
public readonly tagIds: Publishable['tagIds']
public readonly ratings: Publishable['ratings']
public readonly status: Publishable['status']
public readonly frozen: Saleable['frozen']
public readonly price: Saleable['price']
public readonly meta: Record<CourseMeta, number>
public readonly createdAt: number
public readonly updatedAt: number

constructor ({ id, coursables, sections, title, description, photo, user, topicId, tagIds, ratings, status, frozen, price, meta, createdAt, updatedAt }: CourseConstructorArgs) {
super()
this.id = id
this.coursables = coursables
this.sections = sections
this.title = title
this.description = description
this.photo = photo
this.user = generateDefaultUser(user)
this.topicId = topicId
this.tagIds = tagIds
this.ratings = ratings
this.status = status
this.frozen = frozen
this.price = price
this.meta = meta
this.createdAt = createdAt
this.updatedAt = updatedAt
constructor (data: CourseConstructorArgs) {
super(data)
this.coursables = data.coursables
this.sections = data.sections
this.frozen = data.frozen
this.price = data.price
this.meta = data.meta
}

getCoursables () {
Expand Down
37 changes: 6 additions & 31 deletions services/api/src/modules/study/domain/entities/files.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,15 @@
import { generateDefaultUser } from '@modules/users'
import { BaseEntity } from 'equipped'
import { CoursableData, FileType, Media } from '../types'
import { CoursableEntity } from './coursables'

export class FileEntity extends BaseEntity implements CoursableData {
public readonly id: string
export class FileEntity extends CoursableEntity implements CoursableData {
public readonly type: FileType
public readonly media: Media
public readonly title: CoursableData['title']
public readonly description: CoursableData['description']
public readonly photo: CoursableData['photo']
public readonly courseId: CoursableData['courseId']
public readonly user: CoursableData['user']
public readonly topicId: CoursableData['topicId']
public readonly tagIds: CoursableData['tagIds']
public readonly ratings: CoursableData['ratings']
public readonly status: CoursableData['status']
public readonly createdAt: number
public readonly updatedAt: number
ignoreInJSON = ['media']

constructor ({ id, title, description, media, photo, type, courseId, user, topicId, tagIds, ratings, status, createdAt, updatedAt }: FileConstructorArgs) {
super()
this.id = id
this.title = title
this.description = description
this.photo = photo
this.type = type
this.media = media
this.courseId = courseId
this.user = generateDefaultUser(user)
this.topicId = topicId
this.tagIds = tagIds
this.ratings = ratings
this.status = status
this.createdAt = createdAt
this.updatedAt = updatedAt
constructor (data: FileConstructorArgs) {
super(data)
this.type = data.type
this.media = data.media
}
}

Expand Down
44 changes: 11 additions & 33 deletions services/api/src/modules/study/domain/entities/quizzes.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,26 @@
import { generateDefaultUser } from '@modules/users'
import { BaseEntity } from 'equipped'
import { CoursableData, QuizMeta } from '../types'
import { CoursableData, QuizAccess, QuizMeta } from '../types'
import { CoursableEntity } from './coursables'

export class QuizEntity extends BaseEntity implements CoursableData {
public readonly id: string
export class QuizEntity extends CoursableEntity implements CoursableData {
public readonly questions: string[]
public readonly title: CoursableData['title']
public readonly description: CoursableData['description']
public readonly photo: CoursableData['photo']
public readonly courseId: CoursableData['courseId']
public readonly user: CoursableData['user']
public readonly topicId: CoursableData['topicId']
public readonly tagIds: CoursableData['tagIds']
public readonly status: CoursableData['status']
public readonly ratings: CoursableData['ratings']
public readonly meta: Record<QuizMeta, number>
public readonly access: QuizAccess
public readonly isForTutors: boolean
public readonly createdAt: number
public readonly updatedAt: number

constructor ({ id, title, description, photo, questions, courseId, user, topicId, tagIds, ratings, status, meta, isForTutors, createdAt, updatedAt }: QuizConstructorArgs) {
super()
this.id = id
this.title = title
this.description = description
this.photo = photo
this.questions = questions
this.courseId = courseId
this.user = generateDefaultUser(user)
this.topicId = topicId
this.tagIds = tagIds
this.ratings = ratings
this.status = status
this.meta = meta
this.isForTutors = isForTutors
this.createdAt = createdAt
this.updatedAt = updatedAt
constructor (data: QuizConstructorArgs) {
super(data)
this.questions = data.questions
this.meta = data.meta
this.access = data.access
this.isForTutors = data.isForTutors
}
}

type QuizConstructorArgs = CoursableData & {
id: string
questions: string[]
meta: Record<QuizMeta, number>
access: QuizAccess
isForTutors: boolean
createdAt: number
updatedAt: number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ export interface IQuizRepository {
reorder: (id: string, userId: string, questionIds: string[]) => Promise<QuizEntity | null>
updateMeta: (id: string, property: QuizMeta, value: 1 | -1) => Promise<void>
updateRatings (id: string, ratings: number, add: boolean): Promise<boolean>
requestAccess (id: string, userId: string, add: boolean): Promise<boolean>
grantAccess (id: string, ownerId: string, userId: string, grant: boolean): Promise<boolean>
}
5 changes: 5 additions & 0 deletions services/api/src/modules/study/domain/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,9 @@ export enum QuizMeta {
export enum CourseMeta {
purchases = 'purchases',
total = 'total'
}

export type QuizAccess = {
requests: string[]
members: string[]
}
8 changes: 8 additions & 0 deletions services/api/src/modules/study/domain/useCases/quizzes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,12 @@ export class QuizzesUseCase {
async updateRatings (input: { id: string, ratings: number, add: boolean }) {
return await this.repository.updateRatings(input.id, input.ratings, input.add)
}

async requestAccess (data: { id: string, userId: string, add: boolean }) {
return await this.repository.requestAccess(data.id, data.userId, data.add)
}

async grantAccess (data: { id: string, ownerId: string, userId: string, grant: boolean }) {
return await this.repository.grantAccess(data.id, data.ownerId, data.userId, data.grant)
}
}
Loading

0 comments on commit b9b56a3

Please sign in to comment.