diff --git a/backend/src/application.ts b/backend/src/application.ts index fc233c3..1c14da7 100644 --- a/backend/src/application.ts +++ b/backend/src/application.ts @@ -15,6 +15,8 @@ import { UserResolver } from './graphql-resolvers/user.resolver'; import { SzakdolgozatAuthChecker } from './szakdolgozat-auth-checker'; import { CourseResolver } from './graphql-resolvers/course.resolver'; import { SubjectResolver } from './graphql-resolvers/subject.resolver'; +import { FulfillmentModeResolver } from './graphql-resolvers/fulfillment-mode.resolver'; +import { RequirementResolver } from './graphql-resolvers/requirement.resolver'; export { ApplicationConfig }; @@ -36,6 +38,8 @@ export class SzakdolgozatBackendApplication extends BootMixin( s.resolver(UserResolver); s.resolver(CourseResolver); s.resolver(SubjectResolver); + s.resolver(FulfillmentModeResolver); + s.resolver(RequirementResolver); }); // Authentication diff --git a/backend/src/controllers/course-requirement.controller.ts b/backend/src/controllers/course-requirement.controller.ts deleted file mode 100644 index 4ac39de..0000000 --- a/backend/src/controllers/course-requirement.controller.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { - Count, - CountSchema, - Filter, - repository, - Where, -} from '@loopback/repository'; -import { - del, - get, - getModelSchemaRef, - getWhereSchemaFor, - param, - patch, - post, - requestBody, -} from '@loopback/rest'; -import { - Course, - Requirement, -} from '../models'; -import { CourseRepository } from '../repositories'; - -export class CourseRequirementController { - constructor( - @repository(CourseRepository) protected courseRepository: CourseRepository, - ) { - } - - @get('/courses/{id}/requirements', { - responses: { - '200': { - description: 'Array of Course has many Requirement', - content: { - 'application/json': { - schema: {type: 'array', items: getModelSchemaRef(Requirement)}, - }, - }, - }, - }, - }) - async find( - @param.path.number('id') id: number, - @param.query.object('filter') filter?: Filter, - ): Promise { - return this.courseRepository.requirements(id).find(filter); - } - - @post('/courses/{id}/requirements', { - responses: { - '200': { - description: 'Course model instance', - content: {'application/json': {schema: getModelSchemaRef(Requirement)}}, - }, - }, - }) - async create( - @param.path.number('id') id: typeof Course.prototype.id, - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(Requirement, { - title: 'NewRequirementInCourse', - exclude: ['id'], - optional: ['courseId'] - }), - }, - }, - }) requirement: Omit, - ): Promise { - return this.courseRepository.requirements(id).create(requirement); - } - - @patch('/courses/{id}/requirements', { - responses: { - '200': { - description: 'Course.Requirement PATCH success count', - content: {'application/json': {schema: CountSchema}}, - }, - }, - }) - async patch( - @param.path.number('id') id: number, - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(Requirement, {partial: true}), - }, - }, - }) - requirement: Partial, - @param.query.object('where', getWhereSchemaFor(Requirement)) where?: Where, - ): Promise { - return this.courseRepository.requirements(id).patch(requirement, where); - } - - @del('/courses/{id}/requirements', { - responses: { - '200': { - description: 'Course.Requirement DELETE success count', - content: {'application/json': {schema: CountSchema}}, - }, - }, - }) - async delete( - @param.path.number('id') id: number, - @param.query.object('where', getWhereSchemaFor(Requirement)) where?: Where, - ): Promise { - return this.courseRepository.requirements(id).delete(where); - } -} diff --git a/backend/src/controllers/course-subject.controller.ts b/backend/src/controllers/course-subject.controller.ts deleted file mode 100644 index be31a3e..0000000 --- a/backend/src/controllers/course-subject.controller.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { - repository, -} from '@loopback/repository'; -import { - param, - get, - getModelSchemaRef, -} from '@loopback/rest'; -import { - Course, - Subject, -} from '../models'; -import { CourseRepository } from '../repositories'; - -export class CourseSubjectController { - constructor( - @repository(CourseRepository) - public courseRepository: CourseRepository, - ) { - } - - @get('/courses/{id}/subject', { - responses: { - '200': { - description: 'Subject belonging to Course', - content: { - 'application/json': { - schema: {type: 'array', items: getModelSchemaRef(Subject)}, - }, - }, - }, - }, - }) - async getSubject( - @param.path.number('id') id: typeof Course.prototype.id, - ): Promise { - return this.courseRepository.subject(id); - } -} diff --git a/backend/src/controllers/course-user.controller.ts b/backend/src/controllers/course-user.controller.ts deleted file mode 100644 index 020dc95..0000000 --- a/backend/src/controllers/course-user.controller.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { - Count, - CountSchema, - Filter, - repository, - Where, -} from '@loopback/repository'; -import { - del, - get, - getModelSchemaRef, - getWhereSchemaFor, - param, - patch, - post, - requestBody, -} from '@loopback/rest'; -import { - Course, - User, -} from '../models'; -import { CourseRepository } from '../repositories'; - -export class CourseUserController { - constructor( - @repository(CourseRepository) protected courseRepository: CourseRepository, - ) { - } - - @get('/courses/{id}/users', { - responses: { - '200': { - description: 'Array of Course has many User through CourseUser', - content: { - 'application/json': { - schema: {type: 'array', items: getModelSchemaRef(User)}, - }, - }, - }, - }, - }) - async find( - @param.path.number('id') id: number, - @param.query.object('filter') filter?: Filter, - ): Promise { - return this.courseRepository.users(id).find(filter); - } - - @post('/courses/{id}/users', { - responses: { - '200': { - description: 'create a User model instance', - content: {'application/json': {schema: getModelSchemaRef(User)}}, - }, - }, - }) - async create( - @param.path.number('id') id: typeof Course.prototype.id, - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(User, { - title: 'NewUserInCourse', - exclude: ['id'], - }), - }, - }, - }) user: Omit, - ): Promise { - return this.courseRepository.users(id).create(user); - } - - @patch('/courses/{id}/users', { - responses: { - '200': { - description: 'Course.User PATCH success count', - content: {'application/json': {schema: CountSchema}}, - }, - }, - }) - async patch( - @param.path.number('id') id: number, - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(User, {partial: true}), - }, - }, - }) - user: Partial, - @param.query.object('where', getWhereSchemaFor(User)) where?: Where, - ): Promise { - return this.courseRepository.users(id).patch(user, where); - } - - @del('/courses/{id}/users', { - responses: { - '200': { - description: 'Course.User DELETE success count', - content: {'application/json': {schema: CountSchema}}, - }, - }, - }) - async delete( - @param.path.number('id') id: number, - @param.query.object('where', getWhereSchemaFor(User)) where?: Where, - ): Promise { - return this.courseRepository.users(id).delete(where); - } -} diff --git a/backend/src/controllers/course.controller.ts b/backend/src/controllers/course.controller.ts deleted file mode 100644 index 35ef8b1..0000000 --- a/backend/src/controllers/course.controller.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { Count, CountSchema, Filter, FilterExcludingWhere, repository, Where, } from '@loopback/repository'; -import { del, get, getModelSchemaRef, param, patch, post, put, requestBody, response, } from '@loopback/rest'; -import { Course } from '../models'; -import { CourseRepository } from '../repositories'; - -export class CourseController { - constructor( - @repository(CourseRepository) - public courseRepository: CourseRepository, - ) { - } - - @post('/courses') - @response(200, { - description: 'Course model instance', - content: {'application/json': {schema: getModelSchemaRef(Course)}}, - }) - async create( - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(Course, { - title: 'NewCourse', - exclude: ['id'], - }), - }, - }, - }) - course: Omit, - ): Promise { - return this.courseRepository.create(course); - } - - @get('/courses/count') - @response(200, { - description: 'Course model count', - content: {'application/json': {schema: CountSchema}}, - }) - async count( - @param.where(Course) where?: Where, - ): Promise { - return this.courseRepository.count(where); - } - - @get('/courses') - @response(200, { - description: 'Array of Course model instances', - content: { - 'application/json': { - schema: { - type: 'array', - items: getModelSchemaRef(Course, {includeRelations: true}), - }, - }, - }, - }) - async find( - @param.filter(Course) filter?: Filter, - ): Promise { - return this.courseRepository.find(filter); - } - - @patch('/courses') - @response(200, { - description: 'Course PATCH success count', - content: {'application/json': {schema: CountSchema}}, - }) - async updateAll( - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(Course, {partial: true}), - }, - }, - }) - course: Course, - @param.where(Course) where?: Where, - ): Promise { - return this.courseRepository.updateAll(course, where); - } - - @get('/courses/{id}') - @response(200, { - description: 'Course model instance', - content: { - 'application/json': { - schema: getModelSchemaRef(Course, {includeRelations: true}), - }, - }, - }) - async findById( - @param.path.number('id') id: number, - @param.filter(Course, {exclude: 'where'}) filter?: FilterExcludingWhere - ): Promise { - return this.courseRepository.findById(id, filter); - } - - @patch('/courses/{id}') - @response(204, { - description: 'Course PATCH success', - }) - async updateById( - @param.path.number('id') id: number, - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(Course, {partial: true}), - }, - }, - }) - course: Course, - ): Promise { - await this.courseRepository.updateById(id, course); - } - - @put('/courses/{id}') - @response(204, { - description: 'Course PUT success', - }) - async replaceById( - @param.path.number('id') id: number, - @requestBody() course: Course, - ): Promise { - await this.courseRepository.replaceById(id, course); - } - - @del('/courses/{id}') - @response(204, { - description: 'Course DELETE success', - }) - async deleteById(@param.path.number('id') id: number): Promise { - await this.courseRepository.deleteById(id); - } -} diff --git a/backend/src/controllers/index.ts b/backend/src/controllers/index.ts index a84f1ce..e69de29 100644 --- a/backend/src/controllers/index.ts +++ b/backend/src/controllers/index.ts @@ -1,10 +0,0 @@ -export * from './ping.controller'; -export * from './user.controller'; -export * from './subject-course.controller'; -export * from './course-subject.controller'; -export * from './course-user.controller'; -export * from './user-course.controller'; -export * from './course-requirement.controller'; -export * from './course-requirement.controller'; -export * from './subject.controller'; -export * from './course.controller'; diff --git a/backend/src/controllers/ping.controller.ts b/backend/src/controllers/ping.controller.ts deleted file mode 100644 index 2ff11ed..0000000 --- a/backend/src/controllers/ping.controller.ts +++ /dev/null @@ -1,55 +0,0 @@ -import {inject} from '@loopback/core'; -import { - Request, - RestBindings, - get, - response, - ResponseObject, -} from '@loopback/rest'; - -/** - * OpenAPI response for ping() - */ -const PING_RESPONSE: ResponseObject = { - description: 'Ping Response', - content: { - 'application/json': { - schema: { - type: 'object', - title: 'PingResponse', - properties: { - greeting: {type: 'string'}, - date: {type: 'string'}, - url: {type: 'string'}, - headers: { - type: 'object', - properties: { - 'Content-Type': {type: 'string'}, - }, - additionalProperties: true, - }, - }, - }, - }, - }, -}; - -/** - * A simple controller to bounce back http requests - */ -export class PingController { - constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} - - // Map to `GET /ping` - @get('/ping') - @response(200, PING_RESPONSE) - ping(): object { - // Reply with a greeting, the current time, the url, and request headers - return { - greeting: 'Hello from LoopBack', - date: new Date(), - url: this.req.url, - headers: Object.assign({}, this.req.headers), - }; - } -} diff --git a/backend/src/controllers/subject-course.controller.ts b/backend/src/controllers/subject-course.controller.ts deleted file mode 100644 index 15a1e09..0000000 --- a/backend/src/controllers/subject-course.controller.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Count, CountSchema, Filter, repository, Where, } from '@loopback/repository'; -import { del, get, getModelSchemaRef, getWhereSchemaFor, param, patch, post, requestBody, response, } from '@loopback/rest'; -import { Course, Subject, } from '../models'; -import { SubjectRepository } from '../repositories'; - -export class SubjectCourseController { - constructor( - @repository(SubjectRepository) protected subjectRepository: SubjectRepository, - ) { - } - - @get('/subjects/{id}/courses', { - responses: { - '200': { - description: 'Array of Subject has many Course', - content: { - 'application/json': { - schema: {type: 'array', items: getModelSchemaRef(Course)}, - }, - }, - }, - }, - }) - async find( - @param.path.number('id') id: number, - @param.query.object('filter') filter?: Filter, - ): Promise { - return this.subjectRepository.courses(id).find(filter); - } - - @get('/subjects/{id}/courses/count') - @response(200, { - description: 'Course model count', - content: {'application/json': {schema: CountSchema}}, - }) - async count( - @param.path.number('id') id: number, - @param.where(Course) where?: Where, - ): Promise { - return this.subjectRepository.courses(id).count(where); - } - - @post('/subjects/{id}/courses', { - responses: { - '200': { - description: 'Subject model instance', - content: {'application/json': {schema: getModelSchemaRef(Course)}}, - }, - }, - }) - async create( - @param.path.number('id') id: typeof Subject.prototype.id, - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(Course, { - title: 'NewCourseInSubject', - exclude: ['id'], - optional: ['subjectId'] - }), - }, - }, - }) course: Omit, - ): Promise { - return this.subjectRepository.courses(id).create(course); - } - - @patch('/subjects/{id}/courses', { - responses: { - '200': { - description: 'Subject.Course PATCH success count', - content: {'application/json': {schema: CountSchema}}, - }, - }, - }) - async patch( - @param.path.number('id') id: number, - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(Course, {partial: true}), - }, - }, - }) - course: Partial, - @param.query.object('where', getWhereSchemaFor(Course)) where?: Where, - ): Promise { - return this.subjectRepository.courses(id).patch(course, where); - } - - @del('/subjects/{id}/courses', { - responses: { - '200': { - description: 'Subject.Course DELETE success count', - content: {'application/json': {schema: CountSchema}}, - }, - }, - }) - async delete( - @param.path.number('id') id: number, - @param.query.object('where', getWhereSchemaFor(Course)) where?: Where, - ): Promise { - return this.subjectRepository.courses(id).delete(where); - } -} diff --git a/backend/src/controllers/subject.controller.ts b/backend/src/controllers/subject.controller.ts deleted file mode 100644 index 6004f2f..0000000 --- a/backend/src/controllers/subject.controller.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { - Count, - CountSchema, - Filter, - FilterExcludingWhere, - repository, - Where, -} from '@loopback/repository'; -import { - post, - param, - get, - getModelSchemaRef, - patch, - put, - del, - requestBody, - response, -} from '@loopback/rest'; -import { Subject } from '../models'; -import { SubjectRepository } from '../repositories'; - -export class SubjectController { - constructor( - @repository(SubjectRepository) - public subjectRepository: SubjectRepository, - ) { - } - - @post('/subjects') - @response(200, { - description: 'Subject model instance', - content: {'application/json': {schema: getModelSchemaRef(Subject)}}, - }) - async create( - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(Subject, { - title: 'NewSubject', - exclude: ['id'], - }), - }, - }, - }) - subject: Omit, - ): Promise { - return this.subjectRepository.create(subject); - } - - @get('/subjects/count') - @response(200, { - description: 'Subject model count', - content: {'application/json': {schema: CountSchema}}, - }) - async count( - @param.where(Subject) where?: Where, - ): Promise { - return this.subjectRepository.count(where); - } - - @get('/subjects') - @response(200, { - description: 'Array of Subject model instances', - content: { - 'application/json': { - schema: { - type: 'array', - items: getModelSchemaRef(Subject, {includeRelations: true}), - }, - }, - }, - }) - async find( - @param.filter(Subject) filter?: Filter, - ): Promise { - return this.subjectRepository.find(filter); - } - - @patch('/subjects') - @response(200, { - description: 'Subject PATCH success count', - content: {'application/json': {schema: CountSchema}}, - }) - async updateAll( - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(Subject, {partial: true}), - }, - }, - }) - subject: Subject, - @param.where(Subject) where?: Where, - ): Promise { - return this.subjectRepository.updateAll(subject, where); - } - - @get('/subjects/{id}') - @response(200, { - description: 'Subject model instance', - content: { - 'application/json': { - schema: getModelSchemaRef(Subject, {includeRelations: true}), - }, - }, - }) - async findById( - @param.path.number('id') id: number, - @param.filter(Subject, {exclude: 'where'}) filter?: FilterExcludingWhere - ): Promise { - return this.subjectRepository.findById(id, filter); - } - - @patch('/subjects/{id}') - @response(204, { - description: 'Subject PATCH success', - }) - async updateById( - @param.path.number('id') id: number, - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(Subject, {partial: true}), - }, - }, - }) - subject: Subject, - ): Promise { - await this.subjectRepository.updateById(id, subject); - } - - @put('/subjects/{id}') - @response(204, { - description: 'Subject PUT success', - }) - async replaceById( - @param.path.number('id') id: number, - @requestBody() subject: Subject, - ): Promise { - await this.subjectRepository.replaceById(id, subject); - } - - @del('/subjects/{id}') - @response(204, { - description: 'Subject DELETE success', - }) - async deleteById(@param.path.number('id') id: number): Promise { - await this.subjectRepository.deleteById(id); - } -} diff --git a/backend/src/controllers/user-course.controller.ts b/backend/src/controllers/user-course.controller.ts deleted file mode 100644 index 6804587..0000000 --- a/backend/src/controllers/user-course.controller.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { - Count, - CountSchema, - Filter, - repository, - Where, -} from '@loopback/repository'; -import { - del, - get, - getModelSchemaRef, - getWhereSchemaFor, - param, - patch, - post, - requestBody, -} from '@loopback/rest'; -import { - User, - Course, -} from '../models'; -import { UserRepository } from '../repositories'; - -export class UserCourseController { - constructor( - @repository(UserRepository) protected userRepository: UserRepository, - ) { - } - - @get('/users/{id}/courses', { - responses: { - '200': { - description: 'Array of User has many Course through CourseUser', - content: { - 'application/json': { - schema: {type: 'array', items: getModelSchemaRef(Course)}, - }, - }, - }, - }, - }) - async find( - @param.path.number('id') id: number, - @param.query.object('filter') filter?: Filter, - ): Promise { - return this.userRepository.courses(id).find(filter); - } - - @post('/users/{id}/courses', { - responses: { - '200': { - description: 'create a Course model instance', - content: {'application/json': {schema: getModelSchemaRef(Course)}}, - }, - }, - }) - async create( - @param.path.number('id') id: typeof User.prototype.id, - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(Course, { - title: 'NewCourseInUser', - exclude: ['id'], - }), - }, - }, - }) course: Omit, - ): Promise { - return this.userRepository.courses(id).create(course); - } - - @patch('/users/{id}/courses', { - responses: { - '200': { - description: 'User.Course PATCH success count', - content: {'application/json': {schema: CountSchema}}, - }, - }, - }) - async patch( - @param.path.number('id') id: number, - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(Course, {partial: true}), - }, - }, - }) - course: Partial, - @param.query.object('where', getWhereSchemaFor(Course)) where?: Where, - ): Promise { - return this.userRepository.courses(id).patch(course, where); - } - - @del('/users/{id}/courses', { - responses: { - '200': { - description: 'User.Course DELETE success count', - content: {'application/json': {schema: CountSchema}}, - }, - }, - }) - async delete( - @param.path.number('id') id: number, - @param.query.object('where', getWhereSchemaFor(Course)) where?: Where, - ): Promise { - return this.userRepository.courses(id).delete(where); - } -} diff --git a/backend/src/controllers/user.controller.ts b/backend/src/controllers/user.controller.ts deleted file mode 100644 index c80fe02..0000000 --- a/backend/src/controllers/user.controller.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { Count, CountSchema, Filter, FilterExcludingWhere, repository, Where, } from '@loopback/repository'; -import { del, get, getModelSchemaRef, HttpErrors, param, patch, post, Request, requestBody, response, RestBindings, } from '@loopback/rest'; -import { User } from '../models'; -import { UserRepository } from '../repositories'; -import { - TokenServiceBindings, - UserServiceBindings -} from '@loopback/authentication-jwt'; -import { inject } from '@loopback/core'; -import { authenticate, TokenService } from '@loopback/authentication'; -import { SecurityBindings, UserProfile } from '@loopback/security'; -import { genSalt, hash } from 'bcryptjs'; -import { SzakdolgozatUserService } from '../services'; - -export class UserController { - constructor( - @inject(TokenServiceBindings.TOKEN_SERVICE) - public jwtService: TokenService, - @inject(UserServiceBindings.USER_SERVICE) - public userService: SzakdolgozatUserService, - @inject(SecurityBindings.USER, {optional: true}) - public user: UserProfile, - @repository(UserRepository) - public userRepository: UserRepository, - ) { - } - - @post('/users') - @response(200, { - description: 'User model instance', - content: {'application/json': {schema: getModelSchemaRef(User, {exclude: ['password']})}}, - }) - async register( - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(User, { - title: 'Registration request', - exclude: ['id', 'isAdmin'] - }), - }, - }, - }) - request: Pick, - ): Promise { - const password = await hash(request.password, await genSalt()); - const user = { - email: request.email, - name: request.name, - password: password, - isAdmin: false - } as User; - return this.userRepository.create(user); - } - - @post('/users/login', { - responses: { - '200': { - description: 'Token', - content: { - 'application/json': { - schema: { - type: 'object', - properties: { - token: { - type: 'string', - }, - user: getModelSchemaRef(User, {exclude: ['id', 'password']}) - }, - }, - }, - }, - }, - }, - }) - async login( - @requestBody({ - description: 'The input of login function', - required: true, - content: { - 'application/json': { - schema: getModelSchemaRef(User, {exclude: ['id', 'isAdmin', 'name']}) - }, - } - }) credentials: Pick, - ): Promise<{ token: string, user: Omit }> { - // ensure the user exists, and the password is correct - const user = await this.userService.verifyCredentials(credentials); - // convert a User object into a UserProfile object (reduced set of properties) - const userProfile = this.userService.convertToUserProfile(user); - - // create a JSON Web Token based on the user profile - const token = await this.jwtService.generateToken(userProfile); - return {token, user}; - } - - @post('/users/logout', { - responses: { - '204': { - description: 'Logged out', - }, - }, - }) - @authenticate('jwt') - async logout(@inject(RestBindings.Http.REQUEST) request: Request): Promise { - const split = request.headers.authorization?.split(' '); - if (split && split.length > 1) { - if (this.jwtService.revokeToken) { - await this.jwtService.revokeToken(split[1]); - } else { - console.error('Cannot revoke token'); - } - } - } - - @get('/users/count') - @response(200, { - description: 'User model count', - content: {'application/json': {schema: CountSchema}}, - }) - @authenticate('jwt') - async count( - @param.where(User) where?: Where, - ): Promise { - return this.userRepository.count(where); - } - - @get('/users') - @response(200, { - description: 'Array of User model instances', - content: { - 'application/json': { - schema: { - type: 'array', - items: getModelSchemaRef(User, {includeRelations: true}), - }, - }, - }, - }) - @authenticate('jwt') - async find( - @param.filter(User) filter?: Filter, - ): Promise { - return this.userRepository.find(filter); - } - - @patch('/users') - @response(200, { - description: 'User PATCH success count', - content: {'application/json': {schema: CountSchema}}, - }) - @authenticate('jwt') - async updateAll( - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(User, {partial: true}), - }, - }, - }) - user: User, - @param.where(User) where?: Where, - ): Promise { - return this.userRepository.updateAll(user, where); - } - - @get('/users/{id}') - @response(200, { - description: 'User model instance', - content: { - 'application/json': { - schema: getModelSchemaRef(User, {includeRelations: true}), - }, - }, - }) - @authenticate('jwt') - async findById( - @param.path.number('id') id: number, - @param.filter(User, {exclude: 'where'}) filter?: FilterExcludingWhere - ): Promise { - return this.userRepository.findById(id, filter); - } - - @patch('/users/{id}') - @response(204, { - description: 'User PATCH success', - }) - @authenticate('jwt') - async updateById( - @param.path.number('id') id: number, - @requestBody({ - content: { - 'application/json': { - schema: getModelSchemaRef(User, {partial: true}), - }, - }, - }) - user: User, - ): Promise { - if (id === +this.user.id) { - const loggedInUser = await this.userService.findUserById(this.user.id); - if (user.isAdmin !== undefined && loggedInUser.isAdmin !== user.isAdmin) { - throw new HttpErrors.BadRequest('Cannot change admin status of self'); - } - } - await this.userRepository.updateById(id, user); - } - - @del('/users/{id}') - @response(204, { - description: 'User DELETE success', - }) - @authenticate('jwt') - async deleteById(@param.path.number('id') id: number): Promise { - await this.userRepository.deleteById(id); - } -} diff --git a/backend/src/graphql-resolvers/course.resolver.ts b/backend/src/graphql-resolvers/course.resolver.ts index add79bc..c082558 100644 --- a/backend/src/graphql-resolvers/course.resolver.ts +++ b/backend/src/graphql-resolvers/course.resolver.ts @@ -2,9 +2,8 @@ import { repository } from '@loopback/repository'; import { CourseRepository, SubjectRepository, UserRepository } from '../repositories'; import { arg, ID, Int, mutation, query, resolver } from '@loopback/graphql'; import { Course } from '../models'; -import { CourseUpdateInput } from '../graphql-types/input/course-update.input'; import { listResponse, ListResponse } from '../graphql-types/list'; -import { CourseList } from '../graphql-types/course'; +import { CourseList, CourseUpdateInput } from '../graphql-types/course'; @resolver(of => Course) export class CourseResolver { @@ -35,4 +34,10 @@ export class CourseResolver { await this.courseRepo.updateById(input.id, input); return true; } + + @mutation(returns => Boolean) + async courseCreate(@arg('course') input: CourseUpdateInput): Promise { + await this.courseRepo.create(input); + return true; + } } diff --git a/backend/src/graphql-resolvers/fulfillment-mode.resolver.ts b/backend/src/graphql-resolvers/fulfillment-mode.resolver.ts new file mode 100644 index 0000000..78a1974 --- /dev/null +++ b/backend/src/graphql-resolvers/fulfillment-mode.resolver.ts @@ -0,0 +1,38 @@ +import { repository } from '@loopback/repository'; +import { CourseRepository, FulfillmentModeRepository, UserRepository } from '../repositories'; +import { arg, ID, Int, mutation, query, resolver } from '@loopback/graphql'; +import { FulfillmentMode } from '../models'; +import { listResponse, ListResponse } from '../graphql-types/list'; +import { FulfillmentModeList, FulfillmentModeUpdateInput } from '../graphql-types/fulfillment-mode'; + +@resolver(of => FulfillmentMode) +export class FulfillmentModeResolver { + constructor( + @repository('CourseRepository') private courseRepo: CourseRepository, + @repository('FulfillmentModeRepository') private fulfillmentModeRepo: FulfillmentModeRepository, + @repository('UserRepository') private userRepo: UserRepository + ) { + } + + @query(returns => FulfillmentModeList) + async fulfillmentModes(@arg('course', returns => ID) course: number, @arg('offset', returns => Int) offset: number, @arg('limit', returns => Int) limit: number): Promise> { + return listResponse(this.courseRepo.fulfillmentModes(course), offset, limit, FulfillmentModeList); + } + + @query(returns => FulfillmentMode) + async fulfillmentMode(@arg('id', returns => ID) id: number): Promise { + return await this.fulfillmentModeRepo.findById(id); + } + + @mutation(returns => Boolean) + async fulfillmentModeUpdate(@arg('input') input: FulfillmentModeUpdateInput): Promise { + await this.fulfillmentModeRepo.updateById(input.id, input); + return true; + } + + @mutation(returns => Boolean) + async fulfillmentModeCreate(@arg('input') input: FulfillmentModeUpdateInput): Promise { + await this.fulfillmentModeRepo.create(input); + return true; + } +} diff --git a/backend/src/graphql-resolvers/requirement.resolver.ts b/backend/src/graphql-resolvers/requirement.resolver.ts new file mode 100644 index 0000000..b729550 --- /dev/null +++ b/backend/src/graphql-resolvers/requirement.resolver.ts @@ -0,0 +1,38 @@ +import { repository } from '@loopback/repository'; +import { FulfillmentModeRepository, RequirementRepository, UserRepository } from '../repositories'; +import { arg, ID, Int, mutation, query, resolver } from '@loopback/graphql'; +import { Requirement } from '../models'; +import { listResponse, ListResponse } from '../graphql-types/list'; +import { RequirementList, RequirementUpdateInput } from '../graphql-types/requirement'; + +@resolver(of => Requirement) +export class RequirementResolver { + constructor( + @repository('FulfillmentModeRepository') private modeRepo: FulfillmentModeRepository, + @repository('RequirementRepository') private requirementRepo: RequirementRepository, + @repository('UserRepository') private userRepo: UserRepository + ) { + } + + @query(returns => RequirementList) + async requirements(@arg('mode', returns => ID) mode: number, @arg('offset', returns => Int) offset: number, @arg('limit', returns => Int) limit: number): Promise> { + return listResponse(this.modeRepo.requirements(mode), offset, limit, RequirementList); + } + + @query(returns => Requirement) + async requirement(@arg('id', returns => ID) id: number): Promise { + return await this.requirementRepo.findById(id); + } + + @mutation(returns => Boolean) + async requirementUpdate(@arg('requirement') input: RequirementUpdateInput): Promise { + await this.requirementRepo.updateById(input.id, input); + return true; + } + + @mutation(returns => Boolean) + async requirementCreate(@arg('requirement') input: RequirementUpdateInput): Promise { + await this.requirementRepo.create(input); + return true; + } +} diff --git a/backend/src/graphql-resolvers/subject.resolver.ts b/backend/src/graphql-resolvers/subject.resolver.ts index a4498fd..2e98e20 100644 --- a/backend/src/graphql-resolvers/subject.resolver.ts +++ b/backend/src/graphql-resolvers/subject.resolver.ts @@ -3,8 +3,7 @@ import { Subject } from '../models'; import { SubjectRepository } from '../repositories'; import { repository } from '@loopback/repository'; import { listResponse, ListResponse } from '../graphql-types/list'; -import { SubjectList } from '../graphql-types/subject'; -import { SubjectUpdateInput } from '../graphql-types/input/subject-update.input'; +import { SubjectList, SubjectUpdateInput } from '../graphql-types/subject'; @resolver(of => Subject) export class SubjectResolver { @@ -26,4 +25,10 @@ export class SubjectResolver { await this.subjectRepo.updateById(input.id, input); return true; } + + @mutation(returns => Boolean) + async subjectCreate(@arg('subject') input: SubjectUpdateInput): Promise { + await this.subjectRepo.create(input); + return true; + } } diff --git a/backend/src/graphql-resolvers/user.resolver.ts b/backend/src/graphql-resolvers/user.resolver.ts index 0a56e65..e7289f7 100644 --- a/backend/src/graphql-resolvers/user.resolver.ts +++ b/backend/src/graphql-resolvers/user.resolver.ts @@ -8,10 +8,8 @@ import { TokenServiceBindings, UserServiceBindings } from '@loopback/authenticat import { TokenService } from '@loopback/authentication'; import { SecurityBindings, UserProfile } from '@loopback/security'; import { genSalt, hash } from 'bcryptjs'; -import { UserRegisterInput } from '../graphql-types/input/user-register.input'; import { validated } from '../helpers'; -import { LoginResult, UserList } from '../graphql-types/user'; -import { UserUpdateInput } from '../graphql-types/input/user-update.input'; +import { LoginResult, UserList, UserRegisterInput, UserUpdateInput } from '../graphql-types/user'; import { SzakdolgozatBindings } from '../bindings'; import { listResponse } from '../graphql-types/list'; diff --git a/backend/src/graphql-types/course.ts b/backend/src/graphql-types/course.ts index 4fa678a..995d411 100644 --- a/backend/src/graphql-types/course.ts +++ b/backend/src/graphql-types/course.ts @@ -1,6 +1,7 @@ import { ListResponse } from './list'; import { Course } from '../models'; -import { field, Int, objectType } from '@loopback/graphql'; +import { field, ID, inputType, Int, objectType } from '@loopback/graphql'; +import { DataObject } from '@loopback/repository'; @objectType() export class CourseList implements ListResponse { @@ -9,3 +10,15 @@ export class CourseList implements ListResponse { @field(returns => [Course]) list: Course[]; } + +@inputType() +export class CourseUpdateInput implements Pick, 'id' | 'semester' | 'alias' | 'subjectId'> { + @field(returns => ID) + id: number; + @field() + semester?: string; + @field() + alias?: string; + @field() + subjectId?: number; +} diff --git a/backend/src/graphql-types/fulfillment-mode.ts b/backend/src/graphql-types/fulfillment-mode.ts new file mode 100644 index 0000000..5c433cd --- /dev/null +++ b/backend/src/graphql-types/fulfillment-mode.ts @@ -0,0 +1,28 @@ +import { ListResponse } from './list'; +import { FulfillmentMode } from '../models'; +import { field, inputType, Int, objectType } from '@loopback/graphql'; +import { DataObject } from '@loopback/repository'; + +@objectType() +export class FulfillmentModeList implements ListResponse { + @field(returns => Int) + count: number; + @field(returns => [FulfillmentMode]) + list: FulfillmentMode[]; +} + +@inputType() +export class FulfillmentModeUpdateInput implements Omit, 'requirements' | 'courseId'> { + @field(returns => Int) + id: number; + @field() + name: string; + @field() + threshold2: number; + @field() + threshold3: number; + @field() + threshold4: number; + @field() + threshold5: number; +} diff --git a/backend/src/graphql-types/input/course-update.input.ts b/backend/src/graphql-types/input/course-update.input.ts deleted file mode 100644 index 8daf4ff..0000000 --- a/backend/src/graphql-types/input/course-update.input.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { field, ID, inputType } from '@loopback/graphql'; -import { Course } from '../../models'; -import { DataObject } from '@loopback/repository'; - -@inputType() -export class CourseUpdateInput implements Pick, 'id' | 'semester' | 'alias' | 'subjectId'> { - @field(returns => ID) - id: number; - @field() - semester?: string; - @field() - alias?: string; - @field() - subjectId?: number; -} diff --git a/backend/src/graphql-types/input/subject-update.input.ts b/backend/src/graphql-types/input/subject-update.input.ts deleted file mode 100644 index 2c39aa9..0000000 --- a/backend/src/graphql-types/input/subject-update.input.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { field, ID, inputType } from '@loopback/graphql'; -import { DataObject } from '@loopback/repository'; -import { Subject } from '../../models'; - -@inputType() -export class SubjectUpdateInput implements Pick, 'id' | 'name' | 'description'> { - @field(returns => ID) - id: number; - @field() - name?: string; - @field() - description?: string; -} diff --git a/backend/src/graphql-types/input/user-register.input.ts b/backend/src/graphql-types/input/user-register.input.ts deleted file mode 100644 index f8ba22c..0000000 --- a/backend/src/graphql-types/input/user-register.input.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { field, inputType } from '@loopback/graphql'; -import { User } from '../../models'; -import { model, property } from '@loopback/repository'; -import { UserProperties } from '../user'; - -@model() -@inputType() -export class UserRegisterInput implements Pick { - @property(UserProperties.email) - @field() - email: string; - @property(UserProperties.name) - @field() - name: string; - @property(UserProperties.password) - @field() - password: string; -} diff --git a/backend/src/graphql-types/input/user-update.input.ts b/backend/src/graphql-types/input/user-update.input.ts deleted file mode 100644 index bcd246c..0000000 --- a/backend/src/graphql-types/input/user-update.input.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { User } from '../../models'; -import { field, ID, inputType } from '@loopback/graphql'; -import { UserProperties } from '../user'; -import { property } from '@loopback/repository'; - -@inputType() -export class UserUpdateInput implements Partial> { - @field(returns => ID) - id: number; - @field({nullable: true}) - @property(UserProperties.email) - email?: string; - @field({nullable: true}) - @property(UserProperties.name) - name?: string; - @field({nullable: true}) - @property(UserProperties.password) - password?: string; - @field({nullable: true}) - isAdmin?: boolean; -} diff --git a/backend/src/graphql-types/requirement.ts b/backend/src/graphql-types/requirement.ts new file mode 100644 index 0000000..736505a --- /dev/null +++ b/backend/src/graphql-types/requirement.ts @@ -0,0 +1,28 @@ +import { ListResponse } from './list'; +import { Requirement } from '../models'; +import { field, inputType, Int, objectType } from '@loopback/graphql'; +import { DataObject } from '@loopback/repository'; + +@objectType() +export class RequirementList implements ListResponse { + @field(returns => Int) + count: number; + @field(returns => [Requirement]) + list: Requirement[]; +} + +@inputType() +export class RequirementUpdateInput implements Pick, 'id' | 'name' | 'description' | 'deadline' | 'minPoints' | 'maxPoints'> { + @field(returns => Int) + id: number; + @field() + deadline: Date; + @field() + name: string; + @field() + description: string; + @field() + minPoints: number; + @field() + maxPoints: number; +} diff --git a/backend/src/graphql-types/subject.ts b/backend/src/graphql-types/subject.ts index 0e0b0df..6f350e8 100644 --- a/backend/src/graphql-types/subject.ts +++ b/backend/src/graphql-types/subject.ts @@ -1,6 +1,7 @@ import { ListResponse } from './list'; import { Subject } from '../models'; -import { field, Int, objectType } from '@loopback/graphql'; +import { field, ID, inputType, Int, objectType } from '@loopback/graphql'; +import { DataObject } from '@loopback/repository'; @objectType() export class SubjectList implements ListResponse { @@ -9,3 +10,13 @@ export class SubjectList implements ListResponse { @field(returns => [Subject]) list: Subject[]; } + +@inputType() +export class SubjectUpdateInput implements Pick, 'id' | 'name' | 'description'> { + @field(returns => ID) + id: number; + @field() + name?: string; + @field() + description?: string; +} diff --git a/backend/src/graphql-types/user.ts b/backend/src/graphql-types/user.ts index b208c58..be76061 100644 --- a/backend/src/graphql-types/user.ts +++ b/backend/src/graphql-types/user.ts @@ -1,6 +1,7 @@ import { User } from '../models'; -import { field, Int, objectType } from '@loopback/graphql'; +import { field, ID, inputType, Int, objectType } from '@loopback/graphql'; import { ListResponse } from './list'; +import { model, property } from '@loopback/repository'; @objectType() export class UserResult implements Pick { @@ -60,3 +61,34 @@ export const UserProperties = { hidden: true } }; + +@model() +@inputType() +export class UserRegisterInput implements Pick { + @property(UserProperties.email) + @field() + email: string; + @property(UserProperties.name) + @field() + name: string; + @property(UserProperties.password) + @field() + password: string; +} + +@inputType() +export class UserUpdateInput implements Partial> { + @field(returns => ID) + id: number; + @field({nullable: true}) + @property(UserProperties.email) + email?: string; + @field({nullable: true}) + @property(UserProperties.name) + name?: string; + @field({nullable: true}) + @property(UserProperties.password) + password?: string; + @field({nullable: true}) + isAdmin?: boolean; +} diff --git a/backend/src/models/course.model.ts b/backend/src/models/course.model.ts index 4fd8bd0..0b25739 100644 --- a/backend/src/models/course.model.ts +++ b/backend/src/models/course.model.ts @@ -2,8 +2,8 @@ import { belongsTo, Entity, hasMany, model, property } from '@loopback/repositor import { Subject } from './subject.model'; import { User } from './user.model'; import { CourseUser } from './course-user.model'; -import { Requirement } from './requirement.model'; import { field, objectType } from '@loopback/graphql'; +import { FulfillmentMode } from './fulfillment-mode.model'; @model() @objectType() @@ -37,8 +37,8 @@ export class Course extends Entity { @hasMany(() => User, {through: {model: () => CourseUser}}) users: User[]; - @hasMany(() => Requirement) - requirements: Requirement[]; + @hasMany(() => FulfillmentMode) + fulfillmentModes: FulfillmentMode[]; constructor(data?: Partial) { super(data); diff --git a/backend/src/models/fulfillment-mode.model.ts b/backend/src/models/fulfillment-mode.model.ts new file mode 100644 index 0000000..c340509 --- /dev/null +++ b/backend/src/models/fulfillment-mode.model.ts @@ -0,0 +1,68 @@ +import { Entity, hasMany, model, property } from '@loopback/repository'; +import { Requirement } from './requirement.model'; +import { field, ID, objectType } from '@loopback/graphql'; + +@model() +@objectType() +export class FulfillmentMode extends Entity { + @property({ + type: 'number', + id: true, + generated: true, + }) + @field(returns => ID) + id?: number; + + @property({ + type: 'string', + required: true, + }) + @field() + name: string; + + @property({ + type: 'number', + required: true, + }) + @field() + threshold2: number; + + @property({ + type: 'number', + required: true, + }) + @field() + threshold3: number; + + @property({ + type: 'number', + required: true, + }) + @field() + threshold4: number; + + @property({ + type: 'number', + required: true, + }) + @field() + threshold5: number; + + @property({ + type: 'number', + }) + courseId?: number; + + @hasMany(() => Requirement) + requirements: Requirement[]; + + constructor(data?: Partial) { + super(data); + } +} + +export interface FulfillmentModeRelations { + // describe navigational properties here +} + +export type FulfillmentModeWithRelations = FulfillmentMode & FulfillmentModeRelations; diff --git a/backend/src/models/index.ts b/backend/src/models/index.ts index ff67f40..e542769 100644 --- a/backend/src/models/index.ts +++ b/backend/src/models/index.ts @@ -4,3 +4,5 @@ export * from './requirement.model'; export * from './course.model'; export * from './course-user.model'; export * from './rev-token.model'; +export * from './fulfillment-mode.model'; +export * from './user-fulfill-requirement.model'; diff --git a/backend/src/models/requirement.model.ts b/backend/src/models/requirement.model.ts index 7f7422f..09afa3f 100644 --- a/backend/src/models/requirement.model.ts +++ b/backend/src/models/requirement.model.ts @@ -1,40 +1,60 @@ import { Entity, model, property } from '@loopback/repository'; +import { field, ID, Int, objectType } from '@loopback/graphql'; @model() +@objectType() export class Requirement extends Entity { - @property({ - type: 'number', - id: true, - generated: true, - }) - id?: number; + @property({ + type: 'number', + id: true, + generated: true, + }) + @field(returns => ID) + id?: number; - @property({ - type: 'string', - required: true, - }) - name: string; + @property({ + type: 'string', + required: true, + }) + @field() + name: string; - @property({ - type: 'number', - required: true, - }) - maxPoints: number; + @property({ + type: 'string', + required: true, + }) + @field() + description: string; - @property({ - type: 'string', - required: true, - }) - type: string; + @property({ + type: 'number', + required: true, + }) + @field(returns => Int) + minPoints: number; - @property({ - type: 'number', - }) - courseId?: number; + @property({ + type: 'number', + required: true, + }) + @field(returns => Int) + maxPoints: number; - constructor(data?: Partial) { - super(data); - } + @property({ + type: 'date', + required: true, + }) + @field() + deadline: Date; + + @property({ + type: 'number', + }) + fulfillmentModeId?: number; + + constructor(data?: Partial) { + super(data); + } } export interface RequirementRelations { diff --git a/backend/src/models/user-fulfill-requirement.model.ts b/backend/src/models/user-fulfill-requirement.model.ts new file mode 100644 index 0000000..9edfbc9 --- /dev/null +++ b/backend/src/models/user-fulfill-requirement.model.ts @@ -0,0 +1,43 @@ +import { Entity, model, property } from '@loopback/repository'; + +@model() +export class UserFulfillRequirement extends Entity { + @property({ + type: 'number', + id: true, + generated: true, + }) + id?: number; + + @property({ + type: 'number', + required: true, + }) + points: number; + + @property({ + type: 'date', + required: true, + }) + dateTime: string; + + @property({ + type: 'number', + }) + userId?: number; + + @property({ + type: 'number', + }) + requirementId?: number; + + constructor(data?: Partial) { + super(data); + } +} + +export interface UserFulfillRequirementRelations { + // describe navigational properties here +} + +export type UserFulfillRequirementWithRelations = UserFulfillRequirement & UserFulfillRequirementRelations; diff --git a/backend/src/models/user.model.ts b/backend/src/models/user.model.ts index 6c06d7f..c10a433 100644 --- a/backend/src/models/user.model.ts +++ b/backend/src/models/user.model.ts @@ -3,6 +3,8 @@ import { Course } from './course.model'; import { CourseUser } from './course-user.model'; import { field, objectType } from '@loopback/graphql'; import { UserProperties } from '../graphql-types/user'; +import { Requirement } from './requirement.model'; +import { UserFulfillRequirement } from './user-fulfill-requirement.model'; @model() @objectType() @@ -34,6 +36,9 @@ export class User extends Entity { //@field() courses: Course[]; + @hasMany(() => Requirement, {through: {model: () => UserFulfillRequirement}}) + completedRequirements: Requirement[]; + constructor(data?: Partial) { super(data); } diff --git a/backend/src/repositories/course.repository.ts b/backend/src/repositories/course.repository.ts index a35554c..f8a2537 100644 --- a/backend/src/repositories/course.repository.ts +++ b/backend/src/repositories/course.repository.ts @@ -1,37 +1,36 @@ -import { inject, Getter } from '@loopback/core'; -import { - DefaultCrudRepository, - repository, - BelongsToAccessor, - HasManyThroughRepositoryFactory, - HasManyRepositoryFactory -} from '@loopback/repository'; +import { Getter, inject } from '@loopback/core'; +import { BelongsToAccessor, repository } from '@loopback/repository'; import { DatabaseDataSource } from '../datasources'; -import { Course, CourseRelations, Subject, User, CourseUser, Requirement } from '../models'; +import { Course, CourseRelations, CourseUser, FulfillmentMode, Subject, User } from '../models'; import { SubjectRepository } from './subject.repository'; import { CourseUserRepository } from './course-user.repository'; import { UserRepository } from './user.repository'; -import { RequirementRepository } from './requirement.repository'; +import { FulfillmentModeRepository } from './fulfillment-mode.repository'; +import { CustomCrudRepository, CustomHasManyRepositoryFactory, CustomHasManyThroughRepositoryFactory } from './custom-has-many-repository'; -export class CourseRepository extends DefaultCrudRepository { public readonly subject: BelongsToAccessor; - public readonly users: HasManyThroughRepositoryFactory; - public readonly requirements: HasManyRepositoryFactory; + public readonly fulfillmentModes: CustomHasManyRepositoryFactory; constructor( - @inject('datasources.database') dataSource: DatabaseDataSource, @repository.getter('SubjectRepository') protected subjectRepositoryGetter: Getter, @repository.getter('CourseUserRepository') protected courseUserRepositoryGetter: Getter, @repository.getter('UserRepository') protected userRepositoryGetter: Getter, @repository.getter('RequirementRepository') protected requirementRepositoryGetter: Getter, + @inject('datasources.database') dataSource: DatabaseDataSource, + @repository.getter('SubjectRepository') protected subjectRepositoryGetter: Getter, + @repository.getter('CourseUserRepository') protected courseUserRepositoryGetter: Getter, + @repository.getter('UserRepository') protected userRepositoryGetter: Getter, + @repository.getter('FulfillmentModeRepository') protected fulfillmentModeRepositoryGetter: Getter, ) { super(Course, dataSource); - this.requirements = this.createHasManyRepositoryFactoryFor('requirements', requirementRepositoryGetter,); - this.registerInclusionResolver('requirements', this.requirements.inclusionResolver); - this.users = this.createHasManyThroughRepositoryFactoryFor('users', userRepositoryGetter, courseUserRepositoryGetter,); + this.fulfillmentModes = this.createCustomHasManyRepositoryFactoryFor('fulfillmentModes', fulfillmentModeRepositoryGetter, 'courseId'); + this.registerInclusionResolver('fulfillmentModes', this.fulfillmentModes.inclusionResolver); + this.users = this.createCustomHasManyThroughFactoryFor('users', userRepositoryGetter, courseUserRepositoryGetter, 'courseId', 'userId'); this.registerInclusionResolver('users', this.users.inclusionResolver); this.subject = this.createBelongsToAccessorFor('subject', subjectRepositoryGetter,); this.registerInclusionResolver('subject', this.subject.inclusionResolver); diff --git a/backend/src/repositories/fulfillment-mode.repository.ts b/backend/src/repositories/fulfillment-mode.repository.ts new file mode 100644 index 0000000..0a0dfa3 --- /dev/null +++ b/backend/src/repositories/fulfillment-mode.repository.ts @@ -0,0 +1,21 @@ +import { Getter, inject } from '@loopback/core'; +import { repository } from '@loopback/repository'; +import { DatabaseDataSource } from '../datasources'; +import { FulfillmentMode, FulfillmentModeRelations, Requirement } from '../models'; +import { RequirementRepository } from './requirement.repository'; +import { CustomCrudRepository, CustomHasManyRepositoryFactory } from './custom-has-many-repository'; + +export class FulfillmentModeRepository extends CustomCrudRepository { + + public readonly requirements: CustomHasManyRepositoryFactory; + + constructor( + @inject('datasources.database') dataSource: DatabaseDataSource, @repository.getter('RequirementRepository') protected requirementRepositoryGetter: Getter, + ) { + super(FulfillmentMode, dataSource); + this.requirements = this.createCustomHasManyRepositoryFactoryFor('requirements', requirementRepositoryGetter, 'fulfillmentModeId'); + this.registerInclusionResolver('requirements', this.requirements.inclusionResolver); + } +} diff --git a/backend/src/repositories/index.ts b/backend/src/repositories/index.ts index 459edac..b9c372d 100644 --- a/backend/src/repositories/index.ts +++ b/backend/src/repositories/index.ts @@ -4,3 +4,5 @@ export * from './requirement.repository'; export * from './course.repository'; export * from './course-user.repository'; export * from './rev-token.repository'; +export * from './fulfillment-mode.repository'; +export * from './user-fulfill-requirement.repository'; diff --git a/backend/src/repositories/rev-token.repository.ts b/backend/src/repositories/rev-token.repository.ts index 04f81d2..6de1be7 100644 --- a/backend/src/repositories/rev-token.repository.ts +++ b/backend/src/repositories/rev-token.repository.ts @@ -6,9 +6,9 @@ import { RevToken, RevTokenRelations } from '../models'; export class RevTokenRepository extends DefaultCrudRepository { - constructor( - @inject('datasources.database') dataSource: DatabaseDataSource, - ) { - super(RevToken, dataSource); - } + constructor( + @inject('datasources.database') dataSource: DatabaseDataSource, + ) { + super(RevToken, dataSource); + } } diff --git a/backend/src/repositories/user-fulfill-requirement.repository.ts b/backend/src/repositories/user-fulfill-requirement.repository.ts new file mode 100644 index 0000000..48337ea --- /dev/null +++ b/backend/src/repositories/user-fulfill-requirement.repository.ts @@ -0,0 +1,14 @@ +import { inject } from '@loopback/core'; +import { DatabaseDataSource } from '../datasources'; +import { UserFulfillRequirement, UserFulfillRequirementRelations } from '../models'; +import { CustomCrudRepository } from './custom-has-many-repository'; + +export class UserFulfillRequirementRepository extends CustomCrudRepository { + constructor( + @inject('datasources.database') dataSource: DatabaseDataSource, + ) { + super(UserFulfillRequirement, dataSource); + } +} diff --git a/backend/src/repositories/user.repository.ts b/backend/src/repositories/user.repository.ts index 55527e7..c882be2 100644 --- a/backend/src/repositories/user.repository.ts +++ b/backend/src/repositories/user.repository.ts @@ -1,11 +1,13 @@ import { Getter, inject } from '@loopback/core'; -import { repository } from '@loopback/repository'; +import { HasManyThroughRepositoryFactory, repository } from '@loopback/repository'; import { DatabaseDataSource } from '../datasources'; -import { Course, CourseUser, User, UserRelations } from '../models'; +import { Course, CourseUser, Requirement, User, UserFulfillRequirement, UserRelations } from '../models'; import { SubjectRepository } from './subject.repository'; import { CourseUserRepository } from './course-user.repository'; import { CourseRepository } from './course.repository'; import { CustomCrudRepository, CustomHasManyThroughRepositoryFactory } from './custom-has-many-repository'; +import { UserFulfillRequirementRepository } from './user-fulfill-requirement.repository'; +import { RequirementRepository } from './requirement.repository'; export class UserRepository extends CustomCrudRepository; + public readonly completedRequirements: HasManyThroughRepositoryFactory; + constructor( - @inject('datasources.database') dataSource: DatabaseDataSource, @repository.getter('SubjectRepository') protected subjectRepositoryGetter: Getter, @repository.getter('CourseUserRepository') protected courseUserRepositoryGetter: Getter, @repository.getter('CourseRepository') protected courseRepositoryGetter: Getter, + @inject('datasources.database') dataSource: DatabaseDataSource, @repository.getter('SubjectRepository') protected subjectRepositoryGetter: Getter, @repository.getter('CourseUserRepository') protected courseUserRepositoryGetter: Getter, @repository.getter('CourseRepository') protected courseRepositoryGetter: Getter, @repository.getter('UserFulfillRequirementRepository') protected userFulfillRequirementRepositoryGetter: Getter, @repository.getter('RequirementRepository') protected requirementRepositoryGetter: Getter, ) { super(User, dataSource); + this.completedRequirements = this.createHasManyThroughRepositoryFactoryFor('completedRequirements', requirementRepositoryGetter, userFulfillRequirementRepositoryGetter,); + this.registerInclusionResolver('completedRequirements', this.completedRequirements.inclusionResolver); this.courses = this.createCustomHasManyThroughFactoryFor('courses', courseRepositoryGetter, courseUserRepositoryGetter, 'userId', 'courseId'); } } diff --git a/frontend/src/app/graphql/course.graphql b/frontend/src/app/graphql/course.graphql index 20f30ba..990fd4a 100644 --- a/frontend/src/app/graphql/course.graphql +++ b/frontend/src/app/graphql/course.graphql @@ -31,3 +31,7 @@ query Course($id: ID!) { mutation EditCourse($input: CourseUpdateInput!) { courseUpdate(course: $input) } + +mutation CreateCourse($input: CourseUpdateInput!) { + courseCreate(course: $input) +} diff --git a/frontend/src/app/graphql/requirement.graphql b/frontend/src/app/graphql/requirement.graphql new file mode 100644 index 0000000..999aaea --- /dev/null +++ b/frontend/src/app/graphql/requirement.graphql @@ -0,0 +1,32 @@ +query RequirementList($mode: ID!, $limit: Int!, $offset: Int!) { + requirements(mode: $mode, limit: $limit, offset: $offset) { + count + list { + id + name + description + deadline + minPoints + maxPoints + } + } +} + +query Requirement($id: ID!) { + requirement(id: $id) { + id + name + description + deadline + minPoints + maxPoints + } +} + +mutation EditRequirement($input: RequirementUpdateInput!) { + requirementUpdate(requirement: $input) +} + +mutation CreateRequirement($input: RequirementUpdateInput!) { + requirementCreate(requirement: $input) +} diff --git a/frontend/src/app/graphql/subject.graphql b/frontend/src/app/graphql/subject.graphql index 3af3f0b..f09be05 100644 --- a/frontend/src/app/graphql/subject.graphql +++ b/frontend/src/app/graphql/subject.graphql @@ -20,3 +20,7 @@ query Subject($id: ID!) { mutation EditSubject($input: SubjectUpdateInput!) { subjectUpdate(subject: $input) } + +mutation CreateSubject($input: SubjectUpdateInput!) { + subjectCreate(subject: $input) +} diff --git a/frontend/src/app/requirements/requirement-list/requirement-list.component.css b/frontend/src/app/requirements/requirement-list/requirement-list.component.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/requirements/requirement-list/requirement-list.component.html b/frontend/src/app/requirements/requirement-list/requirement-list.component.html new file mode 100644 index 0000000..fa92899 --- /dev/null +++ b/frontend/src/app/requirements/requirement-list/requirement-list.component.html @@ -0,0 +1 @@ + diff --git a/frontend/src/app/requirements/requirement-list/requirement-list.component.spec.ts b/frontend/src/app/requirements/requirement-list/requirement-list.component.spec.ts new file mode 100644 index 0000000..053d64f --- /dev/null +++ b/frontend/src/app/requirements/requirement-list/requirement-list.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RequirementListComponent } from './requirement-list.component'; + +describe('RequirementsListComponent', () => { + let component: RequirementListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [RequirementListComponent] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(RequirementListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/requirements/requirement-list/requirement-list.component.ts b/frontend/src/app/requirements/requirement-list/requirement-list.component.ts new file mode 100644 index 0000000..58ad881 --- /dev/null +++ b/frontend/src/app/requirements/requirement-list/requirement-list.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit } from '@angular/core'; +import { RequirementListGQL } from '../../services/graphql'; + +@Component({ + selector: 'app-requirement-list', + templateUrl: './requirement-list.component.html', + styleUrls: ['./requirement-list.component.css'] +}) +export class RequirementListComponent implements OnInit { + + constructor(public listGQL: RequirementListGQL) { + } + + ngOnInit(): void { + } + +} diff --git a/frontend/src/app/requirements/requirements.module.ts b/frontend/src/app/requirements/requirements.module.ts new file mode 100644 index 0000000..995cef1 --- /dev/null +++ b/frontend/src/app/requirements/requirements.module.ts @@ -0,0 +1,15 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RequirementListComponent } from './requirement-list/requirement-list.component'; +import { SharedComponentsModule } from '../shared-components/shared-components.module'; + + +@NgModule({ + declarations: [RequirementListComponent], + imports: [ + CommonModule, + SharedComponentsModule + ] +}) +export class RequirementsModule { +}