Skip to content

Commit

Permalink
✨ add profile features
Browse files Browse the repository at this point in the history
  • Loading branch information
KMUlee committed Apr 25, 2024
1 parent 35c4783 commit 1d34918
Show file tree
Hide file tree
Showing 14 changed files with 284 additions and 15 deletions.
1 change: 1 addition & 0 deletions src/likes/dto/likes.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export class LikesDto {}
5 changes: 4 additions & 1 deletion src/likes/entities/likes.entity.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Exclude } from 'class-transformer';
import { User } from 'src/user/entities/user.entity';
import {
Column,
Expand All @@ -17,11 +18,13 @@ export class Likes {
user: User;

@Column()
projectId: number;
profileId: number;

@CreateDateColumn({ type: 'timestamptz' })
@Exclude()
createdAt: Date;

@UpdateDateColumn({ type: 'timestamptz' })
@Exclude()
updatedAt: Date;
}
1 change: 1 addition & 0 deletions src/likes/likes.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ import { Likes } from './entities/likes.entity';
imports: [TypeOrmModule.forFeature([Likes])],
controllers: [LikesController],
providers: [LikesService],
exports: [LikesService],
})
export class LikesModule {}
17 changes: 16 additions & 1 deletion src/likes/likes.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Likes } from './entities/likes.entity';
import { Repository } from 'typeorm';

@Injectable()
export class LikesService {}
export class LikesService {
constructor(
@InjectRepository(Likes)
private readonly likesRepository: Repository<Likes>,
) {}
async getLikedUserIdList(profileId: number): Promise<number[] | []> {
const likesList = await this.likesRepository.find({
where: { profileId },
relations: ['user'],
});
return likesList.map((like) => like.user.id);
}
}
28 changes: 28 additions & 0 deletions src/profile/dto/profile.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Type } from 'class-transformer';
import { IsNotEmpty, IsString } from 'class-validator';

export class ProfileDto {
@IsNotEmpty()
@IsString()
readonly name: string;

@IsNotEmpty()
@IsString()
readonly title: string;

@IsNotEmpty()
@IsString()
readonly description: string;

@IsNotEmpty()
@IsString()
readonly githubLink: string;
}

export class GetProfileDto extends ProfileDto {
@Type(() => ProfileDto)
likedByUsers: ProfileDto[] | [];

@Type(() => ProfileDto)
likedProjects: ProfileDto[] | [];
}
32 changes: 30 additions & 2 deletions src/profile/entities/profile.entity.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,41 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { Exclude } from 'class-transformer';
import { User } from 'src/user/entities/user.entity';
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
OneToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';

@Entity()
class ProfileEntity {
export class Profile {
@PrimaryGeneratedColumn()
id: number;

@OneToOne(() => User, (user) => user.profile)
@JoinColumn({ name: 'userId' })
user: User;

@Column()
name: string;

@Column()
title: string;

@Column()
description: string;

@Column()
githubLink: string;

@CreateDateColumn({ type: 'timestamptz' })
@Exclude()
createdAt: Date;

@UpdateDateColumn({ type: 'timestamptz' })
@Exclude()
updatedAt: Date;
}
59 changes: 57 additions & 2 deletions src/profile/profile.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,59 @@
import { Controller } from '@nestjs/common';
import {
Body,
Controller,
ForbiddenException,
Get,
Param,
Post,
Put,
UseGuards,
} from '@nestjs/common';
import { ProfileService } from './profile.service';
import { JwtAuthGuard } from 'src/user/user.guard';
import { GetUser } from 'src/user/decorators/GetUser.decorator';
import { Payload } from 'src/user/dto/jwt-payload.dto';
import { ApiBearerAuth } from '@nestjs/swagger';
import { GetProfileDto, ProfileDto } from './dto/profile.dto';

@Controller('profile')
export class ProfileController {}
@UseGuards(JwtAuthGuard)
@ApiBearerAuth('access-token')
export class ProfileController {
constructor(private readonly profileService: ProfileService) {}

@Get('/:id')
async getProfile(
@Param('id') id: number,
@GetUser() user: Payload,
): Promise<{ profile: GetProfileDto | null }> {
if (id !== user.userId) {
throw new ForbiddenException(
'You do not have permission to access this profile',
);
}
return { profile: await this.profileService.getProfile(id) };
}

@Post('/:id')
async createProfile(
@Param('id') id: number,
@GetUser() user: Payload,
@Body() newProfile: ProfileDto,
): Promise<ProfileDto> {
if (id !== user.userId) {
throw new ForbiddenException(
'You do not have permission to create a profile for this user',
);
}
return await this.profileService.createProfile(id, newProfile);
}

@Put('/:id')
async updateProfile(
@Param('id') id: number,
@GetUser() user: Payload,
@Body() profile: ProfileDto,
) {
return await this.profileService.updateProfile(id, profile);
}
}
10 changes: 9 additions & 1 deletion src/profile/profile.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { Module } from '@nestjs/common';
import { ProfileController } from './profile.controller';
import { ProfileService } from './profile.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Profile } from './entities/profile.entity';
import { JwtStrategy } from 'src/user/strategies/jwt.strategy';
import { UserModule } from 'src/user/user.module';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { LikesModule } from 'src/likes/likes.module';

@Module({
imports: [TypeOrmModule.forFeature([Profile]), UserModule, LikesModule],
controllers: [ProfileController],
providers: [ProfileService],
providers: [ProfileService, JwtService, ConfigService, JwtStrategy],
})
export class ProfileModule {}
99 changes: 97 additions & 2 deletions src/profile/profile.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,99 @@
import { Injectable } from '@nestjs/common';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Profile } from './entities/profile.entity';
import { Repository } from 'typeorm';
import { GetProfileDto, ProfileDto } from './dto/profile.dto';
import { UserService } from 'src/user/user.service';
import { LikesService } from 'src/likes/likes.service';
import { User } from 'src/user/entities/user.entity';

@Injectable()
export class ProfileService {}
export class ProfileService {
constructor(
@InjectRepository(Profile)
private readonly profileRepository: Repository<Profile>,
private readonly userService: UserService,
private readonly likesService: LikesService,
) {}

async getProfile(userId: number): Promise<GetProfileDto | null> {
const profile = await this.profileRepository.findOne({
where: {
user: {
id: userId,
},
},
relations: ['user'],
});

if (!profile) {
return null;
}

const likedProjects: ProfileDto[] = [];
const likedList = await this.userService.getLikedList(userId);
likedList.forEach(async (id) => {
const likedProfile = await this.profileRepository.findOneBy({ id });
likedProjects.push(likedProfile);
});
const likedByUsers: ProfileDto[] = [];
const likedUserIdList = await this.likesService.getLikedUserIdList(
profile.id,
);
likedUserIdList.forEach(async (id) => {
const likedProfile = await this.profileRepository.findOne({
where: {
user: {
id,
},
},
relations: ['user'],
});
likedByUsers.push(likedProfile);
});
const profileData: GetProfileDto = {
...profile,
likedProjects,
likedByUsers,
};
return profileData;
}

async createProfile(
userId: number,
profileData: ProfileDto,
): Promise<ProfileDto> {
const user: User = await this.userService.findOneById(userId);
try {
const profile = this.profileRepository.create({
...profileData,
user,
});
return await this.profileRepository.save(profile);
} catch (error) {
throw new HttpException(
'Failed to create profile. Please try again later.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}

async updateProfile(userId: number, profileData: ProfileDto) {
const profile = await this.profileRepository.findOne({
where: {
user: {
id: userId,
},
},
});
const updatedProfile: Profile = { ...profile, ...profileData };
try {
return await this.profileRepository.save(updatedProfile);
} catch (error) {
throw new HttpException(
'Failed to update profile. Please try again later.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}
9 changes: 9 additions & 0 deletions src/user/decorators/GetUser.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ExecutionContext, createParamDecorator } from '@nestjs/common';
import { Payload } from '../dto/jwt-payload.dto';

export const GetUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext): Payload => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
6 changes: 6 additions & 0 deletions src/user/entities/user.entity.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { Exclude } from 'class-transformer';
import { Likes } from 'src/likes/entities/likes.entity';
import { Profile } from 'src/profile/entities/profile.entity';
import {
Column,
CreateDateColumn,
Entity,
OneToMany,
OneToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';

@Entity()
export class User {
@PrimaryGeneratedColumn()
@Exclude()
id: number;

@Column()
Expand All @@ -23,6 +26,9 @@ export class User {
@OneToMany(() => Likes, (likes) => likes.user)
likesList: Likes[];

@OneToOne(() => Profile, (profile) => profile.user)
profile: Profile;

@CreateDateColumn({ type: 'timestamptz' })
@Exclude()
createdAt: Date;
Expand Down
2 changes: 2 additions & 0 deletions src/user/user.guard.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
8 changes: 6 additions & 2 deletions src/user/user.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { TypeOrmModule } from '@nestjs/typeorm';

import { JwtModule, JwtService } from '@nestjs/jwt';
import { JwtStrategy } from './strategies/jwt.strategy';
import { PassportModule } from '@nestjs/passport';
import { User } from './entities/user.entity';
import { JwtModule } from '@nestjs/jwt';

@Module({
imports: [
Expand All @@ -12,9 +15,10 @@ import { JwtModule } from '@nestjs/jwt';
secret: process.env.JWT_SECRET_KEY,
signOptions: {},
}),
PassportModule,
],
controllers: [UserController],
providers: [UserService],
providers: [UserService, JwtStrategy],
exports: [UserService],
})
export class UserModule {}
Loading

0 comments on commit 1d34918

Please sign in to comment.