├── .dockerignore ├── .prettierrc ├── tsconfig.build.json ├── Dockerfile ├── nest-cli.json ├── src ├── app.service.ts ├── app.controller.ts ├── main.ts ├── random-quote │ ├── quote.entity.ts │ ├── random-quote.module.ts │ ├── random-quote.service.spec.ts │ ├── random-quote.controller.spec.ts │ ├── random-quote.controller.ts │ └── random-quote.service.ts ├── app.controller.spec.ts └── app.module.ts ├── test ├── jest-e2e.json └── app.e2e-spec.ts ├── .gitignore ├── tsconfig.json ├── .eslintrc.js ├── README.md ├── en └── README.md ├── .github └── workflows │ └── deploy.yml └── package.json /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | Dockerfile 3 | node_modules 4 | dist -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | RUN mkdir -p /var/app/happiness-backend 3 | WORKDIR /var/app/happiness-backend 4 | COPY . . 5 | RUN npm install 6 | RUN npm run build 7 | EXPOSE 3010 8 | CMD [ "node", "dist/main.js" ] -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World! its your docker container with auto push deploy2'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | 4 | async function bootstrap() { 5 | const app = await NestFactory.create(AppModule); 6 | 7 | app.enableCors({ 8 | origin: '*', // Allow requests from all origins, you can restrict to specific origins if needed 9 | methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', 10 | allowedHeaders: 'Content-Type, Accept', 11 | }); 12 | 13 | await app.listen(3010); 14 | } 15 | bootstrap(); 16 | -------------------------------------------------------------------------------- /src/random-quote/quote.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; 2 | import { IsNotEmpty } from 'class-validator'; 3 | 4 | @Entity() 5 | export class Quote { 6 | @PrimaryGeneratedColumn() 7 | id: number; 8 | 9 | @Column() 10 | @IsNotEmpty() 11 | content: string; 12 | 13 | @Column() 14 | @IsNotEmpty() 15 | author: string; 16 | 17 | @Column({ nullable: true }) 18 | description: string; 19 | 20 | @Column({ nullable: true }) 21 | link: string; 22 | } 23 | -------------------------------------------------------------------------------- /src/random-quote/random-quote.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { RandomQuoteController } from './random-quote.controller'; 3 | import { RandomQuoteService } from './random-quote.service'; 4 | import { TypeOrmModule } from '@nestjs/typeorm'; 5 | import { Quote } from './quote.entity'; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([Quote])], 9 | controllers: [RandomQuoteController], 10 | providers: [RandomQuoteService], 11 | }) 12 | export class RandomQuoteModule {} 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json -------------------------------------------------------------------------------- /src/random-quote/random-quote.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { RandomQuoteService } from './random-quote.service'; 3 | 4 | describe('RandomQuoteService', () => { 5 | let service: RandomQuoteService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [RandomQuoteService], 10 | }).compile(); 11 | 12 | service = module.get(RandomQuoteService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/random-quote/random-quote.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { RandomQuoteController } from './random-quote.controller'; 3 | 4 | describe('RandomQuoteController', () => { 5 | let controller: RandomQuoteController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [RandomQuoteController], 10 | }).compile(); 11 | 12 | controller = module.get(RandomQuoteController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2021", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | import { RandomQuoteController } from './random-quote/random-quote.controller'; 5 | import { RandomQuoteService } from './random-quote/random-quote.service'; 6 | import { RandomQuoteModule } from './random-quote/random-quote.module'; 7 | import { TypeOrmModule } from '@nestjs/typeorm'; 8 | import { Quote } from './random-quote/quote.entity'; 9 | 10 | @Module({ 11 | imports: [ 12 | TypeOrmModule.forRoot({ 13 | type: 'postgres', 14 | host: 'happiness-postgres', 15 | port: 5432, 16 | username: 'postgres', // Replace with your PostgreSQL username 17 | password: 'wai123!', // Replace with your PostgreSQL password 18 | database: 'happiness-postgres', // Replace with your database name 19 | entities: [Quote], 20 | synchronize: false, 21 | }), 22 | RandomQuoteModule 23 | ], 24 | controllers: [AppController], 25 | providers: [AppService], 26 | }) 27 | export class AppModule {} 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 행복 명언 API 3 | 4 | 이 프로젝트는 한국어 행복 명언을 제공하는 API입니다. 카카오헬스케어의 정재훈님의 글에 감명을 받아 프로젝트가 시작되었습니다. 5 | API를 통해 무작위로 행복 명언과 그에 대한 영어 번역을 얻을 수 있습니다. 6 | 7 | ## 기부 8 | [!["Buy Me A Coffee"](https://cdn.buymeacoffee.com/buttons/default-yellow.png)](https://www.buymeacoffee.com/sobabear) 9 | 10 | 11 | 12 | ## 기능 13 | 14 | - 한국어로 된 무작위 행복 명언 제공 15 | - 명언의 영어 번역과 저자 정보 제공 16 | - 하루 마다 api content가 바뀝니다. 17 | 18 | 19 | ## 사용 방법 20 | 21 | API는 간단하게 무작위 행복 명언을 가져오는 방법을 제공합니다. 다음 엔드포인트를 이용해 시도할 수 있습니다 22 | 23 | ### 무작위 명언 엔드포인트 24 | 25 | ```bash 26 | GET https://api.sobabear.com/happiness/random-quote 27 | ``` 28 | 29 | 30 | ### 예시 응답 31 | 엔드포인트를 호출하면, 아래와 같은 JSON 응답을 받게 됩니다: 32 | ``` 33 | { 34 | "message": "Quote fetched successfully", 35 | "statusCode": 200, 36 | "data": { 37 | "id": 60, 38 | "content": "행복은 내면에서 오지 않고 사이에서 온다. Happiness comes not from within, but from getting the right relationship between yourself and something larger than yourself.", 39 | "author": "Jonathan Haidt, 사회심리학자", 40 | "description": null, 41 | "link": null 42 | } 43 | } 44 | ``` 45 | 46 | ## 사용 사이트 47 | [하루 행복: 크롬 웹 익스텐션](https://chromewebstore.google.com/detail/%ED%95%98%EB%A3%A8-%ED%96%89%EB%B3%B5/mlmeakkbggjjgaefcjpajfdmfhlmldin) 48 | -------------------------------------------------------------------------------- /en/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Korean Happiness Quotes API 3 | 4 | This project provides an API for Korean happiness quotes. Through the API, you can get random happiness quotes and their English translations. 5 | 6 | ## Buy Me A Coffee 7 | [!["Buy Me A Coffee"](https://cdn.buymeacoffee.com/buttons/default-yellow.png)](https://www.buymeacoffee.com/sobabear) 8 | 9 | 10 | ## Features 11 | 12 | - Provides random happiness quotes in Korean 13 | - Provides English translations and author information for the quotes 14 | 15 | 16 | ## Usage 17 | 18 | The API provides a simple way to fetch random happiness quotes. You can try it using the following endpoint: 19 | 20 | ### Random Quote Endpoint 21 | 22 | ```bash 23 | GET https://api.sobabear.com/happiness/random-quote 24 | ``` 25 | 26 | 27 | ### Example Response 28 | 29 | When you call the endpoint, you will receive a JSON response like the one below: 30 | 31 | ``` 32 | { 33 | "message": "Quote fetched successfully", 34 | "statusCode": 200, 35 | "data": { 36 | "id": 60, 37 | "content": "행복은 내면에서 오지 않고 사이에서 온다. Happiness comes not from within, but from getting the right relationship between yourself and something larger than yourself.", 38 | "author": "Jonathan Haidt, 사회심리학자", 39 | "description": null, 40 | "link": null 41 | } 42 | } 43 | ``` 44 | 45 | ## Usage Site 46 | [Daily Happiness: Chrome Web Extension](https://chromewebstore.google.com/detail/%ED%95%98%EB%A3%A8-%ED%96%89%EB%B3%B5/mlmeakkbggjjgaefcjpajfdmfhlmldin) 47 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Vultr 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v2 15 | 16 | - name: Set up SSH 17 | uses: webfactory/ssh-agent@v0.5.3 18 | with: 19 | ssh-private-key: ${{ secrets.VULTR_SSH_PRIVATE_KEY }} 20 | 21 | - name: Verify SSH connection 22 | run: ssh -o StrictHostKeyChecking=no root@${{ secrets.VULTR_IP }} "echo 'SSH connection successful'" 23 | 24 | - name: Deploy code to Vultr 25 | run: | 26 | ssh -o StrictHostKeyChecking=no root@${{ secrets.VULTR_IP }} " 27 | if [ -d /root/deploy/happiness-backend ]; then 28 | cd /root/deploy/happiness-backend && git pull; 29 | else 30 | git clone git@github.com:WAI-laboratory/happiness-backend.git /root/deploy/happiness-backend; 31 | fi; 32 | " 33 | 34 | # Add a step to install dependencies and build the project 35 | - name: Install dependencies and build project 36 | run: | 37 | npm install 38 | npm run build 39 | 40 | - name: Build and run Docker container 41 | run: | 42 | ssh -o StrictHostKeyChecking=no root@${{ secrets.VULTR_IP }} " 43 | # Build the image from the source code 44 | docker build -t happiness-backend-image /root/deploy/happiness-backend; 45 | 46 | # Stop and remove the container if it exists 47 | docker container stop happiness-backend || true && docker container rm happiness-backend || true; 48 | 49 | # Run the newly built image 50 | docker container run -d -p 3010:3010 --name happiness-backend --network app-network happiness-backend-image; 51 | " -------------------------------------------------------------------------------- /src/random-quote/random-quote.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, HttpStatus, Delete, Param } from '@nestjs/common'; 2 | import { RandomQuoteService } from './random-quote.service'; 3 | import { Quote } from './quote.entity'; 4 | 5 | @Controller('random-quote') 6 | export class RandomQuoteController { 7 | 8 | constructor(private readonly randomQuoteService: RandomQuoteService) {} 9 | 10 | // Not allowed yet 11 | // @Post() 12 | // async create(@Body() quote: Partial): Promise { 13 | // if (!quote.content || !quote.author) { 14 | // return { 15 | // message: 'Content and author are required.', 16 | // statusCode: HttpStatus.BAD_REQUEST, 17 | // data: null 18 | // }; 19 | // } 20 | // const createdQuote = await this.randomQuoteService.create(quote); 21 | // return { 22 | // message: 'Quote created successfully', 23 | // statusCode: HttpStatus.CREATED, 24 | // data: createdQuote 25 | // }; 26 | // } 27 | 28 | @Get() 29 | async getDailyQuote(): Promise { 30 | const dailyQuote = await this.randomQuoteService.findDailyQuote(); 31 | return { 32 | message: 'Quote fetched successfully', 33 | statusCode: HttpStatus.OK, 34 | data: dailyQuote 35 | }; 36 | } 37 | 38 | // Not allowed yet 39 | // @Get('all-quotes') 40 | // async findAll(): Promise { 41 | // const quotes = await this.randomQuoteService.findAll(); 42 | // return { 43 | // message: 'All quotes fetched successfully', 44 | // statusCode: HttpStatus.OK, 45 | // data: quotes 46 | // }; 47 | // } 48 | // Not allowed yet 49 | // @Delete(':id') 50 | // async deleteQuote(@Param('id') id: number): Promise { 51 | // await this.randomQuoteService.deleteQuoteById(id); 52 | // return { 53 | // message: `Quote with ID ${id} deleted successfully`, 54 | // statusCode: HttpStatus.OK, 55 | // data: null 56 | // }; 57 | // } 58 | } 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "happiness-backend", 3 | "version": "0.0.2", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "build": "nest build", 10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 11 | "start": "nest start", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@nestjs/common": "^10.0.0", 24 | "@nestjs/core": "^10.0.0", 25 | "@nestjs/platform-express": "^10.0.0", 26 | "@nestjs/typeorm": "^10.0.2", 27 | "class-transformer": "^0.5.1", 28 | "class-validator": "^0.14.1", 29 | "pg": "^8.12.0", 30 | "reflect-metadata": "^0.1.13", 31 | "rxjs": "^7.8.1", 32 | "typeorm": "^0.3.20" 33 | }, 34 | "devDependencies": { 35 | "@nestjs/cli": "^10.0.0", 36 | "@nestjs/schematics": "^10.0.0", 37 | "@nestjs/testing": "^10.0.0", 38 | "@types/express": "^4.17.17", 39 | "@types/jest": "^29.5.2", 40 | "@types/node": "^20.3.1", 41 | "@types/supertest": "^6.0.0", 42 | "@typescript-eslint/eslint-plugin": "^6.0.0", 43 | "@typescript-eslint/parser": "^6.0.0", 44 | "eslint": "^8.42.0", 45 | "eslint-config-prettier": "^9.0.0", 46 | "eslint-plugin-prettier": "^5.0.0", 47 | "jest": "^29.5.0", 48 | "prettier": "^3.0.0", 49 | "source-map-support": "^0.5.21", 50 | "supertest": "^6.3.3", 51 | "ts-jest": "^29.1.0", 52 | "ts-loader": "^9.4.3", 53 | "ts-node": "^10.9.1", 54 | "tsconfig-paths": "^4.2.0", 55 | "typescript": "^5.1.3" 56 | }, 57 | "jest": { 58 | "moduleFileExtensions": [ 59 | "js", 60 | "json", 61 | "ts" 62 | ], 63 | "rootDir": "src", 64 | "testRegex": ".*\\.spec\\.ts$", 65 | "transform": { 66 | "^.+\\.(t|j)s$": "ts-jest" 67 | }, 68 | "collectCoverageFrom": [ 69 | "**/*.(t|j)s" 70 | ], 71 | "coverageDirectory": "../coverage", 72 | "testEnvironment": "node" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/random-quote/random-quote.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NotFoundException } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Quote } from './quote.entity'; 4 | import { Repository } from 'typeorm'; 5 | 6 | @Injectable() 7 | export class RandomQuoteService { 8 | 9 | constructor( 10 | @InjectRepository(Quote) 11 | private quotesRepository: Repository, 12 | ) {} 13 | 14 | create(quote: Partial): Promise { 15 | const newQuote = this.quotesRepository.create(quote); 16 | return this.quotesRepository.save(newQuote); 17 | } 18 | async findDailyQuote(): Promise { 19 | // Get the total number of quotes without deleted ones 20 | const count = await this.quotesRepository.count(); 21 | 22 | if (count === 0) { 23 | throw new NotFoundException('No quotes found'); 24 | } 25 | 26 | // Calculate the day of the year (1-365/366) 27 | const today = new Date(); 28 | const start = new Date(today.getFullYear(), 0, 0); 29 | const diff = (today as any) - (start as any); 30 | const oneDay = 1000 * 60 * 60 * 24; 31 | const dayOfYear = Math.floor(diff / oneDay); 32 | 33 | // Use the day of the year modulo total count to calculate the index 34 | const quoteIndex = dayOfYear % count; 35 | 36 | // Fetch a quote using the calculated index with OFFSET and LIMIT, excluding deleted quotes 37 | const quote = await this.quotesRepository 38 | .createQueryBuilder("quote") 39 | .orderBy("quote.id", "ASC") // Or use any other ordering mechanism 40 | .offset(quoteIndex) // Skip to the correct quote 41 | .limit(1) // Only fetch one quote 42 | .getOne(); 43 | 44 | if (!quote) { 45 | throw new NotFoundException('Quote not found'); 46 | } 47 | 48 | return quote; 49 | } 50 | 51 | findAll(): Promise { 52 | return this.quotesRepository.find(); 53 | } 54 | 55 | // New method to delete a quote by ID 56 | async deleteQuoteById(id: number): Promise { 57 | const result = await this.quotesRepository.delete(id); 58 | if (result.affected === 0) { 59 | throw new NotFoundException(`Quote with ID ${id} not found`); 60 | } 61 | } 62 | } 63 | --------------------------------------------------------------------------------