Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat : 초대링크 생성 API 구현, 지도 생성/수정/조회 API 추가 설정 #33

Merged
merged 3 commits into from
Jul 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"start:dev": "export NODE_ENV=development&& nest start --watch",
"start:stage": "export NODE_ENV=stage&& nest start --watch",
"start:prod": "export NODE_ENV=production&& node dist/main",
"start:debug": "nest start --debug --watch",
"start:debug": "export NODE_ENV=development&& nest start --debug --watch",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
Expand Down
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ConfigModule } from '@nestjs/config';
import { MikroOrmModule } from '@mikro-orm/nestjs';

import { LoggerMiddleware } from 'src/core/intercepters/logging.interceptor';
import { InviteLinkModule } from 'src/invite-link/invite-link.module';

import { AppController } from './app.controller';
import { AppService } from './app.service';
Expand Down Expand Up @@ -36,6 +37,7 @@ import { UtilModule } from './util/util.module';
SearchModule,
UtilModule,
PlaceModule,
InviteLinkModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
3 changes: 1 addition & 2 deletions src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Controller, Get, Redirect, Res, UseGuards } from '@nestjs/common';
import { Controller, Get, Res, UseGuards } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ApiTags } from '@nestjs/swagger';

import { Response } from 'express';

import { KakaoInfo } from 'src/common/decorators/kakao-info.decorator';
import { KakaoGuard } from 'src/common/guards/kakao.guard';
import { NODE_ENVIRONMENT } from 'src/common/helper/env.validation';
import { UserProvider } from 'src/entities';

import { AuthService } from './auth.service';
Expand Down
1 change: 1 addition & 0 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const IS_DEV = process.env.NODE_ENV === 'development';
export const IS_STAGE = process.env.NODE_ENV === 'stage';
export const IS_PROD = process.env.NODE_ENV === 'production';
export const INVITE_LINK_EXPIRATION_DAYS = 7;
20 changes: 20 additions & 0 deletions src/common/decorators/map-role-guard.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { SetMetadata, UseGuards, applyDecorators } from '@nestjs/common';

import { UserMapRole, UserMapRoleValueType } from 'src/entities';

import { MapRoleGuard } from '../guards/map-role.guard';

export const UseMapRoleGuard = (
roles?: UserMapRoleValueType[] | UserMapRoleValueType,
) =>
applyDecorators(
SetMetadata(
'map-roles',
roles
? Array.isArray(roles)
? roles
: [roles]
: [UserMapRole.ADMIN, UserMapRole.READ, UserMapRole.WRITE],
),
UseGuards(MapRoleGuard),
);
41 changes: 41 additions & 0 deletions src/common/guards/map-role.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

import { UserMapRole } from 'src/entities';
import { UserMapService } from 'src/user-map/user-map.service';

@Injectable()
export class MapRoleGuard implements CanActivate {
constructor(
private reflector: Reflector,
private readonly userMapService: UserMapService,
sally0226 marked this conversation as resolved.
Show resolved Hide resolved
) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
const mapRoles = this.reflector.get<string[]>(
'map-roles',
context.getHandler(),
);
if (!mapRoles) {
return true;
}

const request = context.switchToHttp().getRequest();
const user = request.user;
const mapId = request.params.id;

const hasMapRole = async () => {
try {
const userMap = await this.userMapService.findOneByUserAndMap(
user.id,
mapId,
);
return mapRoles.some((role) => userMap.role?.includes(role));
} catch (e) {
return false;
}
};

return await hasMapRole();
}
}
5 changes: 5 additions & 0 deletions src/entities/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { GroupMap } from './group-map.entity';
import { InviteLink } from './invite-link.entity';
import { KakaoPlace } from './kakao-place.entity';
import { PlaceForMap } from './place-for-map.entity';
import { Place } from './place.entity';
Expand All @@ -23,11 +24,15 @@ export * from './place-for-map.repository';
export * from './place.entity';
export * from './place.repository';

export * from './invite-link.entity';
export * from './invite-link.repository';

export const entities = [
User,
GroupMap,
UserMap,
KakaoPlace,
Place,
PlaceForMap,
InviteLink,
];
34 changes: 34 additions & 0 deletions src/entities/invite-link.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Entity, Enum, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core';

import {
UserMapRole,
UserMapRoleValueType,
} from 'src/entities/user-map.entity';
import { User } from 'src/entities/user.entity';

import { InviteLinkRepository } from './invite-link.repository';

@Entity({ tableName: 'invite_link', repository: () => InviteLinkRepository })
export class InviteLink {
@PrimaryKey()
token: string;

@ManyToOne(() => User)
createdBy: User;

@Property({ type: 'string' })
map_id: string;

@Property({ type: 'string', default: UserMapRole.WRITE })
@Enum({ items: [UserMapRole.ADMIN, UserMapRole.READ, UserMapRole.WRITE] })
map_role: UserMapRoleValueType;
Comment on lines +22 to +24
Copy link
Collaborator Author

@sally0226 sally0226 Jul 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invite-link 에 map-role 컬럼을 두어 해당 링크로 지도에 가입할 때, user-map.roleinvite-link.map-role을 넣는 것으로 처리 예정입니다~

  • map-role 기본값 = WRITE


@Property({ type: 'timestamp' })
expires_at: Date;

@Property()
createdAt: Date = new Date();

@Property({ onUpdate: () => new Date() })
updatedAt: Date = new Date();
}
5 changes: 5 additions & 0 deletions src/entities/invite-link.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ExtendedEntityRepository } from 'src/common/helper/extended-repository.helper';

import { InviteLink } from './invite-link.entity';

export class InviteLinkRepository extends ExtendedEntityRepository<InviteLink> {}
15 changes: 15 additions & 0 deletions src/invite-link/invite-link.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Module } from '@nestjs/common';

import { MikroOrmModule } from '@mikro-orm/nestjs';

import { InviteLink } from 'src/entities/index';
import { InviteLinkService } from 'src/invite-link/invite-link.service';
import { UserMapModule } from 'src/user-map/user-map.module';
import { UtilModule } from 'src/util/util.module';

@Module({
imports: [MikroOrmModule.forFeature([InviteLink]), UserMapModule, UtilModule],
providers: [InviteLinkService],
exports: [InviteLinkService],
})
export class InviteLinkModule {}
49 changes: 49 additions & 0 deletions src/invite-link/invite-link.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Test, TestingModule } from '@nestjs/testing';

import { getRepositoryToken } from '@mikro-orm/nestjs';

import { ExtendedEntityRepository } from 'src/common/helper/extended-repository.helper';
import {
MockRepository,
MockRepositoryFactory,
} from 'src/common/helper/mock.helper';
import {
InviteLink,
InviteLinkRepository,
User,
UserRepository,
} from 'src/entities/index';
import { UserService } from 'src/user/user.service';

import { InviteLinkService } from './invite-link.service';

type InviteLinkMockRepositoryType = MockRepository<
ExtendedEntityRepository<InviteLink>
>;
describe('InviteLinkService', () => {
let service: InviteLinkService;
let mockedRepository: InviteLinkMockRepositoryType;

beforeEach(async () => {
const repositoryToken = getRepositoryToken(User);

const module: TestingModule = await Test.createTestingModule({
providers: [
InviteLinkService,
{
provide: repositoryToken,
useFactory:
MockRepositoryFactory.getMockRepository(InviteLinkRepository),
},
],
}).compile();

service = module.get<InviteLinkService>(InviteLinkService);
mockedRepository =
module.get<InviteLinkMockRepositoryType>(repositoryToken);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
43 changes: 43 additions & 0 deletions src/invite-link/invite-link.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Injectable } from '@nestjs/common';

import { InjectRepository } from '@mikro-orm/nestjs';

import { INVITE_LINK_EXPIRATION_DAYS } from 'src/common/constants';
import { InviteLink, InviteLinkRepository, User } from 'src/entities';
import { InviteLinkResponseDto } from 'src/map/dtos/invite-link-response.dto';
import { UtilService } from 'src/util/util.service';

@Injectable()
export class InviteLinkService {
constructor(
@InjectRepository(InviteLink)
private readonly inviteLinkRepository: InviteLinkRepository,
private readonly utilService: UtilService,
) {}

async create(mapId: string, by: User): Promise<InviteLinkResponseDto> {
const expiration: number = this.getExpiration();
const input: string = `map_id=${mapId}&user_id=${by.id}&expiration=${expiration}`;
const token: string = this.utilService.generateMD5TokenWithSalt(input, 5);

const inviteLink: InviteLink = new InviteLink();
inviteLink.token = token;
inviteLink.createdBy = by;
inviteLink.map_id = mapId;
inviteLink.expires_at = new Date(expiration);

await this.inviteLinkRepository.persistAndFlush(inviteLink);

return {
inviteLinkToken: token,
};
}

private getExpiration(): number {
const now: Date = new Date();
const expirationDate = new Date(
now.getTime() + INVITE_LINK_EXPIRATION_DAYS * 24 * 60 * 60 * 1000,
);
return expirationDate.getTime();
}
}
5 changes: 2 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
// import { nodeProfilingIntegration } from '@sentry/profiling-node';
import cookieParser from 'cookie-parser';

import { CustomExceptionFilter } from 'src/core/exception-filters/custom-exception.filter';
import { ResponseInterceptor } from 'src/core/intercepters/response.intercepter';

import { AppModule } from './app.module';
import { CustomExceptionFilter } from './core/exception-filters/custom-exception.filter';
import { ResponseInterceptor } from './core/intercepters/response.intercepter';
import { UtilService } from './util/util.service';

async function bootstrap() {
Expand Down
6 changes: 6 additions & 0 deletions src/map/dtos/invite-link-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';

export class InviteLinkResponseDto {
@ApiProperty()
inviteLinkToken: string;
}
3 changes: 1 addition & 2 deletions src/map/map.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Test, TestingModule } from '@nestjs/testing';

import { MockService, MockServiceFactory } from 'src/common/helper/mock.helper';

import { MockService, MockServiceFactory } from '../common/helper/mock.helper';
import { MapController } from './map.controller';
import { MapService } from './map.service';

Expand Down
Loading
Loading