diff --git a/packages/api-gateway/src/app.module.ts b/packages/api-gateway/src/app.module.ts index d475cce1..41982da8 100644 --- a/packages/api-gateway/src/app.module.ts +++ b/packages/api-gateway/src/app.module.ts @@ -11,6 +11,7 @@ import { FileBucketModule } from './modules/file_bucket/file_bucket.module'; import { FileConfigModule } from './modules/file_config/file_config.module'; import { AnalyticsModule } from './modules/analytics/analytics.module'; import { AnalyticsConfigModule } from './modules/analytics_config/analytics_config.module'; +import { CounterModule } from './modules/counter/counter.module'; @Module({ imports: [ @@ -26,6 +27,7 @@ import { AnalyticsConfigModule } from './modules/analytics_config/analytics_conf FileConfigModule, AnalyticsModule, AnalyticsConfigModule, + CounterModule, ], }) export class AppModule {} diff --git a/packages/api-gateway/src/modules/counter/counter.controller.ts b/packages/api-gateway/src/modules/counter/counter.controller.ts new file mode 100644 index 00000000..801cf644 --- /dev/null +++ b/packages/api-gateway/src/modules/counter/counter.controller.ts @@ -0,0 +1,81 @@ +import { + Body, + Controller, + Get, + Inject, + OnModuleInit, + Param, + Post, +} from '@nestjs/common'; +import { ClientGrpc } from '@nestjs/microservices'; +import { + ApiCreatedResponse, + ApiNotFoundResponse, + ApiOkResponse, + ApiOperation, + ApiTags, +} from '@nestjs/swagger'; +import { CounterProto } from 'juno-proto'; +import { lastValueFrom } from 'rxjs'; + +const { COUNTER_SERVICE_NAME } = CounterProto; + +@ApiTags('counter') +@Controller('counter') +export class CounterController implements OnModuleInit { + private counterService: CounterProto.CounterServiceClient; + + constructor( + @Inject(COUNTER_SERVICE_NAME) private counterClient: ClientGrpc, + ) {} + + onModuleInit() { + this.counterService = + this.counterClient.getService( + COUNTER_SERVICE_NAME, + ); + } + + @Post() + @ApiOperation({ summary: 'Create a new counter' }) + @ApiCreatedResponse({ description: 'Counter created' }) + async createCounter( + @Body() body: { id: string; initialValue?: number }, + ): Promise<{ value: number }> { + return lastValueFrom( + this.counterService.createCounter({ + id: body.id, + initialValue: body.initialValue, + }), + ); + } + + @Get(':id') + @ApiOperation({ summary: 'Get counter value' }) + @ApiOkResponse({ description: 'Counter value retrieved' }) + @ApiNotFoundResponse({ description: 'Counter not found' }) + async getCounter(@Param('id') id: string): Promise<{ value: number }> { + return lastValueFrom(this.counterService.getCounter({ id })); + } + + @Post(':id/increment') + @ApiOperation({ summary: 'Increment counter' }) + @ApiOkResponse({ description: 'Counter incremented' }) + async incrementCounter(@Param('id') id: string): Promise<{ value: number }> { + return lastValueFrom(this.counterService.incrementCounter({ id })); + } + + @Post(':id/decrement') + @ApiOperation({ summary: 'Decrement counter' }) + @ApiOkResponse({ description: 'Counter decremented' }) + async decrementCounter(@Param('id') id: string): Promise<{ value: number }> { + return lastValueFrom(this.counterService.decrementCounter({ id })); + } + + @Post(':id/reset') + @ApiOperation({ summary: 'Reset counter to zero' }) + @ApiOkResponse({ description: 'Counter reset' }) + async resetCounter(@Param('id') id: string): Promise<{ value: number }> { + return lastValueFrom(this.counterService.resetCounter({ id })); + } +} diff --git a/packages/api-gateway/src/modules/counter/counter.module.ts b/packages/api-gateway/src/modules/counter/counter.module.ts new file mode 100644 index 00000000..03c9649b --- /dev/null +++ b/packages/api-gateway/src/modules/counter/counter.module.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { join } from 'path'; +import { ClientsModule, Transport } from '@nestjs/microservices'; +import { CounterProto, CounterProtoFile } from 'juno-proto'; +import { CounterController } from './counter.controller'; + +const { COUNTER_SERVICE_NAME, JUNO_COUNTER_PACKAGE_NAME } = CounterProto; + +@Module({ + imports: [ + ConfigModule.forRoot({ + envFilePath: join(__dirname, '../../../../../.env.local'), + }), + ClientsModule.register([ + { + name: COUNTER_SERVICE_NAME, + transport: Transport.GRPC, + options: { + url: process.env.DB_SERVICE_ADDR, + package: JUNO_COUNTER_PACKAGE_NAME, + protoPath: CounterProtoFile, + }, + }, + ]), + ], + controllers: [CounterController], +}) +export class CounterModule {} diff --git a/packages/api-gateway/test/counter.e2e-spec.ts b/packages/api-gateway/test/counter.e2e-spec.ts new file mode 100644 index 00000000..8ea263b0 --- /dev/null +++ b/packages/api-gateway/test/counter.e2e-spec.ts @@ -0,0 +1,155 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { + ClassSerializerInterceptor, + INestApplication, + ValidationPipe, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import * as request from 'supertest'; +import * as GRPC from '@grpc/grpc-js'; +import * as ProtoLoader from '@grpc/proto-loader'; +import { ResetProtoFile } from 'juno-proto'; +import { AppModule } from './../src/app.module'; +import { RpcExceptionFilter } from 'src/rpc_exception_filter'; + +let app: INestApplication; + +jest.setTimeout(15000); + +beforeAll(async () => { + const proto = ProtoLoader.loadSync([ResetProtoFile]) as any; + + const protoGRPC = GRPC.loadPackageDefinition(proto) as any; + const resetClient = new protoGRPC.juno.reset_db.DatabaseReset( + process.env.DB_SERVICE_ADDR, + GRPC.credentials.createInsecure(), + ); + await new Promise((resolve) => { + resetClient.resetDb({}, () => { + resolve(0); + }); + }); +}); + +afterAll((done) => { + app.close(); + done(); +}); + +beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + }), + ); + app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector))); + app.useGlobalFilters(new RpcExceptionFilter()); + + await app.init(); +}); + +describe('Counter Routes (e2e)', () => { + describe('POST /counter', () => { + it('Should successfully create a counter', async () => { + return await request(app.getHttpServer()) + .post('/counter') + .send({ id: 'test-counter', initialValue: 10 }) + .expect(201) + .expect((res) => { + expect(res.body).toHaveProperty('value', 10); + }); + }); + + it('Should create a counter with default value of 0', async () => { + return await request(app.getHttpServer()) + .post('/counter') + .send({ id: 'test-counter-default' }) + .expect(201) + .expect((res) => { + expect(res.body).toHaveProperty('value', 0); + }); + }); + + it('Should return 409 when creating a duplicate counter', async () => { + await request(app.getHttpServer()) + .post('/counter') + .send({ id: 'test-counter-dup', initialValue: 0 }); + + return await request(app.getHttpServer()) + .post('/counter') + .send({ id: 'test-counter-dup', initialValue: 5 }) + .expect(409); + }); + }); + + describe('GET /counter/:id', () => { + it('Should successfully get a counter value', async () => { + await request(app.getHttpServer()) + .post('/counter') + .send({ id: 'test-get-counter', initialValue: 42 }); + + return await request(app.getHttpServer()) + .get('/counter/test-get-counter') + .expect(200) + .expect((res) => { + expect(res.body).toHaveProperty('value', 42); + }); + }); + + it('Should return 404 for a nonexistent counter', async () => { + return await request(app.getHttpServer()) + .get('/counter/nonexistent') + .expect(404); + }); + }); + + describe('POST /counter/:id/increment', () => { + it('Should increment a counter', async () => { + await request(app.getHttpServer()) + .post('/counter') + .send({ id: 'test-inc', initialValue: 0 }); + + return await request(app.getHttpServer()) + .post('/counter/test-inc/increment') + .expect(201) + .expect((res) => { + expect(res.body).toHaveProperty('value', 1); + }); + }); + }); + + describe('POST /counter/:id/decrement', () => { + it('Should decrement a counter', async () => { + await request(app.getHttpServer()) + .post('/counter') + .send({ id: 'test-dec', initialValue: 5 }); + + return await request(app.getHttpServer()) + .post('/counter/test-dec/decrement') + .expect(201) + .expect((res) => { + expect(res.body).toHaveProperty('value', 4); + }); + }); + }); + + describe('POST /counter/:id/reset', () => { + it('Should reset a counter to zero', async () => { + await request(app.getHttpServer()) + .post('/counter') + .send({ id: 'test-reset', initialValue: 99 }); + + return await request(app.getHttpServer()) + .post('/counter/test-reset/reset') + .expect(201) + .expect((res) => { + expect(res.body).toHaveProperty('value', 0); + }); + }); + }); +}); diff --git a/packages/db-service/prisma/migrations/20260217205235_add_counter_model/migration.sql b/packages/db-service/prisma/migrations/20260217205235_add_counter_model/migration.sql new file mode 100644 index 00000000..17611a9e --- /dev/null +++ b/packages/db-service/prisma/migrations/20260217205235_add_counter_model/migration.sql @@ -0,0 +1,7 @@ +-- CreateTable +CREATE TABLE "Counter" ( + "id" TEXT NOT NULL, + "value" INTEGER NOT NULL, + + CONSTRAINT "Counter_pkey" PRIMARY KEY ("id") +); diff --git a/packages/db-service/prisma/schema.prisma b/packages/db-service/prisma/schema.prisma index c12b4678..9728bc5e 100644 --- a/packages/db-service/prisma/schema.prisma +++ b/packages/db-service/prisma/schema.prisma @@ -154,3 +154,8 @@ model FileServiceFile { @@id([path, bucketName, configId, configEnv]) } + +model Counter { + id String @id + value Int +} diff --git a/packages/db-service/src/app.module.ts b/packages/db-service/src/app.module.ts index 6ce22f40..d0c57236 100644 --- a/packages/db-service/src/app.module.ts +++ b/packages/db-service/src/app.module.ts @@ -13,6 +13,7 @@ import { FileBucketModule } from './modules/file_bucket/file_bucket.module'; import { FileConfigModule } from './modules/file_config/file_config.module'; import { FileProviderModule } from './modules/file_provider/file_provider.module'; import { AnalyticsConfigModule } from './modules/analytics_config/analytics_config.module'; +import { CounterModule } from './modules/counter/counter.module'; @Module({ imports: [ @@ -28,6 +29,7 @@ import { AnalyticsConfigModule } from './modules/analytics_config/analytics_conf FileConfigModule, FileProviderModule, AnalyticsConfigModule, + CounterModule, ], controllers: [AppController], providers: [AppService], diff --git a/packages/db-service/src/main.ts b/packages/db-service/src/main.ts index 3b2b0c5a..83b36847 100644 --- a/packages/db-service/src/main.ts +++ b/packages/db-service/src/main.ts @@ -28,6 +28,8 @@ import { FileProviderProtoFile, AnalyticsConfigProto, AnalyticsConfigProtoFile, + CounterProto, + CounterProtoFile, } from 'juno-proto'; import { CustomRpcExceptionFilter } from './global-exception.filter'; @@ -52,6 +54,7 @@ async function bootstrap() { FileConfigProto.JUNO_FILE_SERVICE_CONFIG_PACKAGE_NAME, FileProviderProto.JUNO_FILE_SERVICE_PROVIDER_PACKAGE_NAME, AnalyticsConfigProto.JUNO_ANALYTICS_SERVICE_ANALYTICS_CONFIG_PACKAGE_NAME, + CounterProto.JUNO_COUNTER_PACKAGE_NAME, ], protoPath: [ UserProtoFile, @@ -66,6 +69,7 @@ async function bootstrap() { FileConfigProtoFile, FileProviderProtoFile, AnalyticsConfigProtoFile, + CounterProtoFile, ], url: process.env.DB_SERVICE_ADDR, }, diff --git a/packages/db-service/src/modules/counter/counter.controller.ts b/packages/db-service/src/modules/counter/counter.controller.ts new file mode 100644 index 00000000..e45b305d --- /dev/null +++ b/packages/db-service/src/modules/counter/counter.controller.ts @@ -0,0 +1,75 @@ +import { Controller } from '@nestjs/common'; +import { RpcException } from '@nestjs/microservices'; +import { status } from '@grpc/grpc-js'; +import { Prisma } from '@prisma/client'; +import { CounterProto } from 'juno-proto'; +import { CounterService } from './counter.service'; + +@Controller() +@CounterProto.CounterServiceControllerMethods() +export class CounterController + implements CounterProto.CounterServiceController +{ + constructor(private readonly counterService: CounterService) {} + + async createCounter( + request: CounterProto.CreateCounterRequest, + ): Promise { + try { + const value = + request.initialValue ?? + (request as { initial_value?: number }).initial_value ?? + 0; + const counter = await this.counterService.createCounter( + request.id, + value, + ); + return { value: counter.value }; + } catch (e) { + if ( + e instanceof Prisma.PrismaClientKnownRequestError && + e.code === 'P2002' + ) { + throw new RpcException({ + code: status.ALREADY_EXISTS, + message: 'Counter already exists', + }); + } + throw e; + } + } + + async getCounter( + request: CounterProto.GetCounterRequest, + ): Promise { + const counter = await this.counterService.getCounter(request.id); + if (!counter) { + throw new RpcException({ + code: status.NOT_FOUND, + message: 'Counter not found', + }); + } + return { value: counter.value }; + } + + async incrementCounter( + request: CounterProto.IncrementCounterRequest, + ): Promise { + const counter = await this.counterService.incrementCounter(request.id); + return { value: counter.value }; + } + + async decrementCounter( + request: CounterProto.DecrementCounterRequest, + ): Promise { + const counter = await this.counterService.decrementCounter(request.id); + return { value: counter.value }; + } + + async resetCounter( + request: CounterProto.ResetCounterRequest, + ): Promise { + const counter = await this.counterService.resetCounter(request.id); + return { value: counter.value }; + } +} diff --git a/packages/db-service/src/modules/counter/counter.module.ts b/packages/db-service/src/modules/counter/counter.module.ts new file mode 100644 index 00000000..3872ef92 --- /dev/null +++ b/packages/db-service/src/modules/counter/counter.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { CounterController } from './counter.controller'; +import { CounterService } from './counter.service'; +import { PrismaService } from 'src/prisma.service'; + +@Module({ + controllers: [CounterController], + providers: [CounterService, PrismaService], +}) +export class CounterModule {} diff --git a/packages/db-service/src/modules/counter/counter.service.ts b/packages/db-service/src/modules/counter/counter.service.ts new file mode 100644 index 00000000..630a1d59 --- /dev/null +++ b/packages/db-service/src/modules/counter/counter.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@nestjs/common'; +import { Counter } from '@prisma/client'; +import { PrismaService } from 'src/prisma.service'; + +@Injectable() +export class CounterService { + constructor(private prisma: PrismaService) {} + + async createCounter(id: string, initialValue: number = 0): Promise { + return this.prisma.counter.create({ + data: { id, value: initialValue }, + }); + } + + async getCounter(id: string): Promise { + return this.prisma.counter.findUnique({ + where: { id }, + }); + } + + async incrementCounter(id: string): Promise { + return this.prisma.counter.upsert({ + where: { id }, + create: { id, value: 1 }, + update: { value: { increment: 1 } }, + }); + } + + async decrementCounter(id: string): Promise { + return this.prisma.counter.upsert({ + where: { id }, + create: { id, value: -1 }, + update: { value: { decrement: 1 } }, + }); + } + + async resetCounter(id: string): Promise { + return this.prisma.counter.upsert({ + where: { id }, + create: { id, value: 0 }, + update: { value: 0 }, + }); + } +} diff --git a/packages/db-service/test/counter.e2e-spec.ts b/packages/db-service/test/counter.e2e-spec.ts new file mode 100644 index 00000000..2a1c4a8f --- /dev/null +++ b/packages/db-service/test/counter.e2e-spec.ts @@ -0,0 +1,214 @@ +import * as GRPC from '@grpc/grpc-js'; +import * as ProtoLoader from '@grpc/proto-loader'; +import { INestMicroservice } from '@nestjs/common'; +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; +import { Test, TestingModule } from '@nestjs/testing'; +import { + CounterProto, + CounterProtoFile, + ResetProto, + ResetProtoFile, +} from 'juno-proto'; +import { AppModule } from '../src/app.module'; + +let app: INestMicroservice; + +jest.setTimeout(15000); + +async function initApp() { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + const app = moduleFixture.createNestMicroservice({ + transport: Transport.GRPC, + options: { + package: [ + CounterProto.JUNO_COUNTER_PACKAGE_NAME, + ResetProto.JUNO_RESET_DB_PACKAGE_NAME, + ], + protoPath: [CounterProtoFile, ResetProtoFile], + url: process.env.DB_SERVICE_ADDR, + }, + }); + + await app.init(); + await app.listen(); + + return app; +} + +beforeAll(async () => { + app = await initApp(); + const proto = ProtoLoader.loadSync([ResetProtoFile]) as any; + + const protoGRPC = GRPC.loadPackageDefinition(proto) as any; + + const resetClient = new protoGRPC.juno.reset_db.DatabaseReset( + process.env.DB_SERVICE_ADDR, + GRPC.credentials.createInsecure(), + ); + + await new Promise((resolve) => { + resetClient.resetDb({}, () => { + resolve(0); + }); + }); + + app.close(); +}); + +beforeEach(async () => { + app = await initApp(); +}); + +afterEach(async () => { + await app.close(); +}); + +describe('Counter Tests', () => { + let counterClient: any; + + beforeEach(() => { + const proto = ProtoLoader.loadSync([CounterProtoFile]) as any; + const protoGRPC = GRPC.loadPackageDefinition(proto) as any; + counterClient = new protoGRPC.juno.counter.CounterService( + process.env.DB_SERVICE_ADDR, + GRPC.credentials.createInsecure(), + ); + }); + + it('creates a counter with initial value', async () => { + const response = await new Promise((resolve, reject) => { + counterClient.createCounter( + { id: 'e2e-create-counter', initial_value: 5 }, + (err, res) => { + if (err) reject(err); + else resolve(res); + }, + ); + }); + expect(response).toHaveProperty('value', 5); + }); + + it('cannot create a duplicate counter', async () => { + await new Promise((resolve) => { + counterClient.createCounter( + { id: 'e2e-create-duplicate', initial_value: 0 }, + () => { + resolve(0); + }, + ); + }); + + await new Promise((resolve) => { + counterClient.createCounter( + { id: 'e2e-create-duplicate', initial_value: 1 }, + (err) => { + expect(err.code).toBe(GRPC.status.ALREADY_EXISTS); + expect(err.details).toBe('Counter already exists'); + resolve(0); + }, + ); + }); + }); + + it('gets a counter value', async () => { + await new Promise((resolve) => { + counterClient.createCounter( + { id: 'e2e-get-counter', initial_value: 10 }, + () => { + resolve(0); + }, + ); + }); + + const response = await new Promise((resolve, reject) => { + counterClient.getCounter({ id: 'e2e-get-counter' }, (err, res) => { + if (err) reject(err); + else resolve(res); + }); + }); + expect(response).toHaveProperty('value', 10); + }); + + it('returns NOT_FOUND for nonexistent counter', async () => { + await new Promise((resolve) => { + counterClient.getCounter({ id: 'nonexistent-id' }, (err) => { + expect(err.code).toBe(GRPC.status.NOT_FOUND); + expect(err.details).toBe('Counter not found'); + resolve(0); + }); + }); + }); + + it('increments a counter', async () => { + await new Promise((resolve) => { + counterClient.createCounter( + { id: 'e2e-increment', initial_value: 0 }, + () => { + resolve(0); + }, + ); + }); + + const first = await new Promise((resolve, reject) => { + counterClient.incrementCounter({ id: 'e2e-increment' }, (err, res) => { + if (err) reject(err); + else resolve(res); + }); + }); + expect(first).toHaveProperty('value', 1); + + const second = await new Promise((resolve, reject) => { + counterClient.incrementCounter({ id: 'e2e-increment' }, (err, res) => { + if (err) reject(err); + else resolve(res); + }); + }); + expect(second).toHaveProperty('value', 2); + }); + + it('decrements a counter', async () => { + await new Promise((resolve) => { + counterClient.createCounter( + { id: 'e2e-decrement', initial_value: 0 }, + () => { + resolve(0); + }, + ); + }); + + const first = await new Promise((resolve, reject) => { + counterClient.decrementCounter({ id: 'e2e-decrement' }, (err, res) => { + if (err) reject(err); + else resolve(res); + }); + }); + expect(first).toHaveProperty('value', -1); + + const second = await new Promise((resolve, reject) => { + counterClient.decrementCounter({ id: 'e2e-decrement' }, (err, res) => { + if (err) reject(err); + else resolve(res); + }); + }); + expect(second).toHaveProperty('value', -2); + }); + + it('resets a counter to zero', async () => { + await new Promise((resolve) => { + counterClient.createCounter({ id: 'e2e-reset', initial_value: 5 }, () => { + resolve(0); + }); + }); + + const response = await new Promise((resolve, reject) => { + counterClient.resetCounter({ id: 'e2e-reset' }, (err, res) => { + if (err) reject(err); + else resolve(res); + }); + }); + expect(response).toHaveProperty('value', 0); + }); +}); diff --git a/packages/proto/definitions/counter.proto b/packages/proto/definitions/counter.proto new file mode 100644 index 00000000..d6319225 --- /dev/null +++ b/packages/proto/definitions/counter.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +package juno.counter; + +service CounterService { + rpc createCounter(CreateCounterRequest) returns (CreateCounterResponse); + rpc getCounter(GetCounterRequest) returns (GetCounterResponse); + rpc incrementCounter(IncrementCounterRequest) returns (IncrementCounterResponse); + rpc decrementCounter(DecrementCounterRequest) returns (DecrementCounterResponse); + rpc resetCounter(ResetCounterRequest) returns (ResetCounterResponse); +} + +message IncrementCounterRequest { string id = 1; } +message IncrementCounterResponse { int32 value = 1; } +message DecrementCounterRequest { string id = 1; } +message DecrementCounterResponse { int32 value = 1; } +message ResetCounterRequest { string id = 1; } +message ResetCounterResponse { int32 value = 1; } +message CreateCounterRequest { string id = 1; int32 initial_value = 2; } +message CreateCounterResponse { int32 value = 1; } +message GetCounterRequest { string id = 1; } +message GetCounterResponse { int32 value = 1; } \ No newline at end of file diff --git a/packages/proto/src/gen/counter.ts b/packages/proto/src/gen/counter.ts new file mode 100644 index 00000000..c777a486 --- /dev/null +++ b/packages/proto/src/gen/counter.ts @@ -0,0 +1,146 @@ +// Code generated by protoc-gen-ts_proto. DO NOT EDIT. +// versions: +// protoc-gen-ts_proto v1.181.2 +// protoc v5.28.2 +// source: counter.proto + +/* eslint-disable */ +import { GrpcMethod, GrpcStreamMethod } from '@nestjs/microservices'; +import { Observable } from 'rxjs'; + +export const protobufPackage = 'juno.counter'; + +export interface IncrementCounterRequest { + id: string; +} + +export interface IncrementCounterResponse { + value: number; +} + +export interface DecrementCounterRequest { + id: string; +} + +export interface DecrementCounterResponse { + value: number; +} + +export interface ResetCounterRequest { + id: string; +} + +export interface ResetCounterResponse { + value: number; +} + +export interface CreateCounterRequest { + id: string; + initialValue: number; +} + +export interface CreateCounterResponse { + value: number; +} + +export interface GetCounterRequest { + id: string; +} + +export interface GetCounterResponse { + value: number; +} + +export const JUNO_COUNTER_PACKAGE_NAME = 'juno.counter'; + +export interface CounterServiceClient { + createCounter( + request: CreateCounterRequest, + ): Observable; + + getCounter(request: GetCounterRequest): Observable; + + incrementCounter( + request: IncrementCounterRequest, + ): Observable; + + decrementCounter( + request: DecrementCounterRequest, + ): Observable; + + resetCounter(request: ResetCounterRequest): Observable; +} + +export interface CounterServiceController { + createCounter( + request: CreateCounterRequest, + ): + | Promise + | Observable + | CreateCounterResponse; + + getCounter( + request: GetCounterRequest, + ): + | Promise + | Observable + | GetCounterResponse; + + incrementCounter( + request: IncrementCounterRequest, + ): + | Promise + | Observable + | IncrementCounterResponse; + + decrementCounter( + request: DecrementCounterRequest, + ): + | Promise + | Observable + | DecrementCounterResponse; + + resetCounter( + request: ResetCounterRequest, + ): + | Promise + | Observable + | ResetCounterResponse; +} + +export function CounterServiceControllerMethods() { + return function (constructor: Function) { + const grpcMethods: string[] = [ + 'createCounter', + 'getCounter', + 'incrementCounter', + 'decrementCounter', + 'resetCounter', + ]; + for (const method of grpcMethods) { + const descriptor: any = Reflect.getOwnPropertyDescriptor( + constructor.prototype, + method, + ); + GrpcMethod('CounterService', method)( + constructor.prototype[method], + method, + descriptor, + ); + } + const grpcStreamMethods: string[] = []; + for (const method of grpcStreamMethods) { + const descriptor: any = Reflect.getOwnPropertyDescriptor( + constructor.prototype, + method, + ); + GrpcStreamMethod('CounterService', method)( + constructor.prototype[method], + method, + descriptor, + ); + } + }; +} + +export const COUNTER_SERVICE_NAME = 'CounterService'; diff --git a/packages/proto/src/gen/file_bucket.ts b/packages/proto/src/gen/file_bucket.ts index 0cac4da5..27ef8ab6 100644 --- a/packages/proto/src/gen/file_bucket.ts +++ b/packages/proto/src/gen/file_bucket.ts @@ -1,7 +1,7 @@ // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: // protoc-gen-ts_proto v1.181.2 -// protoc v5.28.3 +// protoc v5.28.2 // source: file_bucket.proto /* eslint-disable */ diff --git a/packages/proto/src/gen/file_config.ts b/packages/proto/src/gen/file_config.ts index 124697f9..fdc39f73 100644 --- a/packages/proto/src/gen/file_config.ts +++ b/packages/proto/src/gen/file_config.ts @@ -1,7 +1,7 @@ // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: // protoc-gen-ts_proto v1.181.2 -// protoc v5.28.3 +// protoc v5.28.2 // source: file_config.proto /* eslint-disable */ diff --git a/packages/proto/src/gen/file_provider.ts b/packages/proto/src/gen/file_provider.ts index d8b86cde..9e831720 100644 --- a/packages/proto/src/gen/file_provider.ts +++ b/packages/proto/src/gen/file_provider.ts @@ -1,7 +1,7 @@ // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: // protoc-gen-ts_proto v1.181.2 -// protoc v5.28.3 +// protoc v5.28.2 // source: file_provider.proto /* eslint-disable */ diff --git a/packages/proto/src/main.ts b/packages/proto/src/main.ts index cb6cf67f..79b8f974 100644 --- a/packages/proto/src/main.ts +++ b/packages/proto/src/main.ts @@ -15,6 +15,7 @@ export * as FileProviderProto from './gen/file_provider'; export * as CommonProto from './gen/common'; export * as AnalyticsProto from './gen/analytics'; export * as AnalyticsConfigProto from './gen/analytics_config'; +export * as CounterProto from './gen/counter'; import { join } from 'path'; @@ -41,3 +42,4 @@ export const AnalyticsProtoFile = getProtoFilePath('analytics.proto'); export const AnalyticsConfigProtoFile = getProtoFilePath( 'analytics_config.proto', ); +export const CounterProtoFile = getProtoFilePath('counter.proto'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14dcd6dd..112360f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7612,7 +7612,7 @@ packages: integrity: sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==, } engines: { node: '>=6.4.0 <13 || >=14' } - deprecated: Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net + deprecated: Please upgrade to v9.0.0+ as we have fixed a public vulnerability with formidable dependency. Note that v9.0.0+ requires Node.js v14.18.0+. See https://github.com/ladjs/superagent/pull/1800 for insight. This project is supported and maintained by the team at Forward Email @ https://forwardemail.net superagent@9.0.2: resolution: