Skip to content

Commit

Permalink
add array to instructions
Browse files Browse the repository at this point in the history
  • Loading branch information
Szotkowski committed Sep 11, 2023
1 parent 724b78e commit efdb4a5
Show file tree
Hide file tree
Showing 5 changed files with 285 additions and 11 deletions.
70 changes: 69 additions & 1 deletion src/controllers/instruction-step.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class InstructionStepController {
@repository(InstructionRepository)
public instructionRepository: InstructionRepository,
@repository(StepRepository) public stepRepository: StepRepository,
) { }
) {}

@authenticate('jwt')
@post('/users/{id}/instructions/{instructionId}/steps/{stepId}', {
Expand Down Expand Up @@ -229,6 +229,7 @@ export class InstructionStepController {
},
},
link: {type: 'string'},
instructionId: {type: 'string'},
},
},
},
Expand Down Expand Up @@ -364,6 +365,73 @@ export class InstructionStepController {
return true;
}

@authenticate('jwt')
@get('/premium-instructions/{instructionId}/detail', {
responses: {
'200': {
description: 'Get premium instructions',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
steps: {
type: 'object',
items: {
id: {type: 'number'},
titleCz: {type: 'string'},
titleEn: {type: 'string'},
descriptionCz: {
type: 'array',
items: {
type: 'string',
},
},
descriptionEn: {
type: 'array',
items: {
type: 'string',
},
},
link: {type: 'string'},
instructionId: {type: 'number'},
},
},
},
},
},
},
},
},
})
async getPremiumInstructionDetail(
@param.path.number('instructionId') instructionId: number,
): Promise<Omit<Step, 'deleteHash'>[]> {
const user = await this.userRepository.findById(this.user.id);
if (!user) {
throw new HttpErrors.NotFound('User not found');
}
const instruction =
await this.instructionRepository.findById(instructionId);
if (!instruction) {
throw new HttpErrors.NotFound('Instruction not found');
}
if (!instruction.premiumUserIds.includes(this.user.id)) {
throw new HttpErrors.Forbidden(
'You are not authorized to this instruction',
);
}
const data = await this.stepRepository.find({
where: {
instructionId: instructionId,
},
fields: {
deleteHash: false,
},
});
return data;
}

private validateInstructionOwnership(instruction: Instruction): void {
if (Number(instruction.userId) !== Number(this.user.id)) {
throw new HttpErrors.Forbidden(
Expand Down
191 changes: 186 additions & 5 deletions src/controllers/user-instruction.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class UserInstructionController {
@repository(InstructionRepository)
protected instructionRepository: InstructionRepository,
@repository(StepRepository) public stepRepository: StepRepository,
) { }
) {}

@authenticate('jwt')
@post('/users/{id}/instructions/{instructionId}', {
Expand Down Expand Up @@ -66,21 +66,39 @@ export class UserInstructionController {
titleEn: {type: 'string'},
difficulty: {enum: Object.values(Difficulty)},
private: {type: 'boolean'},
premium: {type: 'boolean'},
},
required: ['titleCz', 'titleEn', 'difficulty', 'private'],
required: [
'titleCz',
'titleEn',
'difficulty',
'private',
'premium',
],
},
},
},
})
instruction: Omit<
Instruction,
'id' | 'userId' | 'date' | 'link' | 'deleteHash' | 'premium'
'id' | 'userId' | 'date' | 'link' | 'deleteHash'
>,
key?: string,
): Promise<Instruction> {
const user = await this.userRepository.findById(this.user.id);
if (!user) {
throw new HttpErrors.NotFound('User not found');
}
if (instruction.premium) {
if (!key) {
throw new HttpErrors.Unauthorized('Key not providen');
}
const instructionKey = process.env.INSTRUCTION_KEY ?? '';
const keyMatch = await this.hasher.comparePassword(key, instructionKey);
if (!keyMatch) {
throw new HttpErrors.Unauthorized('Invalid password');
}
}
const newInstruction = await this.instructionRepository.create({
...instruction,
userId: this.user.id,
Expand Down Expand Up @@ -227,6 +245,7 @@ export class UserInstructionController {
private: {type: 'boolean'},
premium: {type: 'boolean'},
date: {type: 'string'},
userId: {type: 'string'},
steps: {
type: 'object',
items: {
Expand Down Expand Up @@ -257,7 +276,9 @@ export class UserInstructionController {
},
},
})
async getUsersInstructions(): Promise<Omit<Instruction, 'deleteHash'>[]> {
async getUsersInstructions(): Promise<
Omit<Instruction, 'deleteHash' | 'premiumUserIds'>[]
> {
const user = await this.userRepository.findById(this.user.id);
if (!user) {
throw new HttpErrors.NotFound('User not found');
Expand All @@ -278,6 +299,7 @@ export class UserInstructionController {
],
fields: {
deleteHash: false,
premiumUserIds: false,
},
});
return data;
Expand Down Expand Up @@ -400,6 +422,15 @@ export class UserInstructionController {
key: string;
},
): Promise<boolean> {
const user = await this.userRepository.findById(this.user.id);
if (!user) {
throw new HttpErrors.NotFound('User not found');
}
const instruction =
await this.instructionRepository.findById(instructionId);
if (!instruction) {
throw new HttpErrors.NotFound('Instruction not found');
}
const instructionKey = process.env.INSTRUCTION_KEY ?? '';
const keyMatch = await this.hasher.comparePassword(
request.key,
Expand Down Expand Up @@ -446,6 +477,7 @@ export class UserInstructionController {
private: {type: 'boolean'},
premium: {type: 'boolean'},
date: {type: 'string'},
userId: {type: 'number'},
steps: {
type: 'object',
items: {
Expand All @@ -467,6 +499,8 @@ export class UserInstructionController {
link: {type: 'string'},
},
},
username: {type: 'string'},
userLink: {type: 'string'},
},
},
},
Expand All @@ -479,22 +513,169 @@ export class UserInstructionController {
async getPublicInstructions(
@param.query.number('limit') limit: number = 10,
@param.query.number('offset') offset: number = 0,
): Promise<Omit<Instruction, 'deleteHash'>[]> {
): Promise<
{
username: string;
userLink: string;
constructor: Function;
toString(): string;
toLocaleString(): string;
valueOf(): Object;
hasOwnProperty(v: PropertyKey): boolean;
isPrototypeOf(v: Object): boolean;
propertyIsEnumerable(v: PropertyKey): boolean;
}[]
> {
const data = await this.instructionRepository.find({
where: {
private: false,
premium: false,
},
include: [
{
relation: 'steps',
},
],
fields: {
deleteHash: false,
premiumUserIds: false,
},
limit,
skip: offset,
});
const userIds = Array.from(
new Set(data.map(instruction => instruction.userId)),
);
const userPromises = userIds.map(async userId => {
const users = await this.userRepository.find({
where: {
id: userId,
},
});
return users.map(user => ({
userId: user.id,
username: user.username,
link: user.link,
}));
});
const usernamesAndLinks = (await Promise.all(userPromises)).flat();
const instructionsWithUserDetails = data.map(instruction => {
const userDetails = usernamesAndLinks.find(
userDetail => userDetail.userId === instruction.userId,
);
return {
...instruction.toJSON(),
username: userDetails?.username ?? '',
userLink: userDetails?.link ?? '',
};
});
return instructionsWithUserDetails;
}

@get('/premium-instructions', {
responses: {
'200': {
description: 'Get premium instructions',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
instructions: {
type: 'object',
items: {
id: {type: 'number'},
titleCz: {type: 'string'},
titleEn: {type: 'string'},
difficulty: {enum: Object.values(Difficulty)},
link: {type: 'string'},
private: {type: 'boolean'},
premium: {type: 'boolean'},
date: {type: 'string'},
userId: {type: 'number'},
},
},
},
},
},
},
},
},
})
async getPremiumInstructions(
@param.query.number('limit') limit: number = 10,
@param.query.number('offset') offset: number = 0,
): Promise<Omit<Instruction, 'deleteHash' | 'premiumUserIds'>[]> {
const data = await this.instructionRepository.find({
where: {
private: false,
premium: true,
},
fields: {
deleteHash: false,
premiumUserIds: false,
},
limit,
skip: offset,
});
return data;
}

@patch('/authorizate-for-premium-instruction/{instructionId}/{userId}', {
responses: {
'200': {
description: 'Authorize user for premium instructions',
content: {
'application/json': {
schema: {
type: 'boolean',
},
},
},
},
},
})
async authorizeUserToPremiumInstruction(
@requestBody({
content: {
'application/json': {
schema: {
type: 'object',
properties: {
key: {type: 'string'},
},
required: ['key'],
},
},
},
})
request: {
key: string;
},
@param.query.number('instructionId') instructionId: number,
@param.query.number('userId') userId: number,
): Promise<boolean> {
const instructionKey = process.env.INSTRUCTION_KEY_PERMISSIONS ?? '';
const keyMatch = await this.hasher.comparePassword(
request.key,
instructionKey,
);
if (!keyMatch) {
throw new HttpErrors.Unauthorized('Invalid password');
}
const instruction =
await this.instructionRepository.findById(instructionId);
if (instruction.premiumUserIds.includes(userId)) {
instruction.premiumUserIds = instruction.premiumUserIds.filter(
id => id !== userId,
);
} else {
instruction.premiumUserIds.push(userId);
}
await this.instructionRepository.updateById(instructionId, instruction);
return true;
}

private validateInstructionOwnership(instruction: Instruction): void {
if (Number(instruction.userId) !== Number(this.user.id)) {
throw new HttpErrors.Forbidden(
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/user-link.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class UserLinkController {
@repository(UserRepository) public userRepository: UserRepository,
@repository(UserLinkRepository)
public userLinkRepository: UserLinkRepository,
) { }
) {}

@authenticate('jwt')
@post('/users/{id}/follow/{followeeId}')
Expand Down
Loading

0 comments on commit efdb4a5

Please sign in to comment.