diff --git a/package-lock.json b/package-lock.json index 0a7e5ce..846819c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@nestjs/schedule": "^3.0.3", "@nestjs/swagger": "^7.1.16", "axios": "^1.3.4", + "cheerio": "^1.0.0-rc.12", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "cookie-parser": "^1.4.6", @@ -40,6 +41,7 @@ "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@prisma/client": "^5.3.1", + "@types/cheerio": "^0.22.35", "@types/cookie-parser": "^1.4.3", "@types/cron": "^2.0.1", "@types/express": "^4.17.13", @@ -3773,6 +3775,15 @@ "@types/node": "*" } }, + "node_modules/@types/cheerio": { + "version": "0.22.35", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.35.tgz", + "integrity": "sha512-yD57BchKRvTV+JD53UZ6PD8KWY5g5rvvMLRnZR3EQBCZXiDT/HR+pKpMzFGlWNhFrXlo7VPZXtKvIEwZkAWOIA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -4986,6 +4997,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "node_modules/bowser": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", @@ -5206,6 +5222,42 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -5595,6 +5647,32 @@ "node": ">= 8" } }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/dayjs": { "version": "1.11.9", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", @@ -9236,6 +9314,17 @@ "node": ">=8" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -9429,6 +9518,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parseley": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", @@ -15011,6 +15134,15 @@ "@types/node": "*" } }, + "@types/cheerio": { + "version": "0.22.35", + "resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.35.tgz", + "integrity": "sha512-yD57BchKRvTV+JD53UZ6PD8KWY5g5rvvMLRnZR3EQBCZXiDT/HR+pKpMzFGlWNhFrXlo7VPZXtKvIEwZkAWOIA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -16025,6 +16157,11 @@ } } }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "bowser": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", @@ -16168,6 +16305,33 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "requires": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + } + }, + "cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "requires": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + } + }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -16467,6 +16631,23 @@ "which": "^2.0.1" } }, + "css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + } + }, + "css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" + }, "dayjs": { "version": "1.11.9", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", @@ -19244,6 +19425,14 @@ "path-key": "^3.0.0" } }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "requires": { + "boolbase": "^1.0.0" + } + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -19377,6 +19566,30 @@ "lines-and-columns": "^1.1.6" } }, + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "requires": { + "entities": "^4.4.0" + }, + "dependencies": { + "entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + } + } + }, + "parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "requires": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + } + }, "parseley": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", diff --git a/package.json b/package.json index e9a985e..c3126cd 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@nestjs/schedule": "^3.0.3", "@nestjs/swagger": "^7.1.16", "axios": "^1.3.4", + "cheerio": "^1.0.0-rc.12", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "cookie-parser": "^1.4.6", @@ -55,6 +56,7 @@ "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.0.0", "@prisma/client": "^5.3.1", + "@types/cheerio": "^0.22.35", "@types/cookie-parser": "^1.4.3", "@types/cron": "^2.0.1", "@types/express": "^4.17.13", diff --git a/src/init.ts b/src/init.ts new file mode 100644 index 0000000..bbda1c3 --- /dev/null +++ b/src/init.ts @@ -0,0 +1,6 @@ +import dayjs from 'dayjs'; +import timezone from 'dayjs/plugin/timezone'; +import utc from 'dayjs/plugin/utc'; + +dayjs.extend(utc); +dayjs.extend(timezone); diff --git a/src/main.ts b/src/main.ts index 7114830..4fe2492 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,8 @@ import { NestFactory } from '@nestjs/core'; -import { AppModule } from './app.module'; -import cookieParser from 'cookie-parser'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import cookieParser from 'cookie-parser'; +import { AppModule } from './app.module'; +import './init'; async function bootstrap() { const app = await NestFactory.create(AppModule); diff --git a/src/notice/dto/getAllNotice.dto.ts b/src/notice/dto/getAllNotice.dto.ts index caa3280..963b67a 100644 --- a/src/notice/dto/getAllNotice.dto.ts +++ b/src/notice/dto/getAllNotice.dto.ts @@ -65,7 +65,7 @@ export class GetAllNoticeQueryDto { @IsString() @IsEnum(['deadline', 'hot', 'recent']) @IsOptional() - orderBy?: string; + orderBy?: 'recent' | 'deadline' | 'hot'; @ApiProperty({ example: 'own', @@ -75,5 +75,5 @@ export class GetAllNoticeQueryDto { @IsString() @IsEnum(['own', 'reminders']) @IsOptional() - my?: string; + my?: 'own' | 'reminders'; } diff --git a/src/notice/notice.module.ts b/src/notice/notice.module.ts index 1ed1c9c..dca5b08 100644 --- a/src/notice/notice.module.ts +++ b/src/notice/notice.module.ts @@ -1,15 +1,26 @@ +import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; -import { NoticeController } from './notice.controller'; -import { NoticeService } from './notice.service'; -import { UserModule } from 'src/user/user.module'; import { ConfigModule } from '@nestjs/config'; -import { ImageModule } from 'src/image/image.module'; import { FcmModule } from 'src/global/service/fcm.module'; +import { ImageModule } from 'src/image/image.module'; import { PrismaModule } from 'src/prisma/prisma.module'; +import { TagModule } from 'src/tag/tag.module'; +import { UserModule } from 'src/user/user.module'; +import { NoticeController } from './notice.controller'; import { NoticeRepository } from './notice.repository'; +import { NoticeService } from './notice.service'; @Module({ - imports: [ConfigModule, UserModule, ImageModule, FcmModule, PrismaModule], + imports: [ + ConfigModule, + UserModule, + ImageModule, + FcmModule, + PrismaModule, + HttpModule, + TagModule, + UserModule, + ], controllers: [NoticeController], providers: [NoticeService, NoticeRepository], }) diff --git a/src/notice/notice.repository.ts b/src/notice/notice.repository.ts index 6ed2a9e..c4ed7b9 100644 --- a/src/notice/notice.repository.ts +++ b/src/notice/notice.repository.ts @@ -199,6 +199,7 @@ export class NoticeRepository { async createNotice( { title, body, deadline, tags, images }: CreateNoticeDto, userUuid: string, + createdAt?: Date, ) { const findedTags = await this.prismaService.tag.findMany({ where: { @@ -236,6 +237,7 @@ export class NoticeRepository { url: image, })), }, + createdAt, }, }) .catch((err) => { diff --git a/src/notice/notice.service.ts b/src/notice/notice.service.ts index 478ef33..d269683 100644 --- a/src/notice/notice.service.ts +++ b/src/notice/notice.service.ts @@ -1,10 +1,24 @@ +import { HttpService } from '@nestjs/axios'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Cron } from '@nestjs/schedule'; +import cheerio from 'cheerio'; import dayjs from 'dayjs'; import { htmlToText } from 'html-to-text'; +import { + catchError, + firstValueFrom, + from, + map, + takeWhile, + throwError, + timeout, + toArray, +} from 'rxjs'; import { FcmService } from 'src/global/service/fcm.service'; import { ImageService } from 'src/image/image.service'; +import { TagService } from 'src/tag/tag.service'; +import { UserService } from 'src/user/user.service'; import { AdditionalNoticeDto } from './dto/additionalNotice.dto'; import { CreateNoticeDto } from './dto/createNotice.dto'; import { ForeignContentDto } from './dto/foreignContent.dto'; @@ -18,6 +32,9 @@ export class NoticeService { private readonly noticeRepository: NoticeRepository, private readonly imageService: ImageService, private readonly fcmService: FcmService, + private readonly httpService: HttpService, + private readonly tagService: TagService, + private readonly userService: UserService, configService: ConfigService, ) { this.s3Url = `https://s3.${configService.get( @@ -193,4 +210,97 @@ export class NoticeService { ); }); } + + private async getAcademicNoticeList() { + const baseUrl = 'https://www.gist.ac.kr/kr/html/sub05/050209.html'; + const stream = this.httpService.get(baseUrl).pipe( + timeout(10000), + map((res) => res.data), + map((e) => cheerio.load(e)), + catchError(throwError), + ); + const $ = await firstValueFrom(stream); + const notices = $('table > tbody > tr') + .filter((_, e) => e.type === 'tag' && !e.attribs.class.includes('lstNtc')) + .toArray() + .map( + (e) => + e.type === 'tag' && { + id: Number.parseInt($(e).find('td').first().text().trim()), + title: $(e).find('td').eq(2).text().trim(), + link: `${baseUrl}${$(e).find('td').eq(2).find('a').attr('href')}`, + author: $(e).find('td').eq(3).text().trim(), + category: $(e).find('td').eq(1).text().trim(), + createdAt: $(e).find('td').eq(5).text().trim(), + }, + ); + return notices; + } + + private async getAcademicNotice(link: string) { + const baseUrl = 'https://www.gist.ac.kr/kr/html/sub05/050209.html'; + const stream = this.httpService.get(link).pipe( + timeout(10000), + map((res) => res.data), + map((e) => cheerio.load(e)), + catchError(throwError), + ); + const $ = await firstValueFrom(stream); + const files = $('.bd_detail_file > ul > li > a') + .toArray() + .map((e) => ({ + href: `${baseUrl}${$(e).attr('href')}`, + name: $(e).text().trim(), + type: $(e).attr('class') as + | 'doc' + | 'hwp' + | 'pdf' + | 'imgs' + | 'xls' + | 'etc', + })); + const content = $('.bd_detail_content').html().trim(); + return { files, content }; + } + + @Cron('0 */30 * * *') + async crawlAcademicNotice() { + const notices = await this.getAcademicNoticeList(); + const recentNotice = await this.noticeRepository.getNoticeList({ + limit: 1, + orderBy: 'recent', + tags: ['academic'], + }); + const noticesToCreate$ = from(notices).pipe( + takeWhile((n) => n.title !== recentNotice[0].contents[0].title), + toArray(), + map((n) => n.reverse()), + ); + const noticesToCreate = await firstValueFrom(noticesToCreate$); + for (const noticeMetadata of noticesToCreate) { + const notice = await this.getAcademicNotice(noticeMetadata.link); + const filesList = notice.files + .map((file) => `
  • ${file.name}
  • `) + .join(''); + const filesBody = ``; + const body = `${notice.files.length ? filesBody : ''}${notice.content}`; + const tags = await this.tagService.findOrCreateTags([ + 'academic', + noticeMetadata.category, + ]); + const user = await this.userService.addTempUser( + `${noticeMetadata.author} (${noticeMetadata.category})`, + ); + await this.noticeRepository.createNotice( + { + title: noticeMetadata.title, + body, + images: [], + tags: tags.map(({ id }) => id), + }, + user.uuid, + dayjs(noticeMetadata.createdAt).tz('Asia/Seoul').toDate(), + ); + } + } } diff --git a/src/tag/tag.module.ts b/src/tag/tag.module.ts index 61683ea..a8c2661 100644 --- a/src/tag/tag.module.ts +++ b/src/tag/tag.module.ts @@ -1,12 +1,13 @@ import { Module } from '@nestjs/common'; -import { TagController } from './tag.controller'; -import { TagService } from './tag.service'; import { PrismaModule } from 'src/prisma/prisma.module'; +import { TagController } from './tag.controller'; import { TagRepository } from './tag.repository'; +import { TagService } from './tag.service'; @Module({ imports: [PrismaModule], controllers: [TagController], providers: [TagService, TagRepository], + exports: [TagService], }) export class TagModule {} diff --git a/src/tag/tag.repository.ts b/src/tag/tag.repository.ts index 318f97e..87e95b7 100644 --- a/src/tag/tag.repository.ts +++ b/src/tag/tag.repository.ts @@ -6,9 +6,9 @@ import { NotFoundException, } from '@nestjs/common'; import { Tag } from '@prisma/client'; +import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'; import { PrismaService } from 'src/prisma/prisma.service'; import { GetTagDto } from './dto/getTag.dto'; -import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'; @Injectable() export class TagRepository { @@ -79,4 +79,24 @@ export class TagRepository { throw new InternalServerErrorException('database error'); }); } + + async findOrCreateTags(tags: string[]): Promise { + await this.prismaSerice.tag + .createMany({ + data: tags.map((name) => ({ name })), + skipDuplicates: true, + }) + .catch((err) => { + this.logger.error('findOrCreateTags'); + this.logger.debug(err); + throw new InternalServerErrorException('database error'); + }); + return this.prismaSerice.tag.findMany({ + where: { + name: { + in: tags, + }, + }, + }); + } } diff --git a/src/tag/tag.service.ts b/src/tag/tag.service.ts index 9e36b8f..49dfc21 100644 --- a/src/tag/tag.service.ts +++ b/src/tag/tag.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; -import { CreateTagDto } from './dto/createTag.dto'; import { Tag } from '@prisma/client'; +import { CreateTagDto } from './dto/createTag.dto'; import { GetTagDto } from './dto/getTag.dto'; import { TagRepository } from './tag.repository'; @@ -27,4 +27,8 @@ export class TagService { async deleteTag(id: number): Promise { await this.tagRepository.deleteTag(id); } + + async findOrCreateTags(tags: string[]): Promise { + return this.tagRepository.findOrCreateTags(tags); + } } diff --git a/src/user/user.module.ts b/src/user/user.module.ts index b5c51de..c48ef03 100644 --- a/src/user/user.module.ts +++ b/src/user/user.module.ts @@ -1,12 +1,12 @@ -import { Module } from '@nestjs/common'; -import { UserController } from './user.controller'; -import { UserService } from './user.service'; import { HttpModule } from '@nestjs/axios'; +import { Module } from '@nestjs/common'; +import { PrismaModule } from 'src/prisma/prisma.module'; +import { AnonymousStrategy } from './guard/anonymous.strategy'; import { IdPGuard } from './guard/idp.guard'; import { IdPStrategy } from './guard/idp.strategy'; -import { AnonymousStrategy } from './guard/anonymous.strategy'; import { IdpOptionalStrategy } from './guard/idpOptional.strategy'; -import { PrismaModule } from 'src/prisma/prisma.module'; +import { UserController } from './user.controller'; +import { UserService } from './user.service'; @Module({ imports: [HttpModule, PrismaModule], @@ -18,6 +18,6 @@ import { PrismaModule } from 'src/prisma/prisma.module'; AnonymousStrategy, ], controllers: [UserController], - exports: [IdPGuard], + exports: [IdPGuard, UserService], }) export class UserModule {} diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 5acc1cc..459a94a 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -7,6 +7,7 @@ import { import { ConfigService } from '@nestjs/config'; import { User } from '@prisma/client'; import { AxiosError } from 'axios'; +import crypto from 'crypto'; import { catchError, firstValueFrom } from 'rxjs'; import { PrismaService } from 'src/prisma/prisma.service'; import { LoginDto } from './dto/login.dto'; @@ -206,4 +207,20 @@ export class UserService { }, }); } + + async addTempUser(name: string) { + const user = await this.prismaService.user.findFirst({ + where: { name }, + }); + if (user) { + return user; + } + return this.prismaService.user.create({ + data: { + uuid: crypto.randomUUID(), + name, + consent: false, + }, + }); + } }