Skip to content

Commit

Permalink
fix: resource user routes (#726)
Browse files Browse the repository at this point in the history
  • Loading branch information
annarhughes authored Dec 9, 2024
1 parent 9740ca3 commit 0780512
Show file tree
Hide file tree
Showing 15 changed files with 190 additions and 68 deletions.
3 changes: 2 additions & 1 deletion src/entities/resource-feedback.entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { Column, Entity, JoinTable, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { FEEDBACK_TAGS_ENUM } from '../utils/constants';
import { BaseBloomEntity } from './base.entity';
import { ResourceEntity } from './resource.entity';
Expand All @@ -14,6 +14,7 @@ export class ResourceFeedbackEntity extends BaseBloomEntity {
@ManyToOne(() => ResourceEntity, (resourceEntity) => resourceEntity.resourceFeedback, {
onDelete: 'CASCADE',
})
@JoinTable({ name: 'resource', joinColumn: { name: 'resourceId' } })
resource: ResourceEntity;

@Column()
Expand Down
4 changes: 3 additions & 1 deletion src/entities/resource-user.entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { Column, Entity, JoinTable, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { BaseBloomEntity } from './base.entity';
import { ResourceEntity } from './resource.entity';
import { UserEntity } from './user.entity';
Expand All @@ -16,8 +16,10 @@ export class ResourceUserEntity extends BaseBloomEntity {
@ManyToOne(() => ResourceEntity, (resourceEntity) => resourceEntity.resourceUser, {
onDelete: 'CASCADE',
})
@JoinTable({ name: 'resource', joinColumn: { name: 'resourceId' } })
resource: ResourceEntity;

@ManyToOne(() => UserEntity, (userEntity) => userEntity.resourceUser, { onDelete: 'CASCADE' })
@JoinTable({ name: 'user', joinColumn: { name: 'userId' } })
user: UserEntity;
}
4 changes: 3 additions & 1 deletion src/entities/resource.entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
import { Column, Entity, JoinTable, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
import { RESOURCE_CATEGORIES, STORYBLOK_STORY_STATUS_ENUM } from '../utils/constants';
import { BaseBloomEntity } from './base.entity';
import { ResourceFeedbackEntity } from './resource-feedback.entity';
Expand Down Expand Up @@ -40,6 +40,7 @@ export class ResourceEntity extends BaseBloomEntity {
@OneToMany(() => ResourceUserEntity, (resourceUserEntity) => resourceUserEntity.resource, {
cascade: true,
})
@JoinTable({ name: 'resourceUser', joinColumn: { name: 'resourceUserId' } })
resourceUser: ResourceUserEntity[];

@OneToMany(
Expand All @@ -49,5 +50,6 @@ export class ResourceEntity extends BaseBloomEntity {
cascade: true,
},
)
@JoinTable({ name: 'resourceFeedback', joinColumn: { name: 'resourceFeedbackId' } })
resourceFeedback: ResourceFeedbackEntity[];
}
15 changes: 0 additions & 15 deletions src/resource-user/dtos/create-resource-user.dto.ts

This file was deleted.

18 changes: 18 additions & 0 deletions src/resource-user/dtos/resource-user.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsNotEmpty, IsString } from 'class-validator';

export class ResourceUserDto {
@IsNotEmpty()
@IsString()
@ApiProperty({ type: String })
resourceId: string;

@IsNotEmpty()
@IsString()
@ApiProperty({ type: String })
userId: string;

@IsBoolean()
@ApiProperty({ type: Date })
completedAt?: Date;
}
10 changes: 6 additions & 4 deletions src/resource-user/dtos/update-resource-user.dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { IsDate, IsOptional } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { IsDefined, IsNotEmpty } from 'class-validator';

export class UpdateResourceUserDto {
@IsOptional()
@IsDate()
completedAt?: Date;
@IsNotEmpty()
@IsDefined()
@ApiProperty({ type: Number })
storyblokId: number;
}
70 changes: 42 additions & 28 deletions src/resource-user/resource-user.controller.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,61 @@
import {
Body,
Controller,
HttpException,
Param,
Patch,
Post,
Req,
UseGuards,
} from '@nestjs/common';
import { ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
import { FirebaseAuthGuard } from 'src/firebase/firebase-auth.guard';
import { CreateResourceUserDto } from './dtos/create-resource-user.dto';
import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { Request } from 'express';
import { ControllerDecorator } from 'src/utils/controller.decorator';
import { UserEntity } from '../entities/user.entity';
import { FirebaseAuthGuard } from '../firebase/firebase-auth.guard';
import { UpdateResourceUserDto } from './dtos/update-resource-user.dto';
import { ResourceUserService } from './resource-user.service';

@Controller('v1/resource-user')
@ApiTags('Resource User')
@ControllerDecorator()
@Controller('/v1/resource-user')
export class ResourceUserController {
constructor(private readonly resourceUserService: ResourceUserService) {}

@Post()
@ApiBearerAuth('access-token')
@ApiOperation({
description: 'Updates resource_user table',
description:
'Stores relationship between a `User` and `Resource` records, once a user has started a resource.',
})
@UseGuards(FirebaseAuthGuard)
create(@Req() req: Request, @Body() createResourceUserDto: CreateResourceUserDto) {
if (req['userEntity'].id !== createResourceUserDto.userId) {
throw new HttpException('Unauthorized', 401);
}
return this.resourceUserService.create(createResourceUserDto);
async createResourceUser(
@Req() req: Request,
@Body() createResourceUserDto: UpdateResourceUserDto,
) {
return await this.resourceUserService.createResourceUser(
req['userEntity'] as UserEntity,
createResourceUserDto,
);
}

@Patch(':id')
@Post('/complete')
@ApiOperation({
description: 'Updates a users resources progress to completed',
})
@ApiBearerAuth('access-token')
@UseGuards(FirebaseAuthGuard)
async complete(@Req() req: Request, @Body() updateResourceUserDto: UpdateResourceUserDto) {
return await this.resourceUserService.setResourceUserCompleted(
req['userEntity'] as UserEntity,
updateResourceUserDto,
true,
);
}

@Post('/incomplete')
@ApiOperation({
description: 'Updates resource_user table',
description:
'Updates a users resources progress to incomplete, undoing a previous complete action',
})
@ApiBearerAuth('access-token')
@UseGuards(FirebaseAuthGuard)
update(
@Req() req: Request,
@Param('id') id: string,
@Body() updateResourceUserDto: UpdateResourceUserDto,
) {
return this.resourceUserService.update(id, updateResourceUserDto);
async incomplete(@Req() req: Request, @Body() updateResourceUserDto: UpdateResourceUserDto) {
return await this.resourceUserService.setResourceUserCompleted(
req['userEntity'] as UserEntity,
updateResourceUserDto,
false,
);
}
}
2 changes: 2 additions & 0 deletions src/resource-user/resource-user.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { UserEntity } from 'src/entities/user.entity';
import { EventLoggerService } from 'src/event-logger/event-logger.service';
import { PartnerAccessService } from 'src/partner-access/partner-access.service';
import { PartnerService } from 'src/partner/partner.service';
import { ResourceService } from 'src/resource/resource.service';
import { ServiceUserProfilesService } from 'src/service-user-profiles/service-user-profiles.service';
import { SubscriptionUserService } from 'src/subscription-user/subscription-user.service';
import { SubscriptionService } from 'src/subscription/subscription.service';
Expand Down Expand Up @@ -46,6 +47,7 @@ import { ResourceUserService } from './resource-user.service';
],
controllers: [ResourceUserController],
providers: [
ResourceService,
ResourceUserService,
UserService,
PartnerAccessService,
Expand Down
83 changes: 75 additions & 8 deletions src/resource-user/resource-user.service.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,97 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { ResourceUserEntity } from 'src/entities/resource-user.entity';
import { UserEntity } from 'src/entities/user.entity';
import { ResourceService } from 'src/resource/resource.service';
import { formatResourceUserObject } from 'src/utils/serialize';
import { Repository } from 'typeorm';
import { CreateResourceUserDto } from './dtos/create-resource-user.dto';
import { ResourceUserDto } from './dtos/resource-user.dto';
import { UpdateResourceUserDto } from './dtos/update-resource-user.dto';

@Injectable()
export class ResourceUserService {
constructor(
@InjectRepository(ResourceUserEntity)
private resourceUserRepository: Repository<ResourceUserEntity>,
private resourceService: ResourceService,
) {}

create(createResourceUserDto: CreateResourceUserDto) {
return this.resourceUserRepository.save(createResourceUserDto);
private async getResourceUser({
resourceId,
userId,
}: ResourceUserDto): Promise<ResourceUserEntity> {
return await this.resourceUserRepository
.createQueryBuilder('resource_user')
.leftJoinAndSelect('resource_user.resource', 'resource')
.where('resource_user.userId = :userId', { userId })
.andWhere('resource_user.resourceId = :resourceId', { resourceId })
.getOne();
}

update(id: string, updateResourceUserDto: UpdateResourceUserDto) {
const resourceUser = this.resourceUserRepository.findOne({ where: { id } });
async createResourceUserRecord({
resourceId,
userId,
}: ResourceUserDto): Promise<ResourceUserEntity> {
return await this.resourceUserRepository.save({
resourceId,
userId,
completedAt: null,
});
}

public async createResourceUser(user: UserEntity, { storyblokId }: UpdateResourceUserDto) {
const resource = await this.resourceService.getResourceByStoryblokId(storyblokId);

if (!resource) {
throw new HttpException('RESOURCE NOT FOUND', HttpStatus.NOT_FOUND);
}

let resourceUser = await this.getResourceUser({
resourceId: resource.id,
userId: user.id,
});

if (!resourceUser) {
throw new HttpException('RESOURCE USER NOT FOUND', HttpStatus.NOT_FOUND);
resourceUser = await this.createResourceUserRecord({
resourceId: resource.id,
userId: user.id,
});
}

return formatResourceUserObject([{ ...resourceUser, resource }])[0];
}

public async setResourceUserCompleted(
user: UserEntity,
{ storyblokId }: UpdateResourceUserDto,
completed: boolean,
) {
const resource = await this.resourceService.getResourceByStoryblokId(storyblokId);

if (!resource) {
throw new HttpException(
`Resource not found for storyblok id: ${storyblokId}`,
HttpStatus.NOT_FOUND,
);
}

const updatedResourceUser = { ...resourceUser, ...updateResourceUserDto };
let resourceUser = await this.getResourceUser({
resourceId: resource.id,
userId: user.id,
});

if (resourceUser) {
await this.resourceUserRepository.save({
...resourceUser,
completedAt: completed ? new Date() : null,
});
} else {
resourceUser = await this.createResourceUserRecord({
resourceId: resource.id,
userId: user.id,
});
}

return this.resourceUserRepository.save(updatedResourceUser);
return formatResourceUserObject([{ ...resourceUser, resource }])[0];
}
}
14 changes: 14 additions & 0 deletions src/resource/resource.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { RESOURCE_CATEGORIES, STORYBLOK_STORY_STATUS_ENUM } from 'src/utils/constants';

export interface IResource {
id?: string;
createdAt?: Date | string;
updatedAt?: Date | string;
name?: string;
slug?: string;
status?: STORYBLOK_STORY_STATUS_ENUM;
storyblokId?: number;
storyblokUuid?: string;
category?: RESOURCE_CATEGORIES;
completedAt?: Date | string;
}
3 changes: 3 additions & 0 deletions src/resource/resource.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ export class ResourceService {
async create(createResourceDto: CreateResourceDto): Promise<ResourceEntity> {
return this.resourceRepository.save(createResourceDto);
}
async getResourceByStoryblokId(storyblokId: number): Promise<ResourceEntity> {
return await this.resourceRepository.findOneBy({ storyblokId: storyblokId });
}
}
10 changes: 0 additions & 10 deletions src/session-user/dtos/create-session-user.dto.ts

This file was deleted.

2 changes: 2 additions & 0 deletions src/user/dtos/get-user.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ICoursesWithSessions } from 'src/course/course.interface';
import { IResource } from 'src/resource/resource.interface';
import { ITherapySession } from 'src/webhooks/webhooks.interface';
import { IPartnerAccessWithPartner } from '../../partner-access/partner-access.interface';
import { IPartnerAdminWithPartner } from '../../partner-admin/partner-admin.interface';
Expand All @@ -10,6 +11,7 @@ export class GetUserDto {
partnerAccesses?: IPartnerAccessWithPartner[];
partnerAdmin?: IPartnerAdminWithPartner;
courses?: ICoursesWithSessions[];
resources?: IResource[];
therapySessions?: ITherapySession[];
subscriptions?: ISubscriptionUser[];
}
2 changes: 2 additions & 0 deletions src/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ export class UserService {
.leftJoinAndSelect('courseUser.course', 'course')
.leftJoinAndSelect('courseUser.sessionUser', 'sessionUser')
.leftJoinAndSelect('sessionUser.session', 'session')
.leftJoinAndSelect('user.resourceUser', 'resourceUser')
.leftJoinAndSelect('resourceUser.resource', 'resource')
.leftJoinAndSelect('user.subscriptionUser', 'subscriptionUser')
.leftJoinAndSelect('subscriptionUser.subscription', 'subscription')
.where('user.id = :id', { id })
Expand Down
Loading

0 comments on commit 0780512

Please sign in to comment.