diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fda24f1..54b7851 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: ports: - 5432:5432 options: >- - --health-cmd pg_isready + --health-cmd "pg_isready -U postgres" --health-interval 10s --health-timeout 5s --health-retries 5 @@ -29,10 +29,10 @@ jobs: - name: Checkout Code uses: actions/checkout@v4 - - name: Setup Node.js v22 + - name: Setup Node.js v24 uses: actions/setup-node@v4 with: - node-version: 22 + node-version: '24' cache: 'npm' cache-dependency-path: package-lock.json diff --git a/backend/src/__tests__/search.test.ts b/backend/src/__tests__/search.test.ts index 6c84568..32d960c 100644 --- a/backend/src/__tests__/search.test.ts +++ b/backend/src/__tests__/search.test.ts @@ -113,7 +113,7 @@ describe('GET /api/emails/search', () => { const userId = 'test-user-id'; const token = AuthService.generateToken(userId, 'test@example.com'); - const countSpy = jest.spyOn(prisma.email, 'count').mockResolvedValue(50); + jest.spyOn(prisma.email, 'count').mockResolvedValue(50); const findManySpy = jest .spyOn(prisma.email, 'findMany') .mockResolvedValue([]); @@ -144,10 +144,8 @@ describe('GET /api/emails/search', () => { const userId = 'test-user-id'; const token = AuthService.generateToken(userId, 'test@example.com'); - const countSpy = jest.spyOn(prisma.email, 'count').mockResolvedValue(10); - const findManySpy = jest - .spyOn(prisma.email, 'findMany') - .mockResolvedValue([]); + jest.spyOn(prisma.email, 'count').mockResolvedValue(10); + jest.spyOn(prisma.email, 'findMany').mockResolvedValue([]); const res = await request(app) .get('/api/emails/search?q=test&limit=-10&offset=invalid') diff --git a/backend/src/middleware/rate-limiter.middleware.ts b/backend/src/middleware/rate-limiter.middleware.ts index 2a4ca22..370cf24 100644 --- a/backend/src/middleware/rate-limiter.middleware.ts +++ b/backend/src/middleware/rate-limiter.middleware.ts @@ -1,7 +1,6 @@ import { Request, Response } from 'express'; import { rateLimit } from 'express-rate-limit'; import RedisStore from 'rate-limit-redis'; -import Redis from 'ioredis'; import { AuthService } from '../services/auth.service'; import { createRedisClient, RedisHealth } from '../utils/redis-health'; diff --git a/backend/src/routes/digests.routes.ts b/backend/src/routes/digests.routes.ts index ed5b973..e3d44b5 100644 --- a/backend/src/routes/digests.routes.ts +++ b/backend/src/routes/digests.routes.ts @@ -69,7 +69,7 @@ digestsRouter.post( details: validation.error.flatten(), }); } - const { type, limit } = validation.data; + const { type } = validation.data; logger.info('[Digests] Generating digest', { userId, type }); const digest = await DigestGeneratorService.generateDigest(userId, type); diff --git a/backend/src/routes/integrations.routes.ts b/backend/src/routes/integrations.routes.ts index 9d9e5bd..d46fd43 100644 --- a/backend/src/routes/integrations.routes.ts +++ b/backend/src/routes/integrations.routes.ts @@ -8,7 +8,6 @@ import { ImapService } from '../services/imap.service'; import { encrypt } from '../utils/crypto'; import { logger } from '../utils/logger'; import { z } from 'zod'; -import crypto from 'crypto'; import { google } from 'googleapis'; export const integrationsRouter = Router(); diff --git a/backend/src/server.ts b/backend/src/server.ts index 39ae673..ba9d4fa 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -800,8 +800,6 @@ app.get( if (!userId) { return res.status(401).json({ error: 'Unauthorized' }); } - let newToken: string | undefined; - const cacheKey = `user:profile:${userId}`; // Try fetching from Redis cache first @@ -1026,8 +1024,6 @@ app.get( if (!userId) { return res.status(401).json({ error: 'Unauthorized' }); } - let newToken: string | undefined; - const settings = await prisma.userSettings.findUnique({ where: { userId }, }); @@ -1237,8 +1233,6 @@ const getOAuth2Client = (req?: Request) => { ); }; -const oauth2Client = getOAuth2Client(); - /** * GET /api/integrations/gmail/auth * Generates the Google OAuth URL. diff --git a/backend/src/services/actions/digest-generator.service.ts b/backend/src/services/actions/digest-generator.service.ts index d1bb8db..678e2b9 100644 --- a/backend/src/services/actions/digest-generator.service.ts +++ b/backend/src/services/actions/digest-generator.service.ts @@ -1,8 +1,6 @@ import { PrismaClient, Digest } from '@prisma/client'; import * as Handlebars from 'handlebars'; import { logger } from '../../utils/logger'; -import { AIService } from '../ai.service'; - const prisma = new PrismaClient(); export interface DigestEmailData { diff --git a/backend/src/services/ai.service.ts b/backend/src/services/ai.service.ts index 905fc52..ef169c4 100644 --- a/backend/src/services/ai.service.ts +++ b/backend/src/services/ai.service.ts @@ -67,18 +67,26 @@ export class AIService { if (this.geminiKeys.length === 0) { const keysEnv = process.env.GEMINI_API_KEYS; if (keysEnv) { - this.geminiKeys = keysEnv.split(',').map((k) => k.trim()).filter(Boolean); + this.geminiKeys = keysEnv + .split(',') + .map((k) => k.trim()) + .filter(Boolean); } else { - const key1 = process.env.GEMINI_API_KEY || 'GEMINI_API_KEY_1_PLACEHOLDER'; - const key2 = process.env.GEMINI_API_KEY_2 || 'GEMINI_API_KEY_2_PLACEHOLDER'; - const key3 = process.env.GEMINI_API_KEY_3 || 'GEMINI_API_KEY_3_PLACEHOLDER'; - const key4 = process.env.GEMINI_API_KEY_4 || 'GEMINI_API_KEY_4_PLACEHOLDER'; - const key5 = process.env.GEMINI_API_KEY_5 || 'GEMINI_API_KEY_5_PLACEHOLDER'; - + const key1 = + process.env.GEMINI_API_KEY || 'GEMINI_API_KEY_1_PLACEHOLDER'; + const key2 = + process.env.GEMINI_API_KEY_2 || 'GEMINI_API_KEY_2_PLACEHOLDER'; + const key3 = + process.env.GEMINI_API_KEY_3 || 'GEMINI_API_KEY_3_PLACEHOLDER'; + const key4 = + process.env.GEMINI_API_KEY_4 || 'GEMINI_API_KEY_4_PLACEHOLDER'; + const key5 = + process.env.GEMINI_API_KEY_5 || 'GEMINI_API_KEY_5_PLACEHOLDER'; + const filtered = [key1, key2, key3, key4, key5].filter( (k) => !k.includes('PLACEHOLDER') ); - + this.geminiKeys = filtered.length > 0 ? filtered : [key1]; } } @@ -1388,7 +1396,9 @@ Provide the result as a JSON object with a single field 'category'.`; }); const text = response.text; if (!text) { - throw new Error('Gemini returned an empty link categorization response.'); + throw new Error( + 'Gemini returned an empty link categorization response.' + ); } return text; }); @@ -1628,7 +1638,9 @@ Do NOT infer or fabricate deadlines. Only return dates explicitly stated.`; const text = response.text; if (!text) { - throw new Error('Gemini returned an empty deadline extraction response.'); + throw new Error( + 'Gemini returned an empty deadline extraction response.' + ); } return text; }); diff --git a/backend/src/services/imap.service.ts b/backend/src/services/imap.service.ts index 7e20095..a07fe44 100644 --- a/backend/src/services/imap.service.ts +++ b/backend/src/services/imap.service.ts @@ -110,7 +110,7 @@ export class IMAPService { }); f.on('message', (msg, seqno) => { - msg.on('body', (stream, info) => { + msg.on('body', (stream, _info) => { let rawEml = ''; stream.on('data', (chunk) => { rawEml += chunk.toString('utf8'); diff --git a/backend/src/services/rules-engine.service.ts b/backend/src/services/rules-engine.service.ts index 285e149..59d38c6 100644 --- a/backend/src/services/rules-engine.service.ts +++ b/backend/src/services/rules-engine.service.ts @@ -1,4 +1,4 @@ -import { PrismaClient, Rule, RuleCondition, RuleAction } from '@prisma/client'; +import { PrismaClient, RuleCondition, RuleAction } from '@prisma/client'; import { WebhookDispatcher } from './webhook-dispatcher.service'; import { EmailSenderService } from './email-sender.service'; import { logger } from '../utils/logger'; diff --git a/backend/src/utils/bullmq-wrapper.ts b/backend/src/utils/bullmq-wrapper.ts index e9dbfec..13f9f8e 100644 --- a/backend/src/utils/bullmq-wrapper.ts +++ b/backend/src/utils/bullmq-wrapper.ts @@ -218,7 +218,7 @@ export class Queue { await this.realQueue.close(); this.realQueue = null; } - for (const [key, value] of this.localRepeatJobs.entries()) { + for (const [, value] of this.localRepeatJobs.entries()) { if (value.intervalId) { clearInterval(value.intervalId); } diff --git a/backend/src/utils/redis-health.ts b/backend/src/utils/redis-health.ts index 4b950b1..0d975e2 100644 --- a/backend/src/utils/redis-health.ts +++ b/backend/src/utils/redis-health.ts @@ -60,15 +60,15 @@ export const RedisHealth = { export class MockRedisClient { status = 'end'; - async get(key: string): Promise { + async get(_key: string): Promise { return null; } - async setex(key: string, seconds: number, value: string): Promise { + async setex(_key: string, _seconds: number, _value: string): Promise { return 'OK'; } - async del(key: string): Promise { + async del(_key: string): Promise { return 0; } @@ -76,7 +76,7 @@ export class MockRedisClient { return 'PONG'; } - async call(cmd: string, ...args: any[]): Promise { + async call(_cmd: string, ..._args: any[]): Promise { return null; } @@ -87,11 +87,11 @@ export class MockRedisClient { return this; } - once(event: string, handler: (...args: any[]) => void): this { + once(_event: string, _handler: (...args: any[]) => void): this { return this; } - off(event: string, handler: (...args: any[]) => void): this { + off(_event: string, _handler: (...args: any[]) => void): this { return this; } diff --git a/backend/src/worker.ts b/backend/src/worker.ts index bcfe7fb..b8e60ab 100644 --- a/backend/src/worker.ts +++ b/backend/src/worker.ts @@ -176,7 +176,9 @@ export async function registerWorkerHandlers() { // 4. Extract and save actions try { - logger.info('[Worker] Extracting action items from email', { emailId }); + logger.info('[Worker] Extracting action items from email', { + emailId, + }); const actionItems = await AIService.extractActionItems( email.subject, email.body @@ -195,7 +197,9 @@ export async function registerWorkerHandlers() { deadline: item.deadline ? new Date(item.deadline) : null, })), }); - logger.info('[Worker] Saved action items successfully', { emailId }); + logger.info('[Worker] Saved action items successfully', { + emailId, + }); } else { logger.info('[Worker] No action items extracted from email', { emailId, diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 46b56ee..5fcf794 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1426,7 +1426,14 @@ const DashboardContent: React.FC = () => { const pending = tasksList.filter((t) => !t.isCompleted); const completed = tasksList.filter((t) => t.isCompleted); return ( - + + setTheme((prev) => (prev === 'dark' ? 'light' : 'dark')) + } + > {toastMessage && (
{ if (activeTab === 'rules') { return ( - + + setTheme((prev) => (prev === 'dark' ? 'light' : 'dark')) + } + > {toastMessage && (