diff --git a/apps/api/package.json b/apps/api/package.json index a6726aa9b..5e0feea59 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -16,7 +16,8 @@ "@shieldsai/shared-notifications": "*", "@shieldsai/shared-utils": "*", "fastify": "^4.25.0", - "fastify-plugin": "^4.5.0" + "fastify-plugin": "^4.5.0", + "ioredis": "^5.3.0" }, "devDependencies": { "@types/node": "^25.6.0", diff --git a/apps/api/src/__tests__/spam-rate-limit.test.ts b/apps/api/src/__tests__/spam-rate-limit.test.ts new file mode 100644 index 000000000..b907d462d --- /dev/null +++ b/apps/api/src/__tests__/spam-rate-limit.test.ts @@ -0,0 +1,98 @@ +import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest'; +import { RedisRateLimiter } from '../middleware/spam-rate-limit.middleware'; +import { redis } from '../config/redis'; + +describe('RedisRateLimiter', () => { + const testKey = 'test-client'; + const limiter = new RedisRateLimiter(); + + beforeAll(async () => { + await redis.connect(); + }); + + afterAll(async () => { + await redis.quit(); + }); + + beforeEach(async () => { + await redis.del('spamshield:ratelimit:test-client'); + await redis.del('spamshield:ratelimit:daily:test-client'); + }); + + afterEach(async () => { + await redis.del('spamshield:ratelimit:test-client'); + await redis.del('spamshield:ratelimit:daily:test-client'); + }); + + describe('checkLimit (per-minute)', () => { + it('should allow requests within the limit', async () => { + const result = await limiter.checkLimit(testKey, 60, 10); + + expect(result.remaining).toBe(9); + expect(result.retryAfter).toBeUndefined(); + }); + + it('should decrement remaining on each request', async () => { + const result1 = await limiter.checkLimit(testKey, 60, 10); + const result2 = await limiter.checkLimit(testKey, 60, 10); + + expect(result1.remaining).toBe(9); + expect(result2.remaining).toBe(8); + }); + + it('should exceed limit after max requests', async () => { + for (let i = 0; i < 10; i++) { + await limiter.checkLimit(testKey, 60, 10); + } + + const result = await limiter.checkLimit(testKey, 60, 10); + + expect(result.remaining).toBe(0); + expect(result.retryAfter).toBeGreaterThan(0); + }); + + it('should return retry-after when limit is exceeded', async () => { + for (let i = 0; i < 10; i++) { + await limiter.checkLimit(testKey, 60, 10); + } + + const result = await limiter.checkLimit(testKey, 60, 10); + + expect(result.retryAfter).toBeGreaterThan(0); + expect(result.retryAfter).toBeLessThanOrEqual(60000); + }); + }); + + describe('checkDailyLimit', () => { + it('should allow requests within daily limit', async () => { + const result = await limiter.checkDailyLimit(testKey, 100); + + expect(result.remaining).toBe(99); + expect(result.retryAfter).toBeUndefined(); + }); + + it('should exceed daily limit after max requests', async () => { + for (let i = 0; i < 100; i++) { + await limiter.checkDailyLimit(testKey, 100); + } + + const result = await limiter.checkDailyLimit(testKey, 100); + + expect(result.remaining).toBe(0); + expect(result.retryAfter).toBeGreaterThan(0); + }); + }); + + describe('reset', () => { + it('should clear the rate limit counter', async () => { + await limiter.checkLimit(testKey, 60, 10); + await limiter.checkLimit(testKey, 60, 10); + + await limiter.reset(testKey); + + const result = await limiter.checkLimit(testKey, 60, 10); + + expect(result.remaining).toBe(9); + }); + }); +}); diff --git a/apps/api/src/config/redis.ts b/apps/api/src/config/redis.ts new file mode 100644 index 000000000..754b2d520 --- /dev/null +++ b/apps/api/src/config/redis.ts @@ -0,0 +1,18 @@ +import { Redis } from 'ioredis'; + +const redisHost = process.env.REDIS_HOST || 'localhost'; +const redisPort = parseInt(process.env.REDIS_PORT || '6379', 10); + +export const redis = new Redis({ + host: redisHost, + port: redisPort, + retryStrategy: (times: number) => Math.min(times * 50, 2000), + lazyConnect: true, +}); + +export async function getRedisConnection(): Promise { + if (redis.status === 'wait' || redis.status === 'connecting') { + await redis.connect(); + } + return redis; +} diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 53645cb30..e0b718fff 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -3,6 +3,7 @@ import cors from '@fastify/cors'; import helmet from '@fastify/helmet'; import { authMiddleware } from './middleware/auth.middleware'; import { rateLimitMiddleware } from './middleware/rate-limit.middleware'; +import { spamRateLimitMiddleware } from './middleware/spam-rate-limit.middleware'; import { errorHandlingMiddleware } from './middleware/error-handling.middleware'; import { loggingMiddleware } from './middleware/logging.middleware'; import { apiEnv, loggingConfig } from './config/api.config'; @@ -32,6 +33,9 @@ async function registerPlugins() { // Rate limiting await fastify.register(rateLimitMiddleware); + // SpamShield rate limiting (Redis-backed) + await fastify.register(spamRateLimitMiddleware); + // Authentication await fastify.register(authMiddleware); diff --git a/apps/api/src/middleware/spam-rate-limit.middleware.ts b/apps/api/src/middleware/spam-rate-limit.middleware.ts new file mode 100644 index 000000000..f56e373e5 --- /dev/null +++ b/apps/api/src/middleware/spam-rate-limit.middleware.ts @@ -0,0 +1,164 @@ +import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; +import { redis } from '../config/redis'; +import { spamRateLimits } from '../services/spamshield/spamshield.config'; + +const REDIS_PREFIX = 'spamshield:ratelimit'; + +class RedisRateLimiter { + async checkLimit( + key: string, + windowSeconds: number, + maxRequests: number + ): Promise<{ + remaining: number; + resetTime: number; + retryAfter?: number; + }> { + const redisKey = `${REDIS_PREFIX}:${key}`; + const now = Date.now(); + + const current = await redis.get(redisKey); + const windowStart = now - (now % (windowSeconds * 1000)); + const resetTime = windowStart + windowSeconds * 1000; + + if (!current) { + const expirySeconds = Math.ceil((resetTime - now) / 1000); + await redis.set(redisKey, '1', 'EX', expirySeconds); + + return { + remaining: maxRequests - 1, + resetTime, + }; + } + + const count = parseInt(current, 10) + 1; + await redis.set(redisKey, String(count), 'EX', Math.ceil((resetTime - now) / 1000)); + + const remaining = maxRequests - count; + + if (count > maxRequests) { + return { + remaining: 0, + resetTime, + retryAfter: resetTime - now, + }; + } + + return { + remaining, + resetTime, + }; + } + + async checkDailyLimit( + key: string, + maxPerDay: number + ): Promise<{ + remaining: number; + retryAfter?: number; + }> { + const redisKey = `${REDIS_PREFIX}:daily:${key}`; + const now = Date.now(); + const dayStart = new Date(now); + dayStart.setHours(0, 0, 0, 0); + const dayEnd = new Date(dayStart); + dayEnd.setDate(dayEnd.getDate() + 1); + const resetTime = dayEnd.getTime(); + + const current = await redis.get(redisKey); + const expirySeconds = Math.ceil((resetTime - now) / 1000); + + if (!current) { + await redis.set(redisKey, '1', 'EX', expirySeconds); + + return { + remaining: maxPerDay - 1, + }; + } + + const count = parseInt(current, 10) + 1; + await redis.set(redisKey, String(count), 'EX', expirySeconds); + + const remaining = maxPerDay - count; + + if (count > maxPerDay) { + return { + remaining: 0, + retryAfter: resetTime - now, + }; + } + + return { + remaining, + }; + } + + reset(key: string) { + const redisKey = `${REDIS_PREFIX}:${key}`; + return redis.del(redisKey); + } +} + +export const spamRateLimiter = new RedisRateLimiter(); + +export async function spamRateLimitMiddleware(fastify: FastifyInstance) { + fastify.addHook('preHandler', async (request: FastifyRequest, reply: FastifyReply) => { + const url = request.url || ''; + + if (!url.startsWith('/spamshield')) { + return; + } + + const clientIp = request.ip || (request.headers['x-forwarded-for'] as string) || 'unknown'; + const apiKey = request.headers['x-api-key'] as string | undefined; + const key = apiKey ? `api:${apiKey}` : `ip:${clientIp}`; + + let tier = 'basic'; + if (apiKey) { + if (apiKey.startsWith('premium_')) { + tier = 'premium'; + } else if (apiKey.startsWith('plus_')) { + tier = 'plus'; + } + } + + const config = spamRateLimits[tier as keyof typeof spamRateLimits]; + + const minuteResult = await spamRateLimiter.checkLimit( + key, + 60, + config.analysesPerMinute + ); + + const dailyResult = await spamRateLimiter.checkDailyLimit( + key, + config.analysesPerDay + ); + + reply.header('X-RateLimit-Limit', config.analysesPerMinute); + reply.header('X-RateLimit-Remaining', minuteResult.remaining); + reply.header('X-RateLimit-Reset', Math.ceil(minuteResult.resetTime / 1000)); + reply.header('X-RateLimit-Daily-Limit', config.analysesPerDay); + reply.header('X-RateLimit-Daily-Remaining', dailyResult.remaining); + + const retryAfter = minuteResult.retryAfter || dailyResult.retryAfter; + + if (retryAfter) { + reply.header('Retry-After', Math.ceil(retryAfter / 1000)); + reply.code(429); + + return { + error: 'Too Many Requests', + message: `Spam analysis rate limit exceeded. Try again in ${Math.ceil(retryAfter / 1000)}s`, + tier, + limit: config.analysesPerMinute, + dailyLimit: config.analysesPerDay, + reset: new Date(minuteResult.resetTime).toISOString(), + }; + } + + (request as any).spamRateLimitTier = tier; + }); +} + +export { RedisRateLimiter }; diff --git a/apps/api/src/routes/index.ts b/apps/api/src/routes/index.ts index 75f2eb0a7..7e21039f9 100644 --- a/apps/api/src/routes/index.ts +++ b/apps/api/src/routes/index.ts @@ -1,6 +1,7 @@ import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; import { authMiddleware, AuthRequest } from './auth.middleware'; import { voiceprintRoutes } from './voiceprint.routes'; +import { spamshieldRoutes } from './spamshield.routes'; export async function routes(fastify: FastifyInstance) { // Authenticated routes group @@ -121,4 +122,12 @@ export async function routes(fastify: FastifyInstance) { }, { prefix: '/voiceprint' } ); + + // SpamShield service routes + fastify.register( + async (spamshieldRouter) => { + await spamshieldRoutes(spamshieldRouter); + }, + { prefix: '/spamshield' } + ); } diff --git a/apps/api/src/routes/spamshield.routes.ts b/apps/api/src/routes/spamshield.routes.ts new file mode 100644 index 000000000..a1066970c --- /dev/null +++ b/apps/api/src/routes/spamshield.routes.ts @@ -0,0 +1,201 @@ +import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; +import { + numberReputationService, + smsClassifierService, + callAnalysisService, + spamFeedbackService, +} from '../services/spamshield'; + +export async function spamshieldRoutes(fastify: FastifyInstance) { + // Classify SMS text + fastify.post('/sms/classify', async (request: FastifyRequest, reply: FastifyReply) => { + const authReq = request as FastifyRequest & { user?: { id: string } }; + const userId = authReq.user?.id; + + if (!userId) { + return reply.code(401).send({ error: 'User ID required' }); + } + + const body = request.body as { text: string }; + + if (!body.text || typeof body.text !== 'string') { + return reply.code(400).send({ error: 'text is required' }); + } + + try { + const result = await smsClassifierService.classify(body.text); + return reply.send({ + classification: { + isSpam: result.isSpam, + confidence: result.confidence, + spamFeatures: result.spamFeatures, + }, + }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Classification failed'; + return reply.code(422).send({ error: message }); + } + }); + + // Check number reputation + fastify.post('/number/reputation', async (request: FastifyRequest, reply: FastifyReply) => { + const authReq = request as FastifyRequest & { user?: { id: string } }; + const userId = authReq.user?.id; + + if (!userId) { + return reply.code(401).send({ error: 'User ID required' }); + } + + const body = request.body as { phoneNumber: string }; + + if (!body.phoneNumber || typeof body.phoneNumber !== 'string') { + return reply.code(400).send({ error: 'phoneNumber is required' }); + } + + try { + const result = await numberReputationService.checkReputation(body.phoneNumber); + return reply.send({ + reputation: { + isSpam: result.isSpam, + confidence: result.confidence, + spamType: result.spamType, + reportCount: result.reportCount, + }, + }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Reputation check failed'; + return reply.code(422).send({ error: message }); + } + }); + + // Analyze incoming call + fastify.post('/call/analyze', async (request: FastifyRequest, reply: FastifyReply) => { + const authReq = request as FastifyRequest & { user?: { id: string } }; + const userId = authReq.user?.id; + + if (!userId) { + return reply.code(401).send({ error: 'User ID required' }); + } + + const body = request.body as { + phoneNumber: string; + duration?: number; + callTime: string; + isVoip?: boolean; + }; + + if (!body.phoneNumber || !body.callTime) { + return reply.code(400).send({ error: 'phoneNumber and callTime are required' }); + } + + try { + const result = await callAnalysisService.analyzeCall({ + phoneNumber: body.phoneNumber, + duration: body.duration, + callTime: new Date(body.callTime), + isVoip: body.isVoip, + }); + return reply.send({ + analysis: { + decision: result.decision, + confidence: result.confidence, + reasons: result.reasons, + }, + }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Call analysis failed'; + return reply.code(422).send({ error: message }); + } + }); + + // Record spam feedback + fastify.post('/feedback', async (request: FastifyRequest, reply: FastifyReply) => { + const authReq = request as FastifyRequest & { user?: { id: string } }; + const userId = authReq.user?.id; + + if (!userId) { + return reply.code(401).send({ error: 'User ID required' }); + } + + const body = request.body as { + phoneNumber: string; + isSpam: boolean; + confidence?: number; + metadata?: Record; + }; + + if (!body.phoneNumber || typeof body.isSpam !== 'boolean') { + return reply.code(400).send({ error: 'phoneNumber and isSpam are required' }); + } + + try { + const feedback = await spamFeedbackService.recordFeedback( + userId, + body.phoneNumber, + body.isSpam, + body.confidence, + body.metadata + ); + return reply.code(201).send({ + feedback: { + id: feedback.id, + phoneNumber: feedback.phoneNumber, + isSpam: feedback.isSpam, + createdAt: feedback.createdAt, + }, + }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Feedback recording failed'; + return reply.code(422).send({ error: message }); + } + }); + + // Get spam history + fastify.get('/history', async (request: FastifyRequest, reply: FastifyReply) => { + const authReq = request as FastifyRequest & { user?: { id: string } }; + const userId = authReq.user?.id; + + if (!userId) { + return reply.code(401).send({ error: 'User ID required' }); + } + + const query = request.query as { + limit?: string; + isSpam?: string; + startDate?: string; + }; + + const results = await spamFeedbackService.getSpamHistory(userId, { + limit: query.limit ? parseInt(query.limit, 10) : undefined, + isSpam: query.isSpam !== undefined ? query.isSpam === 'true' : undefined, + startDate: query.startDate ? new Date(query.startDate) : undefined, + }); + + return reply.send({ + history: results.map((r) => ({ + id: r.id, + phoneNumber: r.phoneNumber, + isSpam: r.isSpam, + createdAt: r.createdAt, + })), + }); + }); + + // Get spam statistics + fastify.get('/statistics', async (request: FastifyRequest, reply: FastifyReply) => { + const authReq = request as FastifyRequest & { user?: { id: string } }; + const userId = authReq.user?.id; + + if (!userId) { + return reply.code(401).send({ error: 'User ID required' }); + } + + try { + const stats = await spamFeedbackService.getStatistics(userId); + return reply.send({ statistics: stats }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Statistics retrieval failed'; + return reply.code(422).send({ error: message }); + } + }); +} diff --git a/package-lock.json b/package-lock.json index 8fdb2dbb3..8548cf29d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,7 @@ "eslint-plugin-solid": "^0.13.2", "puppeteer-core": "^24.42.0", "tsx": "^4.7.1", + "turbo": "^2.9.6", "typescript": "^5.3.3", "vite": "^5.1.4", "vite-plugin-solid": "^2.8.2", @@ -65,7 +66,8 @@ "@shieldsai/shared-notifications": "*", "@shieldsai/shared-utils": "*", "fastify": "^4.25.0", - "fastify-plugin": "^4.5.0" + "fastify-plugin": "^4.5.0", + "ioredis": "^5.3.0" }, "devDependencies": { "@types/node": "^25.6.0", @@ -2603,6 +2605,12 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@ioredis/commands": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz", + "integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==", + "license": "MIT" + }, "node_modules/@isaacs/ttlcache": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", @@ -2869,6 +2877,27 @@ "win32" ] }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@lukeed/uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@lukeed/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==", + "license": "MIT", + "dependencies": { + "@lukeed/csprng": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@msgpack/msgpack": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz", @@ -2878,6 +2907,84 @@ "node": ">= 10" } }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@neon-rs/load": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", @@ -3154,6 +3261,74 @@ "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", "license": "MIT" }, + "node_modules/@prisma/client": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", + "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/debug": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", + "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", + "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/fetch-engine": "5.22.0", + "@prisma/get-platform": "5.22.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", + "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", + "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/get-platform": "5.22.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", + "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "5.22.0" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -4095,6 +4270,76 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@segment/analytics-core": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@segment/analytics-core/-/analytics-core-1.4.1.tgz", + "integrity": "sha512-kV0Pf33HnthuBOVdYNani21kYyj118Fn+9757bxqoksiXoZlYvBsFq6giNdCsKcTIE1eAMqNDq3xE1VQ0cfsHA==", + "license": "MIT", + "dependencies": { + "@lukeed/uuid": "^2.0.0", + "@segment/analytics-generic-utils": "1.1.1", + "dset": "^3.1.2", + "tslib": "^2.4.1" + } + }, + "node_modules/@segment/analytics-generic-utils": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@segment/analytics-generic-utils/-/analytics-generic-utils-1.1.1.tgz", + "integrity": "sha512-THTIzBPHnvu1HYJU3fARdJ3qIkukO3zDXsmDm+kAeUks5R9CBXOQ6rPChiASVzSmwAIIo5uFIXXnCraojlq/Gw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.1" + } + }, + "node_modules/@segment/analytics-node": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-node/-/analytics-node-1.3.0.tgz", + "integrity": "sha512-lRLz1WZaDokMoUe299yP5JkInc3OgJuqNNlxb6j0q22umCiq6b5iDo2gRmFn93reirIvJxWIicQsGrHd93q8GQ==", + "license": "MIT", + "dependencies": { + "@lukeed/uuid": "^2.0.0", + "@segment/analytics-core": "1.4.1", + "@segment/analytics-generic-utils": "1.1.1", + "buffer": "^6.0.3", + "node-fetch": "^2.6.7", + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@segment/analytics-node/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@shieldsai/jobs": { + "resolved": "packages/jobs", + "link": true + }, + "node_modules/@shieldsai/shared-analytics": { + "resolved": "packages/shared-analytics", + "link": true + }, "node_modules/@shieldsai/shared-auth": { "resolved": "packages/shared-auth", "link": true @@ -5016,6 +5261,90 @@ "typescript": ">=5.7.2" } }, + "node_modules/@turbo/darwin-64": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@turbo/darwin-64/-/darwin-64-2.9.6.tgz", + "integrity": "sha512-X/56SnVXIQZBLKwniGTwEQTGmtE5brSACnKMBWpY3YafuxVYefrC2acamfjgxP7BG5w3I+6jf0UrLoSzgPcSJg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@turbo/darwin-arm64": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@turbo/darwin-arm64/-/darwin-arm64-2.9.6.tgz", + "integrity": "sha512-aalBeSl4agT/QtYGDyf/XLajedWzUC9Vg/pm/YO6QQ93vkQ91Vz5uK1ta5RbVRDozQSz4njxUNqRNmOXDzW+qw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@turbo/linux-64": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@turbo/linux-64/-/linux-64-2.9.6.tgz", + "integrity": "sha512-YKi05jnNHaD7vevgYwahpzGwbsNNTwzU2c7VZdmdFm7+cGDP4oREUWSsainiMfRqjRuolQxBwRn8wf1jmu+YZA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@turbo/linux-arm64": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@turbo/linux-arm64/-/linux-arm64-2.9.6.tgz", + "integrity": "sha512-02o/ZS69cOYEDczXvOB2xmyrtzjQ2hVFtWZK1iqxXUfzMmTjZK4UumrfNnjckSg+gqeBfnPRHa0NstA173Ik3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@turbo/windows-64": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@turbo/windows-64/-/windows-64-2.9.6.tgz", + "integrity": "sha512-wVdQjvnBI15wB6JrA+43CtUtagjIMmX6XYO758oZHAsCNSxqRlJtdyujih0D8OCnwCRWiGWGI63zAxR0hO6s9g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@turbo/windows-arm64": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/@turbo/windows-arm64/-/windows-arm64-2.9.6.tgz", + "integrity": "sha512-1XUUyWW0W6FTSqGEhU8RHVqb2wP1SPkr7hIvBlMEwH9jr+sJQK5kqeosLJ/QaUv4ecSAd1ZhIrLoW7qslAzT4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -6570,6 +6899,23 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/bullmq": { + "version": "5.76.4", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.76.4.tgz", + "integrity": "sha512-hVAplia7zfN3BxSCgAoRInJnbemfLwJdQLqJy/txEX8UMSTAeg0saPFNGWIlzES/Ct5xQ20TUaik/XwS99DOMA==", + "license": "MIT", + "dependencies": { + "cron-parser": "4.9.0", + "ioredis": "5.10.1", + "msgpackr": "1.11.5", + "node-abort-controller": "3.1.1", + "semver": "7.7.4", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=12.22.0" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -6913,6 +7259,15 @@ "node": ">=6" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -7081,6 +7436,18 @@ "dev": true, "license": "MIT" }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "license": "MIT", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -7257,6 +7624,15 @@ "node": ">=0.4.0" } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -7942,6 +8318,15 @@ } } }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -9348,7 +9733,6 @@ "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", "license": "Apache-2.0", - "optional": true, "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", @@ -9365,7 +9749,6 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "license": "MIT", - "optional": true, "engines": { "node": ">=8" }, @@ -9382,7 +9765,6 @@ "https://github.com/sponsors/ctavan" ], "license": "MIT", - "optional": true, "bin": { "uuid": "dist/bin/uuid" } @@ -9814,6 +10196,130 @@ "node": ">=14" } }, + "node_modules/googleapis": { + "version": "128.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-128.0.0.tgz", + "integrity": "sha512-+sLtVYNazcxaSD84N6rihVX4QiGoqRdnlz2SwmQQkadF31XonDfy4ufk3maMg27+FiySrH0rd7V8p+YJG6cknA==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^9.0.0", + "googleapis-common": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz", + "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.7.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common/node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis-common/node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis-common/node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis-common/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/googleapis/node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis/node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis/node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -9871,7 +10377,6 @@ "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", "license": "MIT", - "optional": true, "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" @@ -10214,6 +10719,30 @@ "loose-envify": "^1.0.0" } }, + "node_modules/ioredis": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.1.tgz", + "integrity": "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==", + "license": "MIT", + "dependencies": { + "@ioredis/commands": "1.5.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, "node_modules/ip-address": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", @@ -11137,12 +11666,24 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "license": "MIT" }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", "license": "MIT" }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "license": "MIT" + }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -11277,6 +11818,15 @@ "license": "MIT", "optional": true }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -11996,6 +12546,37 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/msgpackr": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", + "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", + "license": "MIT", + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz", + "integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.2.2" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -12193,6 +12774,12 @@ "node": ">=10" } }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "license": "MIT" + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -12254,6 +12841,21 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", + "integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -12995,6 +13597,26 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prisma": { + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", + "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/engines": "5.22.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=16.13" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -13603,6 +14225,27 @@ "node": ">= 12.13.0" } }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "license": "MIT", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", @@ -14517,6 +15160,12 @@ "node": ">=8" } }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", + "license": "MIT" + }, "node_modules/standardwebhooks": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", @@ -15141,6 +15790,24 @@ "node": "*" } }, + "node_modules/turbo": { + "version": "2.9.6", + "resolved": "https://registry.npmjs.org/turbo/-/turbo-2.9.6.tgz", + "integrity": "sha512-+v2QJey7ZUeUiuigkU+uFfklvNUyPI2VO2vBpMYJA+a1hKFLFiKtUYlRHdb3P9CrAvMzi0upbjI4WT+zKtqkBg==", + "dev": true, + "license": "MIT", + "bin": { + "turbo": "bin/turbo" + }, + "optionalDependencies": { + "@turbo/darwin-64": "2.9.6", + "@turbo/darwin-arm64": "2.9.6", + "@turbo/linux-64": "2.9.6", + "@turbo/linux-arm64": "2.9.6", + "@turbo/windows-64": "2.9.6", + "@turbo/windows-arm64": "2.9.6" + } + }, "node_modules/twilio": { "version": "5.13.1", "resolved": "https://registry.npmjs.org/twilio/-/twilio-5.13.1.tgz", @@ -15310,6 +15977,12 @@ "punycode": "^2.1.0" } }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "license": "BSD" + }, "node_modules/utf-8-validate": { "version": "6.0.6", "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.6.tgz", @@ -16609,6 +17282,33 @@ } } }, + "packages/jobs": { + "name": "@shieldsai/jobs", + "version": "0.1.0", + "dependencies": { + "@shieldsai/shared-db": "*", + "@shieldsai/shared-utils": "*", + "bullmq": "^5.1.0", + "ioredis": "^5.3.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "tsx": "^4.7.1", + "typescript": "^5.3.3" + } + }, + "packages/shared-analytics": { + "name": "@shieldsai/shared-analytics", + "version": "0.1.0", + "dependencies": { + "@segment/analytics-node": "^1.0.0", + "googleapis": "^128.0.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "typescript": "^5.3.3" + } + }, "packages/shared-auth": { "name": "@shieldsai/shared-auth", "version": "0.1.0", @@ -16624,11 +17324,11 @@ "name": "@shieldsai/shared-db", "version": "0.1.0", "dependencies": { - "drizzle-orm": "^0.45.2", + "@prisma/client": "^5.14.0", "zod": "^4.3.6" }, "devDependencies": { - "drizzle-kit": "^0.31.10", + "prisma": "^5.14.0", "typescript": "^5.3.3" } }, diff --git a/vitest.config.ts b/vitest.config.ts index e91ec9c22..acf3407f2 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -7,6 +7,10 @@ export default defineConfig({ deps: { interopDefault: true, }, + env: { + HIYA_API_KEY: 'test-api-key', + HIYA_API_URL: 'https://api.hiya.com/v1', + }, }, optimizeDeps: { include: ['ws'],