diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml new file mode 100644 index 00000000..c8eb5c7d --- /dev/null +++ b/.github/workflows/dev.yml @@ -0,0 +1,34 @@ +name: Deploy Backend in Development Enviroment + +on: + push: + branches: [develop] + +jobs: + build: + runs-on: dev + + steps: + - uses: actions/checkout@v4 + + - name: Create .env file + run: | + touch .env + echo TZ=${{ secrets.TZ }} >> .env + echo DB_HOST=${{ secrets.DB_HOST }} >> .env + echo DB_PORT=${{ secrets.DB_PORT }} >> .env + echo DB_USER=${{ secrets.DB_USER }} >> .env + echo DB_PASSWORD=${{ secrets.DB_PASSWORD }} >> .env + echo DB_DATABASE_NAME=${{ secrets.DEV_DB_DATABASE_NAME }} >> .env + echo DATABASE_URL=postgresql://${{ secrets.DB_USER }}:${{ secrets.DB_PASSWORD }}@${{ secrets.DB_HOST }}:${{ secrets.DB_PORT }}/${{ secrets.DEV_DB_DATABASE_NAME }}?schema=public >> .env + echo SECRET_KEY=${{ secrets.SECRET_KEY }} >> .env + echo HOST=${{ secrets.HOST }} >> .env + echo PORT=${{ secrets.PORT }} >> .env + echo SERVER_USER_PASSWORD=${{ secrets.SERVER_USER_PASSWORD }} >> .env + cat .env + + - name: Remove old docker image + run: echo ${{ secrets.SERVER_USER_PASSWORD }} | sudo -S docker compose down --rmi all + + - name: Create new docker image + run: echo ${{ secrets.SERVER_USER_PASSWORD }} | sudo -S docker compose up -d --force-recreate diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml new file mode 100644 index 00000000..56b875d3 --- /dev/null +++ b/.github/workflows/staging.yml @@ -0,0 +1,34 @@ +name: Deploy Backend in Development Enviroment + +on: + push: + branches: [staging] + +jobs: + build: + runs-on: stg + + steps: + - uses: actions/checkout@v4 + + - name: Create .env file + run: | + touch .env + echo TZ=${{ secrets.TZ }} >> .env + echo DB_HOST=${{ secrets.DB_HOST }} >> .env + echo DB_PORT=${{ secrets.DB_PORT }} >> .env + echo DB_USER=${{ secrets.DB_USER }} >> .env + echo DB_PASSWORD=${{ secrets.DB_PASSWORD }} >> .env + echo DB_DATABASE_NAME=${{ secrets.STG_DB_DATABASE_NAME }} >> .env + echo DATABASE_URL=postgresql://${{ secrets.DB_USER }}:${{ secrets.DB_PASSWORD }}@${{ secrets.DB_HOST }}:${{ secrets.DB_PORT }}/${{ secrets.STG_DB_DATABASE_NAME }}?schema=public >> .env + echo SECRET_KEY=${{ secrets.SECRET_KEY }} >> .env + echo HOST=${{ secrets.HOST }} >> .env + echo PORT=${{ secrets.PORT }} >> .env + echo SERVER_USER_PASSWORD=${{ secrets.SERVER_USER_PASSWORD }} >> .env + cat .env + + - name: Remove old docker image + run: echo ${{ secrets.SERVER_USER_PASSWORD }} | sudo -S docker compose down --rmi all + + - name: Create new docker image + run: echo ${{ secrets.SERVER_USER_PASSWORD }} | sudo -S docker compose up -d --force-recreate diff --git a/prisma/migrations/20240516212629_/migration.sql b/prisma/migrations/20240516212629_/migration.sql new file mode 100644 index 00000000..7f623b3f --- /dev/null +++ b/prisma/migrations/20240516212629_/migration.sql @@ -0,0 +1,14 @@ +-- CreateTable +CREATE TABLE "supporters" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "image_url" TEXT NOT NULL, + "link" TEXT NOT NULL, + "created_at" VARCHAR(32) NOT NULL, + "updated_at" VARCHAR(32), + + CONSTRAINT "supporters_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "supporters_name_key" ON "supporters"("name"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a43b535a..7c726855 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -133,3 +133,14 @@ model Partners { @@map("partners") } + +model Supporters { + id String @id @default(uuid()) + name String @unique + imageUrl String @map("image_url") + link String + createdAt String @map("created_at") @db.VarChar(32) + updatedAt String? @map("updated_at") @db.VarChar(32) + + @@map("supporters") +} diff --git a/src/app.module.ts b/src/app.module.ts index c08ea99f..82663d34 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -12,6 +12,7 @@ import { SupplyCategoriesModule } from './supply-categories/supply-categories.mo import { ShelterManagersModule } from './shelter-managers/shelter-managers.module'; import { ShelterSupplyModule } from './shelter-supply/shelter-supply.module'; import { PartnersModule } from './partners/partners.module'; +import { SupportersModule } from './supporters/supporters.module'; @Module({ imports: [ @@ -24,6 +25,7 @@ import { PartnersModule } from './partners/partners.module'; ShelterManagersModule, ShelterSupplyModule, PartnersModule, + SupportersModule, ], controllers: [], providers: [ diff --git a/src/partners/partners.controller.ts b/src/partners/partners.controller.ts index f1829171..b2f38edd 100644 --- a/src/partners/partners.controller.ts +++ b/src/partners/partners.controller.ts @@ -1,6 +1,15 @@ -import { Controller, Get, HttpException, Logger } from '@nestjs/common'; +import { + Body, + Controller, + Get, + HttpException, + Logger, + Post, + UseGuards, +} from '@nestjs/common'; import { PartnersService } from './partners.service'; import { ServerResponse } from '../utils'; +import { AdminGuard } from '@/guards/admin.guard'; @Controller('partners') export class PartnersController { @@ -18,4 +27,16 @@ export class PartnersController { throw new HttpException(err?.code ?? err?.name ?? `${err}`, 400); } } + + @Post('') + @UseGuards(AdminGuard) + async store(@Body() body) { + try { + await this.partnersService.store(body); + return new ServerResponse(200, 'Successfully created partner'); + } catch (err: any) { + this.logger.error(`Failed to create partner: ${err}`); + throw new HttpException(err?.code ?? err?.name ?? `${err}`, 400); + } + } } diff --git a/src/partners/partners.service.ts b/src/partners/partners.service.ts index 6b3f6c8a..05f2628f 100644 --- a/src/partners/partners.service.ts +++ b/src/partners/partners.service.ts @@ -2,6 +2,8 @@ import { Injectable } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { PrismaService } from '../prisma/prisma.service'; +import { z } from 'zod'; +import { CreatePartnerSchema } from './types'; @ApiTags('Parceiros') @Injectable() @@ -11,4 +13,11 @@ export class PartnersService { async index() { return await this.prismaService.partners.findMany({}); } + + async store(body: z.infer) { + const payload = CreatePartnerSchema.parse(body); + await this.prismaService.partners.create({ + data: { ...payload, createdAt: new Date().toISOString() }, + }); + } } diff --git a/src/partners/types.ts b/src/partners/types.ts new file mode 100644 index 00000000..8844bbc5 --- /dev/null +++ b/src/partners/types.ts @@ -0,0 +1,17 @@ +import z from 'zod'; + +const PartnerSchema = z.object({ + id: z.string(), + name: z.string(), + link: z.string(), + createdAt: z.string(), + updatedAt: z.string().nullable().optional(), +}); + +const CreatePartnerSchema = PartnerSchema.omit({ + id: true, + createdAt: true, + updatedAt: true, +}); + +export { PartnerSchema, CreatePartnerSchema }; diff --git a/src/supporters/supporters.controller.spec.ts b/src/supporters/supporters.controller.spec.ts new file mode 100644 index 00000000..8d281a47 --- /dev/null +++ b/src/supporters/supporters.controller.spec.ts @@ -0,0 +1,30 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { SupportersController } from './supporters.controller'; +import { PrismaService } from '../prisma/prisma.service'; +import { SupportersService } from './supporters.service'; + +describe('SupportersController', () => { + let controller: SupportersController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [SupportersController], + providers: [SupportersService], + }) + .useMocker((token) => { + if (token === PrismaService) { + return { + supplyCategory: { findMany: jest.fn().mockResolvedValue(0) }, + }; + } + }) + .compile(); + + controller = module.get(SupportersController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/supporters/supporters.controller.ts b/src/supporters/supporters.controller.ts new file mode 100644 index 00000000..81a27504 --- /dev/null +++ b/src/supporters/supporters.controller.ts @@ -0,0 +1,43 @@ +import { + Body, + Controller, + Get, + HttpException, + Logger, + Post, + UseGuards, +} from '@nestjs/common'; + +import { SupportersService } from './supporters.service'; +import { ServerResponse } from '../utils'; +import { AdminGuard } from '@/guards/admin.guard'; + +@Controller('supporters') +export class SupportersController { + private logger = new Logger(SupportersController.name); + + constructor(private readonly supportersService: SupportersService) {} + + @Get('') + async index() { + try { + const data = await this.supportersService.index(); + return new ServerResponse(200, 'Successfully get supporters', data); + } catch (err: any) { + this.logger.error(`Failed to get supporters: ${err}`); + throw new HttpException(err?.code ?? err?.name ?? `${err}`, 400); + } + } + + @Post('') + @UseGuards(AdminGuard) + async store(@Body() body) { + try { + await this.supportersService.store(body); + return new ServerResponse(200, 'Successfully created supporter'); + } catch (err: any) { + this.logger.error(`Failed to create supporter: ${err}`); + throw new HttpException(err?.code ?? err?.name ?? `${err}`, 400); + } + } +} diff --git a/src/supporters/supporters.module.ts b/src/supporters/supporters.module.ts new file mode 100644 index 00000000..6a449002 --- /dev/null +++ b/src/supporters/supporters.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; + +import { SupportersService } from './supporters.service'; +import { SupportersController } from './supporters.controller'; +import { PrismaModule } from '../prisma/prisma.module'; + +@Module({ + imports: [PrismaModule], + providers: [SupportersService], + controllers: [SupportersController], +}) +export class SupportersModule {} diff --git a/src/supporters/supporters.service.spec.ts b/src/supporters/supporters.service.spec.ts new file mode 100644 index 00000000..e2e9cc81 --- /dev/null +++ b/src/supporters/supporters.service.spec.ts @@ -0,0 +1,28 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { SupportersService } from './supporters.service'; +import { PrismaService } from '../prisma/prisma.service'; + +describe('SupportersService', () => { + let service: SupportersService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [SupportersService], + }) + .useMocker((token) => { + if (token === PrismaService) { + return { + supplyCategory: { findMany: jest.fn().mockResolvedValue(0) }, + }; + } + }) + .compile(); + + service = module.get(SupportersService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/supporters/supporters.service.ts b/src/supporters/supporters.service.ts new file mode 100644 index 00000000..bc488ecc --- /dev/null +++ b/src/supporters/supporters.service.ts @@ -0,0 +1,21 @@ +import { z } from 'zod'; +import { Injectable } from '@nestjs/common'; + +import { PrismaService } from '../prisma/prisma.service'; +import { CreateSupporterSchema } from './types'; + +@Injectable() +export class SupportersService { + constructor(private readonly prismaService: PrismaService) {} + + async index() { + return await this.prismaService.supporters.findMany({}); + } + + async store(body: z.infer) { + const payload = CreateSupporterSchema.parse(body); + await this.prismaService.supporters.create({ + data: { ...payload, createdAt: new Date().toISOString() }, + }); + } +} diff --git a/src/supporters/types.ts b/src/supporters/types.ts new file mode 100644 index 00000000..ae08ce64 --- /dev/null +++ b/src/supporters/types.ts @@ -0,0 +1,18 @@ +import z from 'zod'; + +const SupporterSchema = z.object({ + id: z.string(), + name: z.string(), + imageUrl: z.string(), + link: z.string(), + createdAt: z.string(), + updatedAt: z.string().nullable().optional(), +}); + +const CreateSupporterSchema = SupporterSchema.omit({ + id: true, + createdAt: true, + updatedAt: true, +}); + +export { SupporterSchema, CreateSupporterSchema };