Skip to content

Commit

Permalink
add progress tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
Szotkowski committed Sep 14, 2023
1 parent c95e92c commit faf3c42
Show file tree
Hide file tree
Showing 8 changed files with 333 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './ping.controller';
export * from './user-instruction.controller';
export * from './user.controller';
export * from './user-link.controller';
export * from './user-progress.controller';
227 changes: 227 additions & 0 deletions src/controllers/user-progress.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import {authenticate} from '@loopback/authentication';
import {inject} from '@loopback/core';
import {
repository
} from '@loopback/repository';
import {
HttpErrors,
del,
get,
param,
patch,
post,
requestBody
} from '@loopback/rest';
import {SecurityBindings, UserProfile} from '@loopback/security';
import {
Progress, ProgressRelations
} from '../models';
import {InstructionRepository, ProgressRepository, UserRepository} from '../repositories';
import {JWTService} from '../services';

export class UserProgressController {
constructor(
@inject('services.jwt.service')
public jwtService: JWTService,
@inject(SecurityBindings.USER, {optional: true})
public user: UserProfile,
@repository(InstructionRepository)
protected instructionRepository: InstructionRepository,
@repository(UserRepository) protected userRepository: UserRepository,
@repository(UserRepository) protected progressRepository: ProgressRepository,
) { }

@authenticate('jwt')
@post('/users/{id}/progresses/{progressId}', {
responses: {
'200': {
description: 'Create Progress of Instruction',
content: {
'application/json': {
schema: {
type: 'boolean',
},
},
},
},
},
})
async create(
@requestBody({
content: {
'application/json': {
schema: {
type: 'object',
properties: {
instructionId: {type: 'number'},
stepId: {type: 'number'},
descriptionId: {type: 'number'},
},
required: [
'instructionId',
'stepId',
'descriptionId',
],
},
},
},
})
progress: Omit<Progress, 'id'>,
): Promise<boolean> {
const user = await this.userRepository.findById(this.user.id);
if (!user) {
throw new HttpErrors.NotFound('User not found');
}
const existingProgress = await this.progressRepository.findOne({
where: {
instructionId: progress.instructionId,
userId: this.user.id,
},
});
if (existingProgress) {
throw new HttpErrors.BadRequest(
'Progress with this instructionId and userId already exists.',
);
}
this.progressRepository.create({
...progress,
userId: this.user.id,
});
return true;
}

@authenticate('jwt')
@patch('/users/{id}/progresses/{instructionId}', {
responses: {
'200': {
description: 'Update Progressof Instruction',
content: {
'application/json': {
schema: {
type: 'boolean',
},
},
},
},
},
})
async patch(
@param.path.number('instructionId') instructionId: number,
@requestBody({
content: {
'application/json': {
schema: {
type: 'object',
properties: {
stepId: {type: 'number'},
descriptionId: {type: 'number'},
},
},
},
},
})
progress: Partial<Progress>,
): Promise<boolean> {
const userOriginal = await this.userRepository.findById(this.user.id);
if (!userOriginal) {
throw new HttpErrors.NotFound('User not found');
}
const progressOriginal = await this.progressRepository.findOne({
where: {
instructionId: instructionId,
userId: this.user.id
},
});
if (!progressOriginal) {
throw new HttpErrors.NotFound('Progress not found');
}
this.validateProgressOwnership(progressOriginal);
await this.instructionRepository.updateById(progressOriginal.id, progress);
return true;
}

@authenticate('jwt')
@del('/users/{id}/progresses/{instructionId}', {
responses: {
'200': {
description: 'User.Progress DELETE success count',
content: {
'application/json': {
schema: {
type: 'boolean',
},
},
},
},
},
})
async delete(
@param.query.number('instructionId') instructionId: number,
): Promise<boolean> {
const userOriginal = await this.userRepository.findById(this.user.id);
if (!userOriginal) {
throw new HttpErrors.NotFound('User not found');
}
const progress = await this.progressRepository.findOne({
where: {
instructionId: instructionId,
userId: this.user.id
},
});
if (!progress) {
throw new HttpErrors.NotFound('Progress not found');
}
this.validateProgressOwnership(progress);
await this.progressRepository.deleteById(progress.id);
return true;
}

@authenticate('jwt')
@get('/users/{id}/progress/{instructionId}', {
responses: {
'200': {
description: 'Progress model instance',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
id: {type: 'number'},
instructionId: {type: 'number'},
stepId: {type: 'number'},
descriptionId: {type: 'number'},
userId: {type: 'number'},
},
},
},
},
},
},
})
async find(
@param.path.number('instructionId') instructionId: number,
): Promise<(Progress & ProgressRelations)> {
const user = await this.userRepository.findById(this.user.id);
if (!user) {
throw new HttpErrors.NotFound('User not found');
}
const progress = await this.progressRepository.findOne({
where: {
instructionId: instructionId,
userId: this.user.id,
},
});
if (!progress) {
throw new HttpErrors.NotFound('Progress not found');
}
return progress;
}

private validateProgressOwnership(progress: Progress): void {
if (Number(progress.userId) !== Number(this.user.id)) {
throw new HttpErrors.Forbidden(
'You are not authorized to this progress',
);
}
}
}
1 change: 1 addition & 0 deletions src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './instruction.model';
export * from './step.model';
export * from './user-link.model';
export * from './user.model';
export * from './progress.model';
76 changes: 76 additions & 0 deletions src/models/progress.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {Entity, model, property} from '@loopback/repository';

@model({
name: 'progress',
})
export class Progress extends Entity {
@property({
id: true,
generated: true,
postgresql: {
columnName: 'id',
dataLength: null,
dataPrecision: 10,
dataScale: 0,
nullable: 'NO',
},
})
id: number;

@property({
type: 'number',
required: true,
postgresql: {
columnName: 'instruction_id',
dataType: 'integer',
dataLength: null,
dataPrecision: null,
dataScale: null,
nullable: 'NO',
},
})
instructionId: number;

@property({
type: 'number',
required: true,
postgresql: {
columnName: 'step_id',
dataType: 'integer',
dataLength: null,
dataPrecision: null,
dataScale: null,
nullable: 'NO',
},
})
stepId: number;

@property({
type: 'number',
required: true,
postgresql: {
columnName: 'description_id',
dataType: 'integer',
dataLength: null,
dataPrecision: null,
dataScale: null,
nullable: 'NO',
},
})
descriptionId: number;

@property({
type: 'number',
})
userId: number;

constructor(data?: Partial<Progress>) {
super(data);
}
}

export interface ProgressRelations {
// describe navigational properties here
}

export type ProgressWithRelations = Progress & ProgressRelations;
4 changes: 4 additions & 0 deletions src/models/user.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {Entity, hasMany, model, property} from '@loopback/repository';
import {UserLink} from '.';
import {Instruction} from './instruction.model';
import {Progress} from './progress.model';

export enum Language {
CZ = 'CZ',
Expand Down Expand Up @@ -245,6 +246,9 @@ export class User extends Entity {
@hasMany(() => Instruction, {keyTo: 'userId'})
instructions: Instruction[];

@hasMany(() => Progress, {keyTo: 'userId'})
progresses: Progress[];

constructor(data?: Partial<User>) {
super(data);
}
Expand Down
1 change: 1 addition & 0 deletions src/repositories/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './instruction.repository';
export * from './step.repository';
export * from './user-link.repository';
export * from './user.repository';
export * from './progress.repository';
16 changes: 16 additions & 0 deletions src/repositories/progress.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {inject} from '@loopback/core';
import {DefaultCrudRepository} from '@loopback/repository';
import {DbDataSource} from '../datasources';
import {Progress, ProgressRelations} from '../models';

export class ProgressRepository extends DefaultCrudRepository<
Progress,
typeof Progress.prototype.id,
ProgressRelations
> {
constructor(
@inject('datasources.db') dataSource: DbDataSource,
) {
super(Progress, dataSource);
}
}
9 changes: 7 additions & 2 deletions src/repositories/user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import {
HasManyRepositoryFactory,
} from '@loopback/repository';
import {DbDataSource} from '../datasources';
import {User, UserRelations, Instruction} from '../models';
import {User, UserRelations, Instruction, Progress} from '../models';
import {InstructionRepository} from './instruction.repository';
import {ProgressRepository} from './progress.repository';

export class UserRepository extends DefaultCrudRepository<
User,
Expand All @@ -18,12 +19,16 @@ export class UserRepository extends DefaultCrudRepository<
typeof User.prototype.id
>;

public readonly progresses: HasManyRepositoryFactory<Progress, typeof User.prototype.id>;

constructor(
@inject('datasources.db') dataSource: DbDataSource,
@repository.getter('InstructionRepository')
protected instructionRepositoryGetter: Getter<InstructionRepository>,
protected instructionRepositoryGetter: Getter<InstructionRepository>, @repository.getter('ProgressRepository') protected progressRepositoryGetter: Getter<ProgressRepository>,
) {
super(User, dataSource);
this.progresses = this.createHasManyRepositoryFactoryFor('progresses', progressRepositoryGetter,);
this.registerInclusionResolver('progresses', this.progresses.inclusionResolver);
this.instructions = this.createHasManyRepositoryFactoryFor(
'instructions',
instructionRepositoryGetter,
Expand Down

0 comments on commit faf3c42

Please sign in to comment.