NestJs Chapter 3

yeopยท2022๋…„ 7์›” 14์ผ

Nest JS ์ •๋ฆฌ

๋ชฉ๋ก ๋ณด๊ธฐ
3/10

Email Verification

๐Ÿ”ท One-to-one relations

์ผ๋Œ€์ผ ๊ด€๊ณ„๋Š” A๊ฐ€ B์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ํ•˜๋‚˜๋งŒ ํฌํ•จํ•˜๊ณ  B๊ฐ€ A์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ํ•˜๋‚˜๋งŒ ํฌํ•จํ•˜๋Š” ๊ด€๊ณ„์ด๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์‚ฌ์šฉ์ž ๋ฐ ํ”„๋กœํ•„ ์—”ํ„ฐํ‹ฐ๋ฅผ ๋ณด๋ฉด, ์‚ฌ์šฉ์ž๋Š” ํ•˜๋‚˜์˜ ํ”„๋กœํ•„๋งŒ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํ”„๋กœํ•„์€ ํ•˜๋‚˜์˜ ์‚ฌ์šฉ์ž๋งŒ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค.

@JoinColumn()์„ ์„ค์ •ํ•œ ์ชฝ์˜ ํ…Œ์ด๋ธ”์—๋Š” ํ•ด๋‹น๋˜๋Š” ์—”ํ„ฐํ‹ฐ ํ…Œ์ด๋ธ”์— ๋Œ€ํ•œ relation id์™€ foreign keys๋ฅผ ํฌํ•จํ•œ๋‹ค.

@JoinColumn์€ ๊ด€๊ณ„์˜ ํ•œ ์ชฝ, ์ฆ‰ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์— foreign key๊ฐ€ ์žˆ์–ด์•ผ ํ•˜๋Š” ์ชฝ์—๋งŒ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

@InputType()
@ObjectType()
@Entity()
export class Verification extends CoreEntity {
  @Column()
  @Field((type) => String)
  code: string;

  @OneToOne((type) => User)
  @JoinColumn()
  user: User;
}

Case) Verification์„ ํ†ตํ•ด ๊ทธ ์•ˆ์— User์— ์ ‘๊ทผํ•ด์„œ User์˜ email Verified๋ฅผ false์—์„œ true๋กœ ๋ฐ”๊ฟ€ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— Verification์ชฝ์— @JoinColumn()์„ ์ถ”๊ฐ€ํ•˜๊ณ  user๋ฅผ ํ†ตํ•ด ์ƒ์„ฑํ•œ foreign key์ธ userId์„ ์ถ”๊ฐ€ํ•˜๋„๋ก ํ•œ ๊ฒƒ์ด๋‹ค.

๐Ÿ”ท ํ…Œ์ด๋ธ” ์—ฐ๊ฒฐํ•˜๊ธฐ

TypeORM์—์„œ๋„ ๊ด€๊ณ„๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํ•„๋“œ๋Š” TypeORM์— ๋”ฐ๋กœ ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์ž๋™์œผ๋กœ ํ•ด๋‹น ํ•„๋“œ๋ฅผ ๋ณด์—ฌ์ฃผ์ง€ ์•Š๋Š”๋‹ค.

๐Ÿ”น loadRelationIds: true

true๋กœ ์„ค์ •์‹œ relation id ๊ฐ’๋งŒ์„ ๊ฐ€์ ธ์˜จ๋‹ค. (userId: 10)
์—”ํ„ฐํ‹ฐ์˜ ๋ชจ๋“  ๊ด€๊ณ„ ID๋ฅผ ๋กœ๋“œํ•˜๊ณ  ๊ด€๊ณ„ ๊ฐœ์ฒด๊ฐ€ ์•„๋‹Œ ๊ด€๊ณ„ ๊ฐ’์— ๋งคํ•‘ํžŒ๋‹ค.

๐Ÿ”น relations

relations๋ฅผ ํ†ตํ•ด ํ•ด๋‹น ํ•„๋“œ์˜ ์ „์ฒด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜๋„ ์žˆ๋‹ค.

async verifyEmail(code: string): Promise<boolean> {
    const verification = await this.verification.findOne({
      where: { code },
      relations: ['user'],
    });
    if (verification) {
      verification.user.verified = true;
      this.users.save(verification.user);
    }
    return false;
  }

๐Ÿ”ท Password Hash Error Fix

verified ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•ด this.user.Save(verification.user) ํ•  ๋•Œ BeforeUpdate๊ฐ€ ํ•ญ์ƒ ์‹คํ–‰๋˜์–ด password๊ฐ€ ์—ฌ๋Ÿฌ๋ฒˆ ํ•ด์‰ฌ๋œ๋‹ค.

- ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

  1. {select: false}
  • Entity ํŒŒ์ผ์—์„œ ํ•ด๋‹น Column(password)์— {select: false}๋ฅผ ์‚ฝ์ž…ํ•ด find ์‹คํ–‰์ž(find๋ฉ”์„œ๋“œ๋“ค)๋ฅผ ํ†ตํ•ด ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ํ•ด๋‹น column์„ ์„ ํƒ๋˜์–ด์ง€์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

  • find๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋ฐ›์€ ๊ฐ์ฒด์— ํ•ด๋‹น column์ด ์žˆ์„ ๊ฒฝ์šฐ์—๋งŒ save๊ฐ€ ์‹คํ–‰๋˜๋„๋ก if๋ฌธ์„ ์‚ฌ์šฉํ•œ๋‹ค.

    โ€ป Column์„ {select: false}๋กœ ์ง€์ •ํ–ˆ์„ ๊ฒฝ์šฐ ์ด์ „์— ์งœ๋†“์€ ์ฝ”๋“œ๋“ค ์ค‘ find๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„์—์„œ ํ•ด๋‹น column์„ ๋ฆฌํ„ด๋ฐ›์ง€ ๋ชปํ•ด ์—๋Ÿฌ๊ฐ€ ๋‚  ์ˆ˜ ์žˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ find๋ฉ”์„œ๋“œ์— select:['column']์„ ๋„ฃ์–ด ํ•ด๋‹น Column์ด ๋ฆฌํ„ด๋˜๋„๋ก ์„ค์ •ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

  1. this.user.update(verification.user)
  • update()๋ฅผ ์‹คํ–‰ํ•˜๊ฒŒ ๋˜๋ฉด @BeforeUpdate()๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๊ฐ€ ์‹คํ–‰๋˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ์ด์šฉํ•ด์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ๊ฐ„๋‹จํ•˜๊ฒŒ update()๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ verified๋ฅผ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ๋‹ค.
await this.verification.update(
	verification.user.id,
    { verified: true });

โ€ป Mailgun

๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ ํŠธ๋žœ์ ์…˜ ์ด๋ฉ”์ผ API ์„œ๋น„์Šค
https://www.mailgun.com

โ€ป NestJS Mailer

Nodemailer ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” Nest.js ํ”„๋ ˆ์ž„์›Œํฌ(node.js)์šฉ ๋ฉ”์ผ๋Ÿฌ ๋ชจ๋“ˆ
pug์™€ ์—ฐ๋™ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ง์ ‘ html์„ ์ž‘์„ฑํ•ด ๋ณต์žกํ•˜๊ณ  ํ™”๋ คํ•œ ์ด๋ฉ”์ผ ์ „์†ก์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
mailgun ๋˜ํ•œ mailgun templete์„ ์ด์šฉํ•ด ๋ณต์žกํ•œ ์ด๋ฉ”์ผ ์ „์†ก์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
https://nest-modules.github.io/mailer
https://github.com/nest-modules/mailer

๐Ÿ”ท Send Mail

๐Ÿ”น GOT

npm i got
Node.js๋ฅผ ์œ„ํ•œ ์ธ๊ฐ„ ์นœํ™”์ ์ด๊ณ  ๊ฐ•๋ ฅํ•œ HTTP request ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
(ํ”„๋ก ํŠธ์—”๋“œ์—์„œ์˜ Fetch์™€ ๊ฐ™์€ ์—ญํ• ์„ ํ•œ๋‹ค.)

๐Ÿ”น Form-Data

npm i form-data
์ฝ์„ ์ˆ˜ ์žˆ๋Š” "multipart/form-data" ์ŠคํŠธ๋ฆผ์„ ์ƒ์„ฑํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
๋‹ค๋ฅธ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— form์„ submitํ•˜๊ณ , ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
์—ฌ๊ธฐ์„œ๋Š” mailgun์˜ api๋กœ post๋ฅผ ๋ณด๋‚ผ ๋•Œ form์ด ์‚ฌ์šฉ๋˜์—ˆ๋‹ค.

Form์˜ ํ˜•์‹์€ Mailgun Api์ •๋ณด์— ๋‚˜์™€์žˆ๋Š” ํ˜•์‹์„ ๋”ฐ๋ฅธ๋‹ค.

private async sendMail(subject: string, context: string) {
    const form = new FormData();
    form.append('from', `Excited User <mailgun@${this.options.domain}>`);
    form.append('to', `tem123@nate.com`);
    form.append('subject', subject);
    form.append('text', context);
  
    const response = await got(
      `https://api.mailgun.net/v3/${this.options.domain}/messages`,
      {
        method: 'POST',
        headers: {
          Authorization: `Basic ${Buffer.from(
            `api:${this.options.apiKeys}`,
          ).toString('base64')}`, // Buffer: Binary์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด์„ ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด
        },
        body: form,
      },
    );
    console.log(response.body);
  }

๐Ÿ”น Mailgun Templete

- Handlebars

ํ•ธ๋“ค๋ฐ”๋Š” ๊ฐ„๋‹จํ•œ ํ…œํ”Œ๋ฆฟ ์–ธ์–ด์ด๋‹ค. ํ…œํ”Œ๋ฆฟ๊ณผ input๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ HTML ๋˜๋Š” ๊ธฐํƒ€ ํ…์ŠคํŠธ ํ˜•์‹์„ ์ƒ์„ฑํ•œ๋‹ค.

  • ํ•ธ๋“ค๋ฐ”์—์„œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ: {{ code }}
    ex) < p >{{firstname}} {{lastname}}< / p >
    https://handlebarsjs.com/guide/

- Template (Mailgun template์„ ํ†ตํ•ด ์ƒ์„ฑํ•œ ํ…œํ”Œ๋ฆฟ ์ด๋ฆ„)

ํ…œํ”Œ๋ฆฟ API๋ฅผ ํ†ตํ•ด ์ €์žฅ๋œ ํ…œํ”Œ๋ฆฟ ์ด๋ฆ„
ex) formData.append('template', 'nuber-eats');

- v:my-var (์‚ฌ์šฉํ•  ๋ณ€์ˆ˜ ์ด๋ฆ„)

v: prefix๋ฅผ ํ†ตํ•ด ์ปค์Šคํ…€ JSON ๋ฐ์ดํ„ฐ๋ฅผ ๋ฉ”์„ธ์ง€์— ์ด๋ฆ„์„ ๋ถ™์—ฌ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
ex) formData.append('v:code', 'abcd1234')

    form.append('template', 'verify-email');
    form.append('v:username', 'yeop');
    form.append('v:code', '15646');

0๊ฐœ์˜ ๋Œ“๊ธ€