44,000원
다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
옵셔널한 프로퍼티에서 ?의 사용
안녕하세요. 강의를 보고 프로젝트 진행중 질문사항이 있어서 문의드립니다.response와 request dto 분리해서 작업중인데 response dto에서 @ApiProperty({nullable:true}) @Expose() name?: string;이렇게 작성시 '?'를 작성할 필요가 없지않나 싶어서요!?가 값이 optional로 들어오지 않을 수 있다라는 느낌인데 이건 request dto에서만 작성하고 response dto에서는 작성안해도되는게 맞을까요?
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
이전 질문에 연장되는 질문입니다.
안녕하세요 조현영님. 주말에도 답변 계속 달아주셔서 감사드립니다. 정말 도움이 많이됩니다! 저번 질문에서 제가 리뷰와 이미지 사이의 관계를 여쭤봤을 때 조현영님께서 리뷰와 이미지 사이의 관계는 일대다라고 하셨었죠. 이렇게 되면 리뷰쪽 컬럼에는 oneToMany, 이미지 쪽에는 ManyToOne이 붙게 되어 더불어 JoinColumn까지 이미지 쪽에 붙게 됩니다. 그러면 이미지 쪽에 리뷰에 관한 외래키 컬럼이 생성됩니다. 그런데 저는 여기서 위와 똑같은 방식으로 리뷰 대신 문의의 관련된 엔티티와 리뷰와 이미지를 관계 설정한것 처럼 똑같이 하려 합니다. 그러면 결국에 이미지 쪽에는 리뷰에 관한 외래키 컬럼과 문의에 관한 외래키 컬럼이 생기게 되는 것이죠. 여기서가 핵심인데 이렇게 되면 이미지 엔티티에는 리뷰, 문의가 공존하게 되는데 만약 리뷰가 있을시 문의의 외래키 컬럼에는 null값으로 존재하게 되고 문의가 있을시 리뷰의 외래키 컬럼에는 null값으로 존재합니다. 그리고 이미지에 추가적으로 상품 이미지 역시 존재합니다. 이러면 이미지 엔티티에 세 개(상품, 리뷰, 문의)의 외래키 컬럼이 존재하게 되는데 만약에 한 개의 컬럼에 값이 존재할 때 나머지 두개는 null값인 형태입니다. 그래서 제 결론은 이미지 엔티티 하나에 세 개의 외래키를 전부 넣는것이 아닌 예를 들어 상품 이미지 엔티티, 리뷰 이미지 엔티티, 문의 이미지 엔티티 이런식으로 엔티티를 각각 만들어야 할거 같은데 실무에서는 어떻게 해결하는지 조현영님의 의견이 궁금합니다.
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
many to many 와 one to many, many to one에 대해서 질문드립니다.
안녕하세요 조현영님. 다대다 관계를 설정할 때 관계를 연결할 컬럼을 만들고 many to many 데코레이터를 달은 후 둘 중하나의 컬럼에다가 보통 joinTable컬럼을 달잖아요. 그런데 joinTable을 달게 되면 새로운 테이블이 하나 더 생기게 될텐데 제가 관계 설계중 상품과 리뷰의 관계를 many to many로 서로 해주려 했었는데 이러면 불필요한 테이블이 하나 생기게 되어 필요가 없을거 같아서요. 그런데 또 many to many를 one to many와 many to one으로 바꿔서 사용이 가능하다 하셨는데 질문1. 만약 joinTable대신 joinColumn을 쓰고 싶을 때 many to many 대신 one to many, many to one을 사용할 수 있을까요? 질문2. 위 질문이 가능하다면 저는 many to many를 쓰는 대신 상품 엔티티와 유저 엔티티 사이에 리뷰 엔티티를 만들어 놓고 상품 엔티티 - one to many -> 리뷰 엔티티 상품 엔티티 <- many to one - 리뷰 엔티티 유저 - one to many -> 리뷰 엔티티 유저 <- many to one - 리뷰 엔티티 이런식으로 사용이 가능할까요?
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
test 관련 질문드립니다!
안녕하세요 제로초님! test관련 질문이 있어서 질문글 남깁니다 :) 아래와 같이 따로 Repository provider를 만들고 export const userProviders = [ { provide: USER_REPOSITORY, useFactory: (dataSource: DataSource) => dataSource.getRepository(User), inject: [APP_DATABASE], }, ]; Service에서 아래와 같이 @InjectRepository가 아니라 @Inject로 생성자를 구성했습니다. (USER_REPOSITORY는 따로 constants.ts파일을 만들어두었습니다. @Injectable() export class UserService { constructor( @Inject(USER_REPOSITORY) private userRepository: Repository<User>, ) {} . . . 이후 Service 파일의 테스트 코드를 작성하는데 모킹이 안돼서 질문을 남기게 되었습니다. . . . providers: [ UserService, { provide: getRepositoryToken(User), useValue: mockUserRepository, }, ], . . . 위와 같이 모킹했는데, 아래와 같은 오류가 떴습니다. ㅜㅜ Nest can't resolve dependencies of the UserService (?). Please make sure that the argument USER_REPOSITORY at index [0] is available in the RootTestModule context. Potential solutions: - If USER_REPOSITORY is a provider, is it part of the current RootTestModule? - If USER_REPOSITORY is exported from a separate @Module, is that module imported within RootTestModule? @Module({ imports: [ /* the Module containing USER_REPOSITORY */ ] }) at TestingInjector.lookupComponentInParentModules (../node_modules/@nestjs/core/injector/injector.js:231:19) at TestingInjector.resolveComponentInstance (../node_modules/@nestjs/core/injector/injector.js:184:33) at TestingInjector.resolveComponentInstance (../node_modules/@nestjs/testing/testing-injector.js:16:45) at resolveParam (../node_modules/@nestjs/core/injector/injector.js:106:38) at async Promise.all (index 0) at TestingInjector.resolveConstructorParams (../node_modules/@nestjs/core/injector/injector.js:121:27) at TestingInjector.loadInstance (../node_modules/@nestjs/core/injector/injector.js:52:9) at TestingInjector.loadProvider (../node_modules/@nestjs/core/injector/injector.js:74:9) at async Promise.all (index 3)
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
apiProperty()
swagger에서 apiProperty()에서 type으로 dto 자기 자신을 나타내진 못하나요? 동일한 dto 구조로 내부에 작성하고 싶은데 안된다면 어떻게 진행해야할까요?
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
공지내용대로 해도 여전히 마이그레이션시 dataSource를 찾을 수 없다는 에러가 뜹니다.
제로초님 안녕하세요. typeorm 0.3 버전 사용에 대한 빠른 대응 감사합니다. 저 그런데 공지사항에 올려주신대로 해도 에러메시지가 뜨면서 실행이 안 됩니다. 공지에 올려주신 것처럼 src 폴더 아래 dataSource.ts 파일을 만들었고, https://github.com/typeorm/typeorm/issues/8810#issuecomment-1084255476 여기에 나온 것을 참고해서 npx typeorm migration:generate src/migrations/categoryToType -d src/dataSource.ts 와 npx typeorm migration:run -d src/dataSource.ts를 실행시켜면 Unable to open file: '.../src/dataSource.ts' 라는 에러가 뜨면서 안 됩니다. package.json의 script에 "db:migrate": "npm run typeorm migration:run -d ./src/dataSource.ts"로 지정해서 실행시켜보면 Missing required argument: dataSource라고 나오고요. 어떻게 해결해야할까요?
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
공지내용(typeorm@0.3 관련)관련해서 도움 요청 드립니다.
제로초님 시간이 가능하시면 메일로 주신 공지내용(typeorm@0.3 관련)관련해서 좀 더 참고할 수 있는(강의 내용 일부 기능) 소스 또는 추가 강의 부탁드립니다. dataSource.ts 파일의 const dataSource = new DataSource({...})로 dataSource 인스턴스를 생성했는데, app.module 에서 ormconfig 대신에 TypeOrmModule.forRoot({...}) 로 DB 연결을 또 생성을 하게 되면 연결이 다른 컨넥션 2개를 가지게 되는 건가요? 공지내용 첫 소스 내용에 "private datasource: DataSource" 부분에 이렇게 DI를 하게 되면 const dataSource = new DataSource({...}) 부분의 dataSource 의 DB 연결을 가지고 오게 되나요? 0.2.x 보다는 0.3.x 가 query 작성하는 부분에서 많이 편한게 보여서 0.3.x로 프로토타입을 만들어 보고 싶습니다.
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
쿼리빌더 사용과 조인과 셀렉트에 관련된 질문입니다.
안녕하세요 조현영님. 이번에는 쿼리빌더로 원하는 데이터를 얻기 위해 조인과 셀렉트 과정에서 오류가 발생하여 질문드립니다. 일단 질문을 드리기 전에 제 상황에 대한 사전지식이 필요하기 때문에 코드를 같이 보여드리겠습니다. @Entity("users") export class UserEntity extends CommonEntity { @OneToOne(() => UserProfileEntity, (common) => common.id) @JoinColumn({ name: "commonId", referencedColumnName: "id" }) profile: UserProfileEntity; @OneToOne(() => UserAuthEntity, (auth) => auth.id) @JoinColumn({ name: "authId", referencedColumnName: "id" }) auth: UserAuthEntity; @OneToOne(() => UserActivityEntity, (activity) => activity.id) @JoinColumn({ name: "activityId", referencedColumnName: "id" }) activity: UserActivityEntity; } 사용자에 관련된 엔티티입니다. 사용자와 관련된 컬럼들은 세부적으로 엔티티를 나눈 다음 그 엔티티안에 정의 해두었습니다. 사용자 엔티티에는 프로필, 인증, 활동 엔티티의 아이디만 존재합니다. 아래에는 사용자 엔티티에게 상속을 해주는 CommonEntity입니다. export abstract class CommonEntity { @IsUUID() @PrimaryGeneratedColumn("uuid") id: string; @CreateDateColumn({ type: "timestamp" }) createdAt: Date; @UpdateDateColumn({ type: "timestamp" }) updatedAt: Date; @DeleteDateColumn({ type: "timestamp" }) deletedAt: Date | null; } 아래에는 각각 프로필, 인증, 활동 엔티티의 대한 코드입니다. @Entity("users profile") export class UserProfileEntity { @PrimaryGeneratedColumn("uuid") id: string; @IsString() @IsNotEmpty() @Column({ type: "varchar", length: 20, nullable: false }) realname: string; @IsDateString() @IsNotEmpty() @Column({ type: "date", nullable: false }) birth: Date; @IsEnum(["male", "female"]) @IsNotEmpty() @Column({ type: "enum", enum: ["male", "female"] }) gender: "male" | "female"; @IsMobilePhone() @IsNotEmpty() @Column({ type: "varchar", length: 15, unique: true, nullable: false }) phonenumber: string; @OneToOne(() => UserEntity) user: UserEntity; } @Entity("users auth") export class UserAuthEntity { @PrimaryGeneratedColumn("uuid") id: string; @IsString() @IsNotEmpty() @Column({ type: "varchar", length: 20, unique: true, nullable: false }) nickname: string; @IsEmail() @IsNotEmpty() @Column({ type: "varchar", length: 60, unique: true, nullable: false }) email: string; @IsString() @IsNotEmpty() @Matches(/^[A-Za-z\d!@#$%^&*()]{8,30}$/) @Column({ type: "varchar", nullable: false }) password: string; @Column({ type: "enum", enum: ["general", "special", "admin"], default: "general", }) userType: "general" | "special" | "admin"; @OneToOne(() => UserEntity) user: UserEntity; @OneToMany(() => ImagesEntity, (join) => join.uploader) image: ImagesEntity[]; } @Entity("users activity") export class UserActivityEntity { @PrimaryGeneratedColumn("uuid") id: string; @Column({ type: "smallint", default: 0 }) bonusPoint: number; @Column({ type: "smallint", default: 0 }) purchaseCount: number; @Column({ type: "smallint", default: 0 }) productInquiryCount: number; @Column({ type: "smallint", default: 0 }) productReviewCount: number; @OneToOne(() => UserEntity) user: UserEntity; @OneToMany(() => ReviewEntity, (review) => review.reviewer) review: ReviewEntity; } 이런식으로 한 사용자 엔티티에는 프로필, 인증, 활등 에 대한 엔티티와 1:1 관계를 설정 해 두었습니다. 그리고 이제 특정 사용자에 대한 정보를 가져올 때 사용되는 메서드입니다. async findUserWithId(userId: string): Promise<UserEntity> { try { return await this.userRepository .createQueryBuilder("user") .innerJoinAndSelect("user.profile", "profile") .innerJoinAndSelect("user.auth", "auth") .innerJoinAndSelect("user.activity", "activity") .where("user.id = :id", { id: userId }) .getOneOrFail(); } catch (err) { throw new UnauthorizedException("해당 사용자아이디는 존재하지 않습니다."); } } userId 매개변수는 CommenEntity에서 상속된 id컬럼을 사용합니다. 즉 사용자 엔티티 id라고 보시면 됩니다. userRepository또한 아래처럼 사용자 엔티티의 리파지토리입니다. @InjectRepository(UserEntity) private readonly userRepository: Repository<UserEntity> 이제 본격적으로 문제에 관련된 질문을 드리자면 innerJoinAndSelect를 사용할 시 관계가 맺어진 엔티티들의 정보들을 모두 가져오게 되어서 노출이 되어서는 안될 비밀번호등 까지 보여지게 됩니다. 그래서 저는 innerJoinAndSelect대신 innerJoin으로 관계가 맺어진 엔티티들을 조인하고 select메서드로 원하는 컬럼만 선택하려했습니다. async findUserWithId(userId: string): Promise<UserEntity> { try { return await this.userRepository .createQueryBuilder("user") .select(this.select.UserInformationReturnProperty) .innerJoin("user.profile", "profile") .inneroin("user.auth", "auth") .innerJoin("user.activity", "activity") .where("user.id = :id", { id: userId }) .getOneOrFail(); } catch (err) { throw new UnauthorizedException("해당 사용자아이디는 존재하지 않습니다."); } } select안에 인수는 아래와 같습니다. UserInformationReturnProperty: [ "profile.realname", "auth.nickname", "profile.birth", "profile.gender", "auth.email", "profile.phonenumber", "auth.userType", "activity.purchaseCount", "activity.bonusPoint", "activity.productInquiryCount", "activity.productReviewCount", ] 이렇게 보여줘도 상관없을 만한 정보들로 구성하였습니다. 문제는 이렇게 하고 쿼리를 돌렸을 때 'EntityNotFoundError: Could not find any entity of type "UserEntity" matching: [object Object]'이런 오류가 납니다. 해석해보면 '일치하는 "UserEntity" 유형의 엔터티를 찾을 수 없음' 이런 뜻인거 같은데 분명히 리파지토리도 UserEntity이고 select 대신 innerJoinAndSelect를 하면 정상적으로 불러와 지는데 어떤 부분이 잘못된건지 모르겠어서 질문드립니다.
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
제로초님 entity가져오고 에러가 계속나네요... 그대로 복붙했거든요 ..
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요.
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
ormconfig.ts 에러 질문입니다.
제로초님 안녕하세요! ormconfig.ts typeorm seeding, migration 쓰강의 보다가 에러나서 질문드려요 ㅠㅠ 그리고 connection: Connection으로 주셨는데 현재 사용하지 않는거 같아서 공식문서 보니 dataSource로 바뀐것같아서 타입으로 주고 실행하는데데 타입동작하지않아요
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
typeorm migration 실행이 안 됩니다.
제로초님 안녕하세요. 강좌에 나온대로 app module에 TypeOrmModule 관련된 설정은 아래처럼 해둔 상태인데요. npx typeorm migration:create -n categoryToType 명령어를 실행시키면 ...@Module({imports: [ConfigModule.forRoot({ isGlobal: true, load: [getEnv] }),TypeOrmModule.forRootAsync({inject: [ConfigService],useFactory: async (configService: ConfigService) => {return {type: 'mysql',host: 'localhost',port: 3306,username: configService.get('DB_USERNAME'),password: configService.get('DB_PASSWORD'),database: configService.get('DB_DATABASE'),entities: [...],migrations: [__dirname + '/src/migrations/*.ts'],cli: { migrationsDir: 'src/migrations' },autoLoadEntities: true,charset: 'utf8mb4',synchronize: false,logging: true, keepConnectionAlive: true, };},}),UsersModule,WorkspacesModule,ChannelsModule,DmsModule,],controllers: [AppController],providers: [AppService,ConfigService,{ provide: 'CUSTOM_KEY', useValue: 'CUSTOM_VALUE' },UsersService,],exports: [],})export class AppModule implements NestModule {configure(consumer: MiddlewareConsumer): any {consumer.apply(LoggerMiddleware).forRoutes('*');}} Not enough non-option arguments: got 0, need at least 1 이라는 에러메시지가 나오면서 마이그레이션이 안 됩니다. typeorm 공식문서(https://typeorm.io/using-cli#create-a-new-migration) 에서도 위처럼 TypeOrmModule 설정에 cli를 설정해두거나 아니면 npx typeorm migration:create -n UserMigration -d src/user/migration 이런 식으로 하게끔 되어 있는 것 같아서 path를 지정해서 해봤는데도 같은 에러가 나옵니다. 혹시 무엇이 문제인 걸까요?
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
인터셉터를 통한 응답의 관련된 질문입니다.
안녕하세요 조현영님. 인터셉터 관련해서 궁금한게 있어 질문드립니다. 아래는 제가 만들어 놓은 인터셉터입니다. intercept(context: ArgumentsHost, next: CallHandler<any>): Observable<any> { // controller 도달 전 const req = context.switchToHttp().getRequest(); const res = context.switchToHttp().getResponse(); console.log(`Receive request from ${req.method} ${req.originalUrl}`); const now = Date.now(); return next.handle().pipe( map((data: JSON<null>) => { // controller 도달 후 console.log( `Send response from ${req.method} ${ req.originalUrl } :: time taken : ${Date.now() - now}ms`, ); return res.status(data.statusCode).setHeader("X-Powered-By", "").json({ success: true, ...data}); }), ); } 이전까지는 이런식으로 인터셉터를 구성해서 포스트맨으로 사용할 땐 문제없이 요청과 응답이 오고 갈수 있었습니다. 그런데 시험삼아서 브라우저에 서버 url을 입력후 get메서드를 사용하는 api를 사용해봤는데 응답은 잘 갔었지만 서버 콘솔에 cannot set headers 오류가 났습니다. 이 오류는 꽤나 익숙해서 응답이 두번 보내져서 그런가 싶어 인터셉터 응답을 아래처럼 바꿔보았습니다. intercept(context: ArgumentsHost, next: CallHandler<any>): Observable<any> { // controller 도달 전 const req = context.switchToHttp().getRequest(); const res = context.switchToHttp().getResponse(); console.log(`Receive request from ${req.method} ${req.originalUrl}`); const now = Date.now(); return next.handle().pipe( map((data: JSON<null>) => { // controller 도달 후 console.log( `Send response from ${req.method} ${ req.originalUrl } :: time taken : ${Date.now() - now}ms`, ); res.status(data.statusCode).setHeader("X-Powered-By", ""); return { success: true, ...data }; }), ); } 이상한게 get, delete 메서드를 사용하는 api는 return문을 거쳤을 때 응답이 잘 도달되었지만 post,patch등은 응답이 가고 계속 로딩중입니다. 계속 기다려도 이러한 상태여서 디버깅을 통해 문제를 해결하고자 return문에서 부터 계속 디버깅을 시도했습니다. 그리고 문제였던 아래 코드를 발견했습니다. return async (result, res) => { result = await this.responseController.transformToResult(result); !isResponseHandled && (await this.responseController.apply(result, res, httpStatusCode)); }; 위의 코드는 node_modules/@nestjs/core/router/router-execution-context.js 라는 파일의 174 ~ 177줄의 코드입니다. 아마 result 변수가 인터셉터에서 리턴된 값으로 사용되는 변수 같은데 그 아래 있는 코드 실행 이후 계속 응답이 닿지 않는 모습이었습니다. 그래서 저는 아래처럼 다시 수정해봤습니다. return async (result, res) => { result = await this.responseController.transformToResult(result); await this.responseController.apply(result, res, httpStatusCode); }; 이후에는 post, patch메서드의 응답이 잘 닿았지만 과연 이게 올바른 해결 방법인지는 잘 모르겠습니다. 만약 이 프로젝트를 git에서 pull, clone등을 할 때 node_modules는 .gitignore에 등록해놓아서 위 처럼 변경 사항을 불러올 수가 없어서 git에서 pull, clone하려면 npm i로 모듈들을 받은 후 계속 저런식으로 수정을 해야 되서 이 방법은 좀 아닌거 같지만 일단 임시방편으로 해놓은 상태입니다. 조현영님께서는 이렇게 어떤 문제가 해결이 안될때 node_modules를 건드려서 해결하신적이 있으신가요?
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
Nest서버에서 실행 컨텍스트가 뭔가요?
제로초님 안녕하세요. 좋은 강의 재밌게 잘 듣고 있습니다. 다름이 아니라 커스텀 데코레이터 만드는 것을 설명해주시면서 같은 서버로 http, websocket, rpc를 동시에 돌릴 수 있기 때문에 이 중에서 필요한 부분의 정보를 가져올 때 switchTo__ 함수를 쓸 수 있다고 하셨는데요. 그런데 제가 아는 실행 컨텍스트는 자바스크립트에서 이야기하는 실행컨텍스트밖에 없는데, nest의 실행 컨텍스트라는 개념은 어디서 나온 건지 궁금합니다. Nest에서 관련된 문서를 읽어보기는 했는데, 프로그래밍 언어 레벨에서의 실행 컨텍스트와는 별도로 그냥 nest에서 http, ws, rpc를 동시에 지원하기 위해서 만든 개념이라고 생각하면 될까요? 감사합니다.
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
connection 여러개에 대하여
현재 스케줄러를 사용해서 탈퇴된 계정을 오전 6시에 일괄적으로 처리하는 코드를 구현중입니다. 그래서 아래와같은 db와 커넥션을 맺는 함수(userLeaveRoom)를 스케줄러에 등록해서 사용하려고하는 중에 질문이 생겼습니다. // 매일 6시 삭제된지 한달 지난 계정 삭제 @Cron('0 0 06 * * *', { name: 'initAccount' }) async initAccount() { / ... / const promises = roomMembers.map((member) => this.roomsService.userLeaveRoom(member)); const results = await Promise.allSettled(promises); } 이런식으로 사용할 경우에 순간적으로 커넥션을 많이 맺게되는데 이게 문제가 없을지 궁금합니다. async userLeaveRoom(leaveMember: RoomMembers) { const queryRunner = this.connection.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { /.../ await queryRunner.commitTransaction(); } catch (err) { await queryRunner.rollbackTransaction(); throw err; } finally { await queryRunner.release(); } }
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
multer를 사용해서 사진 혹은 동영상을 업로드 할 때 url을 통해서 사진은 접근이 가능하나 동영상은 404에러가 뜹니다.
안녕하세요. 오랜만에 질문드립니다. 제가 사진이나 동영상등을 멀터를 통해서 업로드 한 후 사진과 동영상을 각각 uploads/image, uploads/video 디렉터리에 disk storage 방식으로 저장하게 하였습니다. 그리고 저장된 파일이름과 서버url을 결합시켜서 db에 저장하게 하였고 사진은 url을 통해서 접근이 가능한데 동영상은 url로 접근할 때 404에러가 뜨더라구요. 동영상이 저장된 디렉터리로 이동시 동영상 파일이 온전히 있고 동시에 재생도 가능했습니다. 인코딩등의 문제가 관련이 있을까요?
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
제로초님 질문드리고싶습니다. 이런문제는 왜발생한건지 파일 캡쳐합니다 도저히 이해가 안돼네요 undefined property verify
import { Injectable, ExecutionContext, HttpException, HttpStatus, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { JwtService } from '@nestjs/jwt'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') { constructor( private readonly jwtService: JwtService, // @Inject(forwardRef(() => AdminsService)) // private readonly adminsService: AdminsService, ) { super(); } canActivate(context: ExecutionContext) { const request = context.switchToHttp().getRequest(); const { authorization } = request.headers; if (authorization === undefined) { throw new HttpException('Token 전송 안됨', HttpStatus.UNAUTHORIZED); } //const token = authorization.replace('Bearer ', authorization); const token = authorization.replace('Bearer ', ''); //console.log(token, 'token!!!'); request.user = this.validateToken(token); return true; } validateToken(token: string) { const secretKey = process.env.SECRET ? process.env.SECRET : 'dev'; try { const data = this.jwtService.verify(token, { secret: secretKey, }); console.log(data, '11번가데이터'); return data; } catch (e) { switch (e.message) { // 토큰에 대한 오류를 판단합니다. case 'INVALID_TOKEN': case 'TOKEN_IS_ARRAY': case 'NO_USER': throw new HttpException('유효하지 않은 토큰입니다.', 401); case 'EXPIRED_TOKEN': throw new HttpException('토큰이 만료되었습니다.', 410); default: console.trace(e); // console.log('광섭짱과 함께하는 코딩공부',) throw new HttpException('서버 오류입니다.', 500); } } } } 이부분은 jwt.guard.ts 입니다 저 빨간줄에서 Trace: TypeError: Cannot read properties of undefined (reading 'verify') 이렇게 나오는데 왜 저렇게 나오는건지 도저히 모르겠네요 해당 토큰값도 잘 받아와서 verify 를 이용해 토큰 유효성 검사를 진행하려하는데 그부분에서 에러가 계속 납니다... 도와주세요
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
관계가 적용되었을 때 row를 삭제하면 관계가 적용된 row도 삭제 하게 하려면 어떻게 해야 할까요?
상품 entity와 이미지 entity가 아래 처럼 있다고 가정합니다. @Entity("products") export class ProductEntity extends CommonEntity { @IsString() @IsNotEmpty() @Column({ type: "varchar", length: 20, unique: true, nullable: false }) name: string; @IsNumber() @IsNotEmpty() @Column({ type: "int", unsigned: true, nullable: false }) price: number; @IsString() @IsNotEmpty() @Column({ type: "varchar", length: 20, nullable: false }) origin: string; @IsString() @IsNotEmpty() @Column({ type: "varchar", length: 20, nullable: false }) type: string; @IsString() @IsNotEmpty() @Column({ type: "text", nullable: true }) description: string; @Column({ type: "int", default: 50 }) quantity: number; @Column({ type: "float", default: 0.0 }) rating: float; @OneToOne(() => ImagesEntity, (image) => image.product, { cascade: true }) @JoinColumn({ name: "imageId" }) image: ImagesEntity; } @Entity("images") export class ImagesEntity extends CommonEntity { @OneToOne(() => ProductEntity, (product: ProductEntity) => product, { onDelete: "CASCADE", onUpdate: "CASCADE", }) product: ProductEntity; @Column({ type: "varchar", nullable: false, unique: true }) url: string; @ManyToOne(() => UserAuthEntity, (Join) => Join.image) @JoinColumn({ name: "uploaderId", referencedColumnName: "id" }) uploader: UserAuthEntity; @Column({ type: "enum", enum: ["product upload", "review", "inquiry"] }) uploadReason: "product upload" | "review"; } product entity에는 image라는 가상 컬럼이 존재하고 images entity에는 product라는 가상 컬럼이 존재합니다. 여기서 상품쪽 row가 지워지면 이미지쪽 row도 지워지게 하고 싶어서 각각의 가상컬럼에 casecade : true 와 onDelete: "CASCADE"를 주었습니다. 질문 1. 상품 쪽에서 지울 때 이미지 쪽에서도 지워지게 하려면 옵션을 저렇게 설정하는게 맞을까요? 질문 2. 아래는 제가 구글링을 하다가 찾게 된 코드입니다. const author = await Author.findOne({ id: '123' }); author.books.push(new Book(...)); await author.save(); 위 같은 방식이 activate record가 맞을까요? 질문 3. 위 질문이 맞다면 제가 save메서드를 호출해야 cascade가 적용 된다는 글을 봤었는데(https://velog.io/@hahaha/TypeORM-Relation) 그러면 repository pattern을 사용하면 cascade를 적용시킬 수 없는것일까요?
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
typeorm 쿼리를 사용할 때 에러가 날 수있는 가능성이 궁금합니다.
안녕하세요 조현영님. typeorm 쿼리 중에서 뭔가를 찾거나 할 때 find혹은 findOne등을 사용잖아요. 그리고 create로 생성하거나 save로 저장하고 이런 쿼리등을 사용할 때 에러가 날 수 있는 경우가 있을까요? 단 find의 where등으로 컬럼에 접근 하는 경우를 제외하고요. await을 연달아 쓰는것 대신 settled로 묶은 후reject된값을 에러 배열에 담은 다음 에러 배열의 길이가 1이상이면 InternalServerErrorException을 throw 하려 하는데 에러 처리하는 구문 때문에 함수가 비대해지는 느낌이 있어서요. 만약 에러가 날 가능성이 없다면 이 에러 처리 구문을 굳이 안넣으려 하거든요. 가능성이 있을지 궁금합니다.
- 해결됨Slack 클론 코딩[백엔드 with NestJS + TypeORM]
OneToMany 관계 설정 질문드립니다.
안녕하세요 조현영님. 질문이 계속 쏟아져나오네요! 첫 질문 때 이야기 했던 사용자(UserEntity)와 이미지(ImageEntity)간의 관계는 OneToMany 관계인것에 대해 질문하려합니다. UserEntity에 image라는 컬럼을 두고 아래 처럼 관계 설정 하였습니다. // user.entity.ts @OneToMany(() => ImagesEntity, (join) => join.imageForigenKeyForUser) @JoinColumn() image?: ImagesEntity; 아래는 ImageEntity의 관계 설정입니다. // image.entity.ts @ManyToOne(() => UserEntity) imageForigenKeyForUser: string; 질문 1. mysql 워크밴치등으로 Image테이블을 보면 imageForigenKeyForUserId라는 컬럼이 생성되었습니다. 이렇게 컬럼이름 + Id가 붙은 컬럼을 가상 컬럼이라 부르는것이 맞을까요? 질문 2. 만약 위 질문이 맞다면 원래 제가 예상한것은 User테이블에 imageId라는 가상 컬럼이 생기는것을 예상했는데 User테이블에는 가상 컬럼이 존재하지가 않네요. 위 코드에서 관계 설정이 잘못된 것일까요? 제가 이전에 상품과 이미지 관계를 OneToOne으로 맺었을 때는 제가 원한대로 (JoinColumn()이 붙은쪽에 컬럼이름 + Id가 붙은 가상 컬럼이 생성됨) 관계가 형성 되었습니다. 아래 코드를 사용해서요. // product.entity.ts @OneToOne(() => ImagesEntity, (join) => join.imageForigenKeyForProduct) @JoinColumn() image: ImagesEntity; // image.entity.ts @OneToOne(() => ProductEntity) imageForigenKeyForProduct: string; 질문 3. 한쪽에서 OneToMany 관계로 시작하면 받는쪽은 ManyToOne이 맞죠? ex) @OneToOne() Image => @ManyToOne() ImageForigenKey 질문이 많은거 같은데 시간 나실 때 천천히 봐주시면 감사할거 같습니다.
- 미해결Slack 클론 코딩[백엔드 with NestJS + TypeORM]
SQL문 질문
선생님 채팅방 강퇴를 구현중인데, 강퇴한사람이 해당 방 맴버가 맞는지 그리고 강퇴권한(isOwner OR isManager)이 있는지 체크하려는 sql문을 작성중입니다. SELECT `rm`.`UserId` AS `rm_UserId`, `rm`.`RoomId` AS `rm_RoomId` FROM `room_members` `rm` WHERE `rm`.`RoomId` = ? AND `rm`.`UserId` = ? AND `rm`.`isOwner` = ? OR `rm`.`isManager` = ?이렇게 sql을 짜도 될지 모르겠습니다.roomId와 userId에 인덱싱이 걸려서 먼저 처리되고 isOwner OR isManager 둘중 하나인지 확인하려고하는데, 마지막에 or 때문에 풀쿼리들어갈까봐 걱정됩니다. 저렇게 짜도 괜찮을까요?? typeorm으론 이렇습니다. const isManager = await this.roomMembersRepository .createQueryBuilder('rm') .select('rm.UserId') .where('rm.RoomId = :RoomId AND rm.UserId = :UserId', { RoomId, UserId }) .andWhere('rm.isOwner = :isOwner OR rm.isManager = :isManager', { isOwner: true, isManager: true, }) .getOne();