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

Release #359

Merged
merged 3 commits into from
Apr 12, 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
26 changes: 4 additions & 22 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-push-prod:
if: github.ref != 'refs/heads/staging'
Expand All @@ -22,7 +22,7 @@ jobs:
contents: read
packages: write

environment:
environment:
name: Production
url: https://api.veganify.app

Expand Down Expand Up @@ -52,35 +52,17 @@ jobs:
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

deploy-prod:
needs: build-and-push-prod
if: success() && github.ref != 'refs/heads/staging'
runs-on: ubuntu-latest

steps:
- name: SSH into the server and run Docker Compose
uses: appleboy/[email protected]
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd vegancheck
docker-compose stop vc-backend
docker-compose pull vc-backend
docker-compose up -d vc-backend

build-and-push-staging:
if: github.ref == 'refs/heads/staging'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

environment:
environment:
name: Staging
url: https://staging.api.veganify.app

steps:
- name: Checkout repository
uses: actions/checkout@v3
Expand Down
104 changes: 104 additions & 0 deletions OpenAPI.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,110 @@ paths:
description: Internal Server Error.
tags:
- Peta
/health:
get:
operationId: HealthController_check
parameters: []
responses:
'200':
description: The Health Check is successful
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: ok
info:
type: object
example: &ref_0
database: &ref_1
status: up
additionalProperties:
type: object
required:
- status
properties:
status:
type: string
additionalProperties: true
nullable: true
error:
type: object
example: {}
additionalProperties:
type: object
required:
- status
properties:
status:
type: string
additionalProperties: true
nullable: true
details:
type: object
example: *ref_0
additionalProperties:
type: object
required:
- status
properties:
status:
type: string
additionalProperties: true
'503':
description: The Health Check is not successful
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: error
info:
type: object
example: *ref_0
additionalProperties:
type: object
required:
- status
properties:
status:
type: string
additionalProperties: true
nullable: true
error:
type: object
example:
redis: &ref_2
status: down
message: Could not connect
additionalProperties:
type: object
required:
- status
properties:
status:
type: string
additionalProperties: true
nullable: true
details:
type: object
example:
database: *ref_1
redis: *ref_2
additionalProperties:
type: object
required:
- status
properties:
status:
type: string
additionalProperties: true
tags:
- Health
info:
title: Veganify API
description: API for checking if products and ingredients are vegan
Expand Down
8 changes: 7 additions & 1 deletion src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,28 @@ import { ProductService } from "./product.service";
import { PetaController } from "./peta.controller";
import { RateLimiterMiddleware } from "./rate-limiter.middleware";
import { LoggerModule } from "nestjs-pino";
import { HealthController } from "./health/health.controller";
import { TerminusModule } from "@nestjs/terminus";
import { HealthModule } from "./health/health.module";

@Module({
imports: [
HealthModule,
HttpModule,
ConfigModule.forRoot(),
LoggerModule.forRoot({
pinoHttp: {
level: "warn",
level: "info",
},
}),
TerminusModule,
],
controllers: [
GradesController,
IngredientsController,
ProductController,
PetaController,
HealthController,
ErrorsController,
],
providers: [GradesService, ProductService, ConfigService, TranslationService],
Expand Down
117 changes: 83 additions & 34 deletions src/errors.controller.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,122 @@
import { Controller, Get, Post, Options, All, Res, Req, HttpException, HttpStatus } from '@nestjs/common';
import fs from 'fs';
import pino from 'pino';
import { ApiExcludeController } from '@nestjs/swagger';
import {
Controller,
Get,
Post,
Options,
All,
Res,
Req,
HttpException,
HttpStatus,
} from "@nestjs/common";
import fs from "fs";
import pino from "pino";
import { ApiExcludeController } from "@nestjs/swagger";

const logger = pino({ level: process.env.LOG_LEVEL ?? 'warn' });
const logger = pino({ level: process.env.LOG_LEVEL ?? "warn" });

@Controller()
@ApiExcludeController()
export class ErrorsController {

@Get(['/OpenAPI.yaml', '/OpenAPI.yml', '/openapi', '/spec', '/specification', '/v0/OpenAPI.yaml', '/v0/OpenAPI.yml', '/v0/openapi', '/v0/spec', '/v0/specification'])
@Get([
"/OpenAPI.yaml",
"/OpenAPI.yml",
"/openapi",
"/spec",
"/specification",
"/v0/OpenAPI.yaml",
"/v0/OpenAPI.yml",
"/v0/openapi",
"/v0/spec",
"/v0/specification",
])
getOpenApi(@Res() res: any): void {
fs.readFile('./OpenAPI.yaml', 'utf8', (err: NodeJS.ErrnoException | null, contents: string) => {
if (err != null) {
logger.error('Error reading file:', err);
throw new HttpException('Error reading OpenAPI specification', HttpStatus.INTERNAL_SERVER_ERROR);
fs.readFile(
"./OpenAPI.yaml",
"utf8",
(err: NodeJS.ErrnoException | null, contents: string) => {
if (err != null) {
logger.error("Error reading file:", err);
throw new HttpException(
"Error reading OpenAPI specification",
HttpStatus.INTERNAL_SERVER_ERROR
);
Dismissed Show dismissed Hide dismissed
}
res.setHeader("Content-Type", "text/yaml");
res.send(contents);
}
res.setHeader('Content-Type', 'text/yaml');
res.send(contents);
});
);
}

@Get('/.well-known/security.txt')
@Get("/.well-known/security.txt")
getSecurityTxt(@Res() res: any): void {
fs.readFile('./.well-known/security.txt', 'utf8', (err: NodeJS.ErrnoException | null, contents: string) => {
if (err != null) {
logger.warn(err);
fs.readFile(
"./.well-known/security.txt",
"utf8",
(err: NodeJS.ErrnoException | null, contents: string) => {
if (err != null) {
logger.warn(err);
}
res.setHeader("Content-Type", "text/plain");
res.send(contents);
}
res.setHeader('Content-Type', 'text/plain');
res.send(contents);
});
);
}

@Post('*')
@Post("*")
handlePostWildcard(@Req() req: any, @Res() res: any): void {
this.handleWildcard(req, res, 404, 'Not found', 'Try v0/ingredients (GET) or v0/product');
this.handleWildcard(
req,
res,
404,
"Not found",
"Try v0/ingredients (GET) or v0/product"
);
}

@Get('*')
@Get("*")
handleGetWildcard(@Req() req: any, @Res() res: any): void {
this.handleWildcard(req, res, 404, 'Not found', 'Try v0/ingredients or v0/product (POST)');
this.handleWildcard(
req,
res,
404,
"Not found",
"Try v0/ingredients or v0/product (POST)"
);
}

@All(['PUT', 'DELETE', 'PATCH', 'PROPFIND'])
@All(["PUT", "DELETE", "PATCH", "PROPFIND"])
handleMethodNotAllowed(@Req() req: any, @Res() res: any): void {
this.handleWildcard(req, res, 405, 'Method not allowed', '');
this.handleWildcard(req, res, 405, "Method not allowed", "");
}

@Options('*')
@Options("*")
handleOptions(@Res() res: any): void {
const result = {
GET: {
paths: ['/v0/ingredients/:ingredientslist', 'v0/peta/crueltyfree']
paths: ["/v0/ingredients/:ingredientslist", "v0/peta/crueltyfree"],
},
POST: { paths: '/v0/product/:barcode' }
POST: { paths: "/v0/product/:barcode" },
};
res.status(200).json(result);
}

private handleWildcard(req: any, res: any, status: number, code: string, message: string): void {
const fullUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
private handleWildcard(
req: any,
res: any,
status: number,
code: string,
message: string
): void {
const fullUrl = `${req.protocol}://${req.get("host")}${req.originalUrl}`;
const result = {
status: status,
code: code,
message: message,
debug: {
method: req.method,
uri: fullUrl
}
uri: fullUrl,
},
};
res.status(status).json(result);
}
Expand Down
19 changes: 19 additions & 0 deletions src/health/health.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Controller, Get } from "@nestjs/common";
import { HealthCheckService, HealthCheck } from "@nestjs/terminus";
import { HealthService } from "./health.service";
import { ApiTags } from "@nestjs/swagger";

@ApiTags("Health")
@Controller("health")
export class HealthController {
constructor(
private healthService: HealthService,
private health: HealthCheckService
) {}

@Get("/")
@HealthCheck()
check() {
return this.healthService.check();
}
}
12 changes: 12 additions & 0 deletions src/health/health.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from "@nestjs/common";
import { TerminusModule } from "@nestjs/terminus";
import { HealthController } from "./health.controller";
import { HealthService } from "./health.service";

@Module({
imports: [TerminusModule],
controllers: [HealthController],
providers: [HealthService],
exports: [HealthService],
})
export class HealthModule {}
Loading
Loading