From ddfa7d65d679e397f2af63ac6d22c4c9b46a37f3 Mon Sep 17 00:00:00 2001 From: Pratapa Lakshmi Date: Sun, 15 Dec 2024 13:58:07 +0530 Subject: [PATCH] chore: add telemetry information to supabase --- pnpm-lock.yaml | 71 ++++++++++++++++- services/workflows-service/.env.example | 2 +- services/workflows-service/Dockerfile | 3 +- services/workflows-service/entrypoint.sh | 68 ++++++++++++++++ services/workflows-service/package.json | 1 + services/workflows-service/src/app.module.ts | 3 + .../src/auth/local/local-auth.guard.ts | 25 +++--- services/workflows-service/src/env.ts | 20 +++++ services/workflows-service/src/main.ts | 1 - .../src/supabase/mock-supabase.service.ts | 15 ++++ .../src/supabase/supabase.module.ts | 10 +++ .../src/supabase/supabase.service.ts | 77 +++++++++++++++++++ .../workflows-service/src/supabase/types.ts | 4 + 13 files changed, 287 insertions(+), 13 deletions(-) create mode 100755 services/workflows-service/entrypoint.sh create mode 100644 services/workflows-service/src/supabase/mock-supabase.service.ts create mode 100644 services/workflows-service/src/supabase/supabase.module.ts create mode 100644 services/workflows-service/src/supabase/supabase.service.ts create mode 100644 services/workflows-service/src/supabase/types.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 81cbcc56a4..863ed7787c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2717,6 +2717,9 @@ importers: '@sinclair/typebox': specifier: 0.32.15 version: 0.32.15 + '@supabase/supabase-js': + specifier: ^2.43.1 + version: 2.43.1 '@t3-oss/env-core': specifier: ^0.6.1 version: 0.6.1(typescript@4.9.3)(zod@3.23.4) @@ -17096,6 +17099,63 @@ packages: - typescript dev: false + /@supabase/auth-js@2.64.2: + resolution: {integrity: sha512-s+lkHEdGiczDrzXJ1YWt2y3bxRi+qIUnXcgkpLSrId7yjBeaXBFygNjTaoZLG02KNcYwbuZ9qkEIqmj2hF7svw==} + dependencies: + '@supabase/node-fetch': 2.6.15 + dev: false + + /@supabase/functions-js@2.3.1: + resolution: {integrity: sha512-QyzNle/rVzlOi4BbVqxLSH828VdGY1RElqGFAj+XeVypj6+PVtMlD21G8SDnsPQDtlqqTtoGRgdMlQZih5hTuw==} + dependencies: + '@supabase/node-fetch': 2.6.15 + dev: false + + /@supabase/node-fetch@2.6.15: + resolution: {integrity: sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==} + engines: {node: 4.x || >=6.0.0} + dependencies: + whatwg-url: 5.0.0 + dev: false + + /@supabase/postgrest-js@1.15.2: + resolution: {integrity: sha512-9/7pUmXExvGuEK1yZhVYXPZnLEkDTwxgMQHXLrN5BwPZZm4iUCL1YEyep/Z2lIZah8d8M433mVAUEGsihUj5KQ==} + dependencies: + '@supabase/node-fetch': 2.6.15 + dev: false + + /@supabase/realtime-js@2.9.5: + resolution: {integrity: sha512-TEHlGwNGGmKPdeMtca1lFTYCedrhTAv3nZVoSjrKQ+wkMmaERuCe57zkC5KSWFzLYkb5FVHW8Hrr+PX1DDwplQ==} + dependencies: + '@supabase/node-fetch': 2.6.15 + '@types/phoenix': 1.6.4 + '@types/ws': 8.5.10 + ws: 8.16.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + + /@supabase/storage-js@2.5.5: + resolution: {integrity: sha512-OpLoDRjFwClwc2cjTJZG8XviTiQH4Ik8sCiMK5v7et0MDu2QlXjCAW3ljxJB5+z/KazdMOTnySi+hysxWUPu3w==} + dependencies: + '@supabase/node-fetch': 2.6.15 + dev: false + + /@supabase/supabase-js@2.43.1: + resolution: {integrity: sha512-A+RV50mWNtyKo6M0u4G6AOqEifQD+MoOjZcpRkPMPpEAFgMsc2dt3kBlBlR/MgZizWQgUKhsvrwKk0efc8g6Ug==} + dependencies: + '@supabase/auth-js': 2.64.2 + '@supabase/functions-js': 2.3.1 + '@supabase/node-fetch': 2.6.15 + '@supabase/postgrest-js': 1.15.2 + '@supabase/realtime-js': 2.9.5 + '@supabase/storage-js': 2.5.5 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + /@sveltejs/vite-plugin-svelte-inspector@1.0.4(@sveltejs/vite-plugin-svelte@2.5.2)(svelte@3.59.2)(vite@4.5.3): resolution: {integrity: sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ==} engines: {node: ^14.18.0 || >= 16} @@ -18891,6 +18951,10 @@ packages: '@types/express': 4.17.9 dev: true + /@types/phoenix@1.6.4: + resolution: {integrity: sha512-B34A7uot1Cv0XtaHRYDATltAdKx0BvVKNgYNqE4WjtPUa4VQJM7kxeXcVKaH+KS+kCmZ+6w+QaUdcljiheiBJA==} + dev: false + /@types/pretty-hrtime@1.0.3: resolution: {integrity: sha512-nj39q0wAIdhwn7DGUyT9irmsKK1tV0bd5WFEhgpqNTMFZ8cE+jieuTphCW0tfdm47S2zVT5mr09B28b1chmQMA==} dev: true @@ -19138,6 +19202,12 @@ packages: resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} dev: true + /@types/ws@8.5.10: + resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} + dependencies: + '@types/node': 18.17.19 + dev: false + /@types/ws@8.5.9: resolution: {integrity: sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==} dependencies: @@ -39638,7 +39708,6 @@ packages: optional: true utf-8-validate: optional: true - dev: true /ws@8.17.1: resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} diff --git a/services/workflows-service/.env.example b/services/workflows-service/.env.example index 72780fb0b1..2da67e251b 100644 --- a/services/workflows-service/.env.example +++ b/services/workflows-service/.env.example @@ -35,4 +35,4 @@ WEB_UI_SDK_URL=http://localhost:5202 #HASHING_KEY_SECRET="$2b$10$FovZTB91/QQ4Yu28nvL8e." NOTION_API_KEY=secret HASHING_KEY_SECRET_BASE64=JDJiJDEwJFRYNjhmQi5JMlRCWHN0aGowakFHSi4= -SECRETS_MANAGER_PROVIDER=in-memory \ No newline at end of file +SECRETS_MANAGER_PROVIDER=in-memory diff --git a/services/workflows-service/Dockerfile b/services/workflows-service/Dockerfile index 186b0f421d..5db91f23d3 100644 --- a/services/workflows-service/Dockerfile +++ b/services/workflows-service/Dockerfile @@ -39,8 +39,9 @@ COPY --from=dev /app/scripts ./scripts COPY --from=dev /app/src ./src COPY --from=dev /app/tsconfig.build.json ./tsconfig.build.json COPY --from=dev /app/tsconfig.json ./tsconfig.json +COPY --from=dev /app/entrypoint.sh ./entrypoint.sh EXPOSE 3000 -CMD [ "dumb-init", "npm", "run", "prod" ] +ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/services/workflows-service/entrypoint.sh b/services/workflows-service/entrypoint.sh new file mode 100755 index 0000000000..9cb52189d1 --- /dev/null +++ b/services/workflows-service/entrypoint.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +infra_file="/tmp/infra.json" + +## Get cloudProvider details +function get_cloud_provider() { + release_details=$(uname -r || echo "") + if [[ $release_details == *"amzn"* ]]; then + cloud_provider="amazon" + elif [[ $release_details == *"azure"* ]]; then + cloud_provider="azure" + elif [[ $release_details == *"cloud"* ]]; then + cloud_provider="gcp" + elif [[ $release_details == *"generic"* ]]; then + cloud_provider="digitalocean" + elif [[ $release_details == *"ecs"* ]]; then + cloud_provider="alibaba" + elif [[ -n "${DYNO:-}" ]]; then + cloud_provider="heroku" + else + cloud_provider="others(including local)" + fi +} + +## Get deployment tool details +function get_tool() { + if [[ -z "${KUBERNETES_SERVICE_HOST:-}" ]]; then + dep_tool="likely docker" + else + dep_tool="kubernetes" + fi +} + +## Check hostname +function get_hostname() { + if [[ -f /etc/hostname ]]; then + hostname="$(cat /etc/hostname)" + else + hostname="$(hostname || echo "unknown")" + fi +} + +## Get current Time +function get_current_time() { + currentTime="$(date -u -Iseconds || echo "unknown")" +} + +## Check if it's an ECS Fargate deployment +function check_for_fargate() { + if [[ $cloud_provider == "amazon" && $dep_tool == "likely docker" ]]; then + dep_tool="ecs-fargate" + fi +} + +## Main Block +get_cloud_provider || true +get_tool || true +get_hostname || true +check_for_fargate || true +get_current_time || true + +infra_json='{"cloudProvider":"'"${cloud_provider}"'","tool":"'"${dep_tool}"'","hostname":"'"${hostname}"'","currentTime":"'"${currentTime}"'"}' +echo "${infra_json}" + +echo "${infra_json}" > "${infra_file}" + +exec dumb-init npm run prod + diff --git a/services/workflows-service/package.json b/services/workflows-service/package.json index ca48a9ebe8..944e83595b 100644 --- a/services/workflows-service/package.json +++ b/services/workflows-service/package.json @@ -70,6 +70,7 @@ "@sentry/integrations": "^7.52.1", "@sentry/node": "^7.52.1", "@sinclair/typebox": "0.32.15", + "@supabase/supabase-js": "^2.43.1", "@t3-oss/env-core": "^0.6.1", "ajv": "^8.12.0", "ajv-formats": "^2.1.1", diff --git a/services/workflows-service/src/app.module.ts b/services/workflows-service/src/app.module.ts index b9966c8509..9d82a433b6 100644 --- a/services/workflows-service/src/app.module.ts +++ b/services/workflows-service/src/app.module.ts @@ -11,6 +11,7 @@ import { ServeStaticOptionsService } from './serve-static-options.service'; import { EndUserModule } from './end-user/end-user.module'; import { BusinessModule } from './business/business.module'; import { StorageModule } from './storage/storage.module'; +import { SupabaseModule } from './supabase/supabase.module'; import { MulterModule } from '@nestjs/platform-express'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { FilterModule } from '@/filter/filter.module'; @@ -74,6 +75,7 @@ export const validate = async (config: Record) => { return result.data; }; + @Module({ controllers: [SwaggerController], imports: [ @@ -109,6 +111,7 @@ export const validate = async (config: Record) => { AuthModule, HealthModule, PrismaModule, + SupabaseModule, ConfigModule.forRoot({ validate, isGlobal: true, diff --git a/services/workflows-service/src/auth/local/local-auth.guard.ts b/services/workflows-service/src/auth/local/local-auth.guard.ts index 226d84526a..39c1167f13 100644 --- a/services/workflows-service/src/auth/local/local-auth.guard.ts +++ b/services/workflows-service/src/auth/local/local-auth.guard.ts @@ -1,14 +1,21 @@ +import { CanActivate, Injectable, ExecutionContext } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; -import { ExecutionContext } from '@nestjs/common'; -import type { Request } from 'express'; +import { SupabaseService } from '../../supabase/supabase.service'; -export class LocalAuthGuard extends AuthGuard('local') { - async canActivate(context: ExecutionContext) { - const result = await super.canActivate(context); - const request = context.switchToHttp().getRequest(); - - await super.logIn(request); +@Injectable() +export class LocalAuthGuard extends AuthGuard('local') implements CanActivate { + constructor(private readonly supabaseService: SupabaseService) { + super(); + } - return result as boolean; + async canActivate(context: ExecutionContext): Promise { + const result = super.canActivate(context) as boolean; + const request = context.switchToHttp().getRequest(); + if (result && request.user) { + const fullUrl = request.protocol + '://' + request.get('host') + request.originalUrl; + this.supabaseService.logSignIn(fullUrl); + } + super.logIn(request); + return Promise.resolve(result); } } diff --git a/services/workflows-service/src/env.ts b/services/workflows-service/src/env.ts index c7fea92bd9..f7371109ea 100644 --- a/services/workflows-service/src/env.ts +++ b/services/workflows-service/src/env.ts @@ -95,6 +95,26 @@ export const serverEnvSchema = { IN_MEMORIES_SECRET_ACQUIRER_ID: z.string().optional(), IN_MEMORIES_SECRET_PRIVATE_KEY: z.string().optional(), IN_MEMORIES_SECRET_CONSUMER_KEY: z.string().optional(), + SUPABASE_TELEMETRY_ENABLED: z + .enum(['true', 'false', '']) + .default('true') + .nullable() + .optional() + .transform(value => value === 'true' || value === null) + .describe('Enable or disable telemetry'), + TELEMETRY_SUPABASE_URL: z + .string() + .url() + .optional() + .default('https://lrwqumfbfgajpfnupayx.supabase.co') + .describe('Supabase URL for telemetry'), + TELEMETRY_SUPABASE_API_KEY: z + .string() + .optional() + .default( + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imxyd3F1bWZiZmdhanBmbnVwYXl4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTUyNTU3NDMsImV4cCI6MjAzMDgzMTc0M30.EJWCMJhnBvG9_BQWw6wy3dT0GrwR3S41w2Z92R80BVI', + ) + .describe('Supabase API key for telemetry'), }; if (!process.env['ENVIRONMENT_NAME'] || process.env['ENVIRONMENT_NAME'] === 'local') { diff --git a/services/workflows-service/src/main.ts b/services/workflows-service/src/main.ts index d7f08499f1..87aeacf488 100644 --- a/services/workflows-service/src/main.ts +++ b/services/workflows-service/src/main.ts @@ -1,5 +1,4 @@ import '@total-typescript/ts-reset'; - import passport from 'passport'; import dayjs from 'dayjs'; import cookieSession from 'cookie-session'; diff --git a/services/workflows-service/src/supabase/mock-supabase.service.ts b/services/workflows-service/src/supabase/mock-supabase.service.ts new file mode 100644 index 0000000000..79fecdbdfc --- /dev/null +++ b/services/workflows-service/src/supabase/mock-supabase.service.ts @@ -0,0 +1,15 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ISupabaseService } from './types'; + +@Injectable() +export class MockSupabaseService implements ISupabaseService { + private readonly logger = new Logger(MockSupabaseService.name); + + async logSignIn(url: string): Promise { + this.logger.debug(`Mock log: Sign-in recorded for URL: ${url}`); + } + + async logInfraData(infradata: JSON): Promise { + this.logger.debug(`Mock log: ${infradata} Infra data logged`); + } +} diff --git a/services/workflows-service/src/supabase/supabase.module.ts b/services/workflows-service/src/supabase/supabase.module.ts new file mode 100644 index 0000000000..af273da8a7 --- /dev/null +++ b/services/workflows-service/src/supabase/supabase.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { SupabaseService } from './supabase.service'; +import { SentryModule } from '@/sentry/sentry.module'; + +@Module({ + providers: [SupabaseService], + exports: [SupabaseService], + imports: [SentryModule], +}) +export class SupabaseModule {} diff --git a/services/workflows-service/src/supabase/supabase.service.ts b/services/workflows-service/src/supabase/supabase.service.ts new file mode 100644 index 0000000000..caa34e8620 --- /dev/null +++ b/services/workflows-service/src/supabase/supabase.service.ts @@ -0,0 +1,77 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import { ISupabaseService } from './types'; +import { SentryService } from '../sentry/sentry.service'; + +@Injectable() +export class SupabaseService implements ISupabaseService { + private readonly supabaseClient!: SupabaseClient; + private readonly logger = new Logger(SupabaseService.name); + + constructor( + private readonly configService: ConfigService, + private readonly SentryService: SentryService, + ) { + const telemetryEnabled = this.configService.get('TELEMETRY_ENABLED'); + const supabaseUrl = this.configService.get('TELEMETRY_SUPABASE_URL'); + const supabaseApiKey = this.configService.get('TELEMETRY_SUPABASE_API_KEY'); + if (telemetryEnabled) { + if (!supabaseUrl || !supabaseApiKey) { + throw new Error('Supabase URL or API key is missing in configuration'); + } else { + this.supabaseClient = createClient(supabaseUrl, supabaseApiKey, { + db: { schema: 'public' }, + }); + this.logger.log('Supabase client initialized.'); + // This log is created as part of the entrypoint script + // which gather details of the infrastructure. + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const infradata = require('/tmp/infra.json'); + void this.logInfraData(infradata); + } catch (error: Error | any) { + if (error.code === 'MODULE_NOT_FOUND') { + this.logger.error(`file not present: ${error.message}`); + this.SentryService.captureException(error.message); + } else { + this.logger.error(`Exception infra data not present: ${error.message}`); + } + } + } + } else { + this.logger.log('Telemetry disabled.'); + } + } + + async logSignIn(url: string): Promise { + try { + const { data, error } = await this.supabaseClient.from('logins').insert([{ url }]); + + if (error) { + this.logger.error(`Failed to log sign-in: ${error.message}`); + } else { + this.logger.log(`Sign-in logged successfully: ${data}`); + } + } catch (error: Error | any) { + this.logger.error(`Exception to log sign in data: ${error.message}`); + this.SentryService.captureException(error.message); + } + } + + async logInfraData(infradata: JSON): Promise { + try { + const { data, error } = await this.supabaseClient.from('infra').insert([infradata]); + + if (error) { + this.logger.error(`Failed to log infra data: ${error.message}`); + } else { + this.logger.log(`logged infra data successfully: ${data}`); + } + } catch (error: Error | any) { + this.logger.error(`Exception to log infra data: ${error.message}`); + this.SentryService.captureException(error.message); + } + } +} diff --git a/services/workflows-service/src/supabase/types.ts b/services/workflows-service/src/supabase/types.ts new file mode 100644 index 0000000000..77aed4706a --- /dev/null +++ b/services/workflows-service/src/supabase/types.ts @@ -0,0 +1,4 @@ +export interface ISupabaseService { + logSignIn(url: string): Promise; + logInfraData(infradata: JSON): Promise; +}