diff --git a/.woodpecker.yml b/.woodpecker.yml index 1095e7e..afdcaab 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -28,21 +28,32 @@ steps: SKIP_ENV_VALIDATION: "true" commands: - *install_deps - - pnpm lint || true # Non-blocking while fixing legacy code + - pnpm lint || true # Non-blocking while fixing legacy code depends_on: - install when: - evaluate: 'CI_PIPELINE_EVENT != "pull_request" || CI_COMMIT_BRANCH != "main"' + prisma-generate: + image: *node_image + environment: + SKIP_ENV_VALIDATION: "true" + commands: + - *install_deps + - pnpm --filter "@mosaic/api" prisma:generate + depends_on: + - install + typecheck: image: *node_image environment: SKIP_ENV_VALIDATION: "true" commands: - *install_deps + - pnpm --filter "@mosaic/api" prisma:generate - pnpm typecheck depends_on: - - install + - prisma-generate test: image: *node_image @@ -50,9 +61,10 @@ steps: SKIP_ENV_VALIDATION: "true" commands: - *install_deps - - pnpm test -- --run || true # Non-blocking while fixing legacy tests + - pnpm --filter "@mosaic/api" prisma:generate + - pnpm test || true # Non-blocking while fixing legacy tests depends_on: - - install + - prisma-generate build: image: *node_image @@ -61,7 +73,9 @@ steps: NODE_ENV: "production" commands: - *install_deps + - pnpm --filter "@mosaic/api" prisma:generate - pnpm build depends_on: - - typecheck # Only block on critical checks + - typecheck # Only block on critical checks - security-audit + - prisma-generate diff --git a/apps/api/package.json b/apps/api/package.json index a23f71b..c6bf149 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -36,6 +36,7 @@ "@nestjs/websockets": "^11.1.12", "@prisma/client": "^6.19.2", "@types/marked": "^6.0.0", + "@types/multer": "^2.0.0", "adm-zip": "^0.5.16", "archiver": "^7.0.1", "better-auth": "^1.4.17", @@ -66,7 +67,6 @@ "@types/archiver": "^7.0.0", "@types/express": "^5.0.1", "@types/highlight.js": "^10.1.0", - "@types/multer": "^2.0.0", "@types/node": "^22.13.4", "@types/sanitize-html": "^2.16.0", "@vitest/coverage-v8": "^4.0.18", diff --git a/apps/api/src/activity/interceptors/activity-logging.interceptor.ts b/apps/api/src/activity/interceptors/activity-logging.interceptor.ts index dbbf1af..45821cb 100644 --- a/apps/api/src/activity/interceptors/activity-logging.interceptor.ts +++ b/apps/api/src/activity/interceptors/activity-logging.interceptor.ts @@ -100,8 +100,8 @@ export class ActivityLoggingInterceptor implements NestInterceptor { entityType, entityId, details, - ipAddress: ip, - userAgent, + ipAddress: ip ?? undefined, + userAgent: userAgent ?? undefined, }); } catch (error) { // Don't fail the request if activity logging fails diff --git a/apps/api/src/activity/interfaces/activity.interface.ts b/apps/api/src/activity/interfaces/activity.interface.ts index 89cb575..d0ef668 100644 --- a/apps/api/src/activity/interfaces/activity.interface.ts +++ b/apps/api/src/activity/interfaces/activity.interface.ts @@ -10,8 +10,8 @@ export interface CreateActivityLogInput { entityType: EntityType; entityId: string; details?: Prisma.JsonValue; - ipAddress?: string; - userAgent?: string; + ipAddress?: string | undefined; + userAgent?: string | undefined; } /** diff --git a/apps/api/src/auth/guards/auth.guard.ts b/apps/api/src/auth/guards/auth.guard.ts index 1afd463..eff76e9 100644 --- a/apps/api/src/auth/guards/auth.guard.ts +++ b/apps/api/src/auth/guards/auth.guard.ts @@ -21,8 +21,12 @@ export class AuthGuard implements CanActivate { throw new UnauthorizedException("Invalid or expired session"); } - // Attach user to request - request.user = sessionData.user; + // Attach user to request (with type assertion for session data structure) + const user = sessionData.user as unknown as AuthenticatedRequest["user"]; + if (!user) { + throw new UnauthorizedException("Invalid user data in session"); + } + request.user = user; request.session = sessionData.session; return true; diff --git a/apps/api/src/common/utils/query-builder.ts b/apps/api/src/common/utils/query-builder.ts index a54bd2d..ed377e9 100644 --- a/apps/api/src/common/utils/query-builder.ts +++ b/apps/api/src/common/utils/query-builder.ts @@ -93,10 +93,10 @@ export class QueryBuilder { return {}; } - const filter: Prisma.JsonObject = {}; + const filter: Record = {}; if (from || to) { - const dateFilter: Prisma.JsonObject = {}; + const dateFilter: Record = {}; if (from) { dateFilter.gte = from; } @@ -106,7 +106,7 @@ export class QueryBuilder { filter[field] = dateFilter; } - return filter; + return filter as Prisma.JsonObject; } /** diff --git a/apps/api/src/domains/domains.service.ts b/apps/api/src/domains/domains.service.ts index c03ef51..2bdff3d 100644 --- a/apps/api/src/domains/domains.service.ts +++ b/apps/api/src/domains/domains.service.ts @@ -21,8 +21,10 @@ export class DomainsService { const domain = await this.prisma.domain.create({ data: { name: createDomainDto.name, - description: createDomainDto.description, - color: createDomainDto.color, + slug: createDomainDto.slug, + description: createDomainDto.description ?? null, + color: createDomainDto.color ?? null, + icon: createDomainDto.icon ?? null, workspace: { connect: { id: workspaceId }, }, @@ -53,9 +55,11 @@ export class DomainsService { const skip = (page - 1) * limit; // Build where clause - const where: Prisma.DomainWhereInput = { - workspaceId: query.workspaceId, - }; + const where: Prisma.DomainWhereInput = query.workspaceId + ? { + workspaceId: query.workspaceId, + } + : {}; // Add search filter if provided if (query.search) { @@ -130,12 +134,24 @@ export class DomainsService { throw new NotFoundException(`Domain with ID ${id} not found`); } + // Build update data, only including defined fields + const updateData: Prisma.DomainUpdateInput = {}; + if (updateDomainDto.name !== undefined) updateData.name = updateDomainDto.name; + if (updateDomainDto.slug !== undefined) updateData.slug = updateDomainDto.slug; + if (updateDomainDto.description !== undefined) + updateData.description = updateDomainDto.description; + if (updateDomainDto.color !== undefined) updateData.color = updateDomainDto.color; + if (updateDomainDto.icon !== undefined) updateData.icon = updateDomainDto.icon; + if (updateDomainDto.metadata !== undefined) { + updateData.metadata = updateDomainDto.metadata as unknown as Prisma.InputJsonValue; + } + const domain = await this.prisma.domain.update({ where: { id, workspaceId, }, - data: updateDomainDto, + data: updateData, include: { _count: { select: { tasks: true, events: true, projects: true, ideas: true }, diff --git a/apps/api/src/events/events.service.ts b/apps/api/src/events/events.service.ts index 818eb74..25ac365 100644 --- a/apps/api/src/events/events.service.ts +++ b/apps/api/src/events/events.service.ts @@ -18,19 +18,23 @@ export class EventsService { * Create a new event */ async create(workspaceId: string, userId: string, createEventDto: CreateEventDto) { + const projectConnection = createEventDto.projectId + ? { connect: { id: createEventDto.projectId } } + : undefined; + const data: Prisma.EventCreateInput = { title: createEventDto.title, - description: createEventDto.description, + description: createEventDto.description ?? null, startTime: createEventDto.startTime, - endTime: createEventDto.endTime, - location: createEventDto.location, + endTime: createEventDto.endTime ?? null, + location: createEventDto.location ?? null, workspace: { connect: { id: workspaceId } }, creator: { connect: { id: userId } }, allDay: createEventDto.allDay ?? false, metadata: createEventDto.metadata ? (createEventDto.metadata as unknown as Prisma.InputJsonValue) : {}, - project: createEventDto.projectId ? { connect: { id: createEventDto.projectId } } : undefined, + ...(projectConnection && { project: projectConnection }), }; const event = await this.prisma.event.create({ @@ -62,9 +66,11 @@ export class EventsService { const skip = (page - 1) * limit; // Build where clause - const where: Prisma.EventWhereInput = { - workspaceId: query.workspaceId, - }; + const where: Prisma.EventWhereInput = query.workspaceId + ? { + workspaceId: query.workspaceId, + } + : {}; if (query.projectId) { where.projectId = query.projectId; @@ -155,12 +161,32 @@ export class EventsService { throw new NotFoundException(`Event with ID ${id} not found`); } + // Build update data, only including defined fields (excluding projectId) + const updateData: Prisma.EventUpdateInput = {}; + if (updateEventDto.title !== undefined) updateData.title = updateEventDto.title; + if (updateEventDto.description !== undefined) + updateData.description = updateEventDto.description; + if (updateEventDto.startTime !== undefined) updateData.startTime = updateEventDto.startTime; + if (updateEventDto.endTime !== undefined) updateData.endTime = updateEventDto.endTime; + if (updateEventDto.allDay !== undefined) updateData.allDay = updateEventDto.allDay; + if (updateEventDto.location !== undefined) updateData.location = updateEventDto.location; + if (updateEventDto.recurrence !== undefined) { + updateData.recurrence = updateEventDto.recurrence as unknown as Prisma.InputJsonValue; + } + if (updateEventDto.metadata !== undefined) { + updateData.metadata = updateEventDto.metadata as unknown as Prisma.InputJsonValue; + } + // Handle project relation separately + if (updateEventDto.projectId !== undefined) { + updateData.project = { connect: { id: updateEventDto.projectId } }; + } + const event = await this.prisma.event.update({ where: { id, workspaceId, }, - data: updateEventDto, + data: updateData, include: { creator: { select: { id: true, name: true, email: true }, diff --git a/apps/api/src/ideas/ideas.service.ts b/apps/api/src/ideas/ideas.service.ts index 7df89d2..bd78209 100644 --- a/apps/api/src/ideas/ideas.service.ts +++ b/apps/api/src/ideas/ideas.service.ts @@ -19,10 +19,18 @@ export class IdeasService { * Create a new idea */ async create(workspaceId: string, userId: string, createIdeaDto: CreateIdeaDto) { + const domainConnection = createIdeaDto.domainId + ? { connect: { id: createIdeaDto.domainId } } + : undefined; + + const projectConnection = createIdeaDto.projectId + ? { connect: { id: createIdeaDto.projectId } } + : undefined; + const data: Prisma.IdeaCreateInput = { - title: createIdeaDto.title, + title: createIdeaDto.title ?? null, content: createIdeaDto.content, - category: createIdeaDto.category, + category: createIdeaDto.category ?? null, workspace: { connect: { id: workspaceId } }, creator: { connect: { id: userId } }, status: createIdeaDto.status ?? IdeaStatus.CAPTURED, @@ -31,8 +39,8 @@ export class IdeasService { metadata: createIdeaDto.metadata ? (createIdeaDto.metadata as unknown as Prisma.InputJsonValue) : {}, - domain: createIdeaDto.domainId ? { connect: { id: createIdeaDto.domainId } } : undefined, - project: createIdeaDto.projectId ? { connect: { id: createIdeaDto.projectId } } : undefined, + ...(domainConnection && { domain: domainConnection }), + ...(projectConnection && { project: projectConnection }), }; const idea = await this.prisma.idea.create({ @@ -67,7 +75,7 @@ export class IdeasService { workspace: { connect: { id: workspaceId } }, creator: { connect: { id: userId } }, content: captureIdeaDto.content, - title: captureIdeaDto.title, + title: captureIdeaDto.title ?? null, status: IdeaStatus.CAPTURED, priority: "MEDIUM", tags: [], @@ -101,9 +109,11 @@ export class IdeasService { const skip = (page - 1) * limit; // Build where clause - const where: Prisma.IdeaWhereInput = { - workspaceId: query.workspaceId, - }; + const where: Prisma.IdeaWhereInput = query.workspaceId + ? { + workspaceId: query.workspaceId, + } + : {}; if (query.status) { where.status = query.status; @@ -206,12 +216,31 @@ export class IdeasService { throw new NotFoundException(`Idea with ID ${id} not found`); } + // Build update data, only including defined fields (excluding domainId and projectId) + const updateData: Prisma.IdeaUpdateInput = {}; + if (updateIdeaDto.title !== undefined) updateData.title = updateIdeaDto.title; + if (updateIdeaDto.content !== undefined) updateData.content = updateIdeaDto.content; + if (updateIdeaDto.status !== undefined) updateData.status = updateIdeaDto.status; + if (updateIdeaDto.priority !== undefined) updateData.priority = updateIdeaDto.priority; + if (updateIdeaDto.category !== undefined) updateData.category = updateIdeaDto.category; + if (updateIdeaDto.tags !== undefined) updateData.tags = updateIdeaDto.tags; + if (updateIdeaDto.metadata !== undefined) { + updateData.metadata = updateIdeaDto.metadata as unknown as Prisma.InputJsonValue; + } + // Handle domain and project relations separately + if (updateIdeaDto.domainId !== undefined) { + updateData.domain = { connect: { id: updateIdeaDto.domainId } }; + } + if (updateIdeaDto.projectId !== undefined) { + updateData.project = { connect: { id: updateIdeaDto.projectId } }; + } + const idea = await this.prisma.idea.update({ where: { id, workspaceId, }, - data: updateIdeaDto, + data: updateData, include: { creator: { select: { id: true, name: true, email: true }, diff --git a/apps/api/src/knowledge/services/link-sync.service.ts b/apps/api/src/knowledge/services/link-sync.service.ts index c7b9267..bc9e34a 100644 --- a/apps/api/src/knowledge/services/link-sync.service.ts +++ b/apps/api/src/knowledge/services/link-sync.service.ts @@ -1,4 +1,5 @@ import { Injectable } from "@nestjs/common"; +import { Prisma } from "@prisma/client"; import { PrismaService } from "../../prisma/prisma.service"; import { LinkResolutionService } from "./link-resolution.service"; import { parseWikiLinks } from "../utils/wiki-link-parser"; @@ -81,29 +82,24 @@ export class LinkSyncService { }); // Resolve all parsed links - const linkCreations: { - sourceId: string; - targetId: string | null; - linkText: string; - displayText: string; - positionStart: number; - positionEnd: number; - resolved: boolean; - }[] = []; + const linkCreations: Prisma.KnowledgeLinkUncheckedCreateInput[] = []; for (const link of parsedLinks) { const targetId = await this.linkResolver.resolveLink(workspaceId, link.target); - // Create link record (resolved or unresolved) - linkCreations.push({ - sourceId: entryId, - targetId: targetId ?? null, - linkText: link.target, - displayText: link.displayText, - positionStart: link.start, - positionEnd: link.end, - resolved: targetId !== null, - }); + // Only create link record if targetId was resolved + // (Schema requires targetId to be non-null) + if (targetId) { + linkCreations.push({ + sourceId: entryId, + targetId, + linkText: link.target, + displayText: link.displayText, + positionStart: link.start, + positionEnd: link.end, + resolved: true, + }); + } } // Determine which existing links to keep/delete diff --git a/apps/api/src/layouts/layouts.service.ts b/apps/api/src/layouts/layouts.service.ts index f133bed..bb9fd58 100644 --- a/apps/api/src/layouts/layouts.service.ts +++ b/apps/api/src/layouts/layouts.service.ts @@ -105,7 +105,7 @@ export class LayoutsService { workspaceId, userId, isDefault: createLayoutDto.isDefault ?? false, - layout: createLayoutDto.layout as unknown as Prisma.JsonValue, + layout: createLayoutDto.layout as unknown as Prisma.InputJsonValue, }, }); }); @@ -141,13 +141,21 @@ export class LayoutsService { }); } + // Build update data, handling layout field separately + const updateData: Prisma.UserLayoutUpdateInput = {}; + if (updateLayoutDto.name !== undefined) updateData.name = updateLayoutDto.name; + if (updateLayoutDto.isDefault !== undefined) updateData.isDefault = updateLayoutDto.isDefault; + if (updateLayoutDto.layout !== undefined) { + updateData.layout = updateLayoutDto.layout as unknown as Prisma.InputJsonValue; + } + return tx.userLayout.update({ where: { id, workspaceId, userId, }, - data: updateLayoutDto, + data: updateData, }); }); } diff --git a/apps/api/src/personalities/services/prompt-formatter.service.ts b/apps/api/src/personalities/services/prompt-formatter.service.ts index edf59ee..e12f0f4 100644 --- a/apps/api/src/personalities/services/prompt-formatter.service.ts +++ b/apps/api/src/personalities/services/prompt-formatter.service.ts @@ -72,7 +72,7 @@ export class PromptFormatterService { if (options?.includeDateTime === true) { const now = new Date(); - const dateStr: string = context?.currentDate ?? now.toISOString().split("T")[0]; + const dateStr: string = context?.currentDate ?? now.toISOString().split("T")[0] ?? ""; const timeStr: string = context?.currentTime ?? now.toTimeString().slice(0, 5); const tzStr: string = context?.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone; parts.push(`Current date: ${dateStr}, Time: ${timeStr} (${tzStr})`); diff --git a/apps/api/src/prisma/prisma.service.ts b/apps/api/src/prisma/prisma.service.ts index 965fedd..0fc7310 100644 --- a/apps/api/src/prisma/prisma.service.ts +++ b/apps/api/src/prisma/prisma.service.ts @@ -64,7 +64,7 @@ export class PrismaService extends PrismaClient implements OnModuleInit, OnModul SELECT current_database(), version() `; - if (result.length > 0) { + if (result.length > 0 && result[0]) { const dbVersion = result[0].version.split(" ")[0]; return { connected: true, diff --git a/apps/api/src/projects/projects.service.ts b/apps/api/src/projects/projects.service.ts index f2b2006..604b747 100644 --- a/apps/api/src/projects/projects.service.ts +++ b/apps/api/src/projects/projects.service.ts @@ -21,10 +21,10 @@ export class ProjectsService { async create(workspaceId: string, userId: string, createProjectDto: CreateProjectDto) { const data: Prisma.ProjectCreateInput = { name: createProjectDto.name, - description: createProjectDto.description, - color: createProjectDto.color, - startDate: createProjectDto.startDate, - endDate: createProjectDto.endDate, + description: createProjectDto.description ?? null, + color: createProjectDto.color ?? null, + startDate: createProjectDto.startDate ?? null, + endDate: createProjectDto.endDate ?? null, workspace: { connect: { id: workspaceId } }, creator: { connect: { id: userId } }, status: createProjectDto.status ?? ProjectStatus.PLANNING, @@ -62,9 +62,11 @@ export class ProjectsService { const skip = (page - 1) * limit; // Build where clause - const where: Prisma.ProjectWhereInput = { - workspaceId: query.workspaceId, - }; + const where: Prisma.ProjectWhereInput = query.workspaceId + ? { + workspaceId: query.workspaceId, + } + : {}; if (query.status) { where.status = query.status; @@ -175,12 +177,25 @@ export class ProjectsService { throw new NotFoundException(`Project with ID ${id} not found`); } + // Build update data, only including defined fields + const updateData: Prisma.ProjectUpdateInput = {}; + if (updateProjectDto.name !== undefined) updateData.name = updateProjectDto.name; + if (updateProjectDto.description !== undefined) + updateData.description = updateProjectDto.description; + if (updateProjectDto.status !== undefined) updateData.status = updateProjectDto.status; + if (updateProjectDto.startDate !== undefined) updateData.startDate = updateProjectDto.startDate; + if (updateProjectDto.endDate !== undefined) updateData.endDate = updateProjectDto.endDate; + if (updateProjectDto.color !== undefined) updateData.color = updateProjectDto.color; + if (updateProjectDto.metadata !== undefined) { + updateData.metadata = updateProjectDto.metadata as unknown as Prisma.InputJsonValue; + } + const project = await this.prisma.project.update({ where: { id, workspaceId, }, - data: updateProjectDto, + data: updateData, include: { creator: { select: { id: true, name: true, email: true }, diff --git a/apps/api/src/tasks/tasks.service.ts b/apps/api/src/tasks/tasks.service.ts index 8715346..30d901d 100644 --- a/apps/api/src/tasks/tasks.service.ts +++ b/apps/api/src/tasks/tasks.service.ts @@ -19,7 +19,22 @@ export class TasksService { * Create a new task */ async create(workspaceId: string, userId: string, createTaskDto: CreateTaskDto) { - const data: Prisma.TaskCreateInput = Object.assign({}, createTaskDto, { + const assigneeConnection = createTaskDto.assigneeId + ? { connect: { id: createTaskDto.assigneeId } } + : undefined; + + const projectConnection = createTaskDto.projectId + ? { connect: { id: createTaskDto.projectId } } + : undefined; + + const parentConnection = createTaskDto.parentId + ? { connect: { id: createTaskDto.parentId } } + : undefined; + + const data: Prisma.TaskCreateInput = { + title: createTaskDto.title, + description: createTaskDto.description ?? null, + dueDate: createTaskDto.dueDate ?? null, workspace: { connect: { id: workspaceId } }, creator: { connect: { id: userId } }, status: createTaskDto.status ?? TaskStatus.NOT_STARTED, @@ -28,12 +43,10 @@ export class TasksService { metadata: createTaskDto.metadata ? (createTaskDto.metadata as unknown as Prisma.InputJsonValue) : {}, - assignee: createTaskDto.assigneeId - ? { connect: { id: createTaskDto.assigneeId } } - : undefined, - project: createTaskDto.projectId ? { connect: { id: createTaskDto.projectId } } : undefined, - parent: createTaskDto.parentId ? { connect: { id: createTaskDto.parentId } } : undefined, - }); + ...(assigneeConnection && { assignee: assigneeConnection }), + ...(projectConnection && { project: projectConnection }), + ...(parentConnection && { parent: parentConnection }), + }; // Set completedAt if status is COMPLETED if (data.status === TaskStatus.COMPLETED) { @@ -72,16 +85,18 @@ export class TasksService { const skip = (page - 1) * limit; // Build where clause - const where: Prisma.TaskWhereInput = { - workspaceId: query.workspaceId, - }; + const where: Prisma.TaskWhereInput = query.workspaceId + ? { + workspaceId: query.workspaceId, + } + : {}; if (query.status) { - where.status = query.status; + where.status = Array.isArray(query.status) ? { in: query.status } : query.status; } if (query.priority) { - where.priority = query.priority; + where.priority = Array.isArray(query.priority) ? { in: query.priority } : query.priority; } if (query.assigneeId) { @@ -190,23 +205,39 @@ export class TasksService { throw new NotFoundException(`Task with ID ${id} not found`); } - // Build update data - const data: Prisma.TaskUpdateInput = { - title: updateTaskDto.title, - description: updateTaskDto.description, - status: updateTaskDto.status, - priority: updateTaskDto.priority, - dueDate: updateTaskDto.dueDate, - sortOrder: updateTaskDto.sortOrder, - metadata: updateTaskDto.metadata - ? (updateTaskDto.metadata as unknown as Prisma.InputJsonValue) - : undefined, - assignee: updateTaskDto.assigneeId - ? { connect: { id: updateTaskDto.assigneeId } } - : undefined, - project: updateTaskDto.projectId ? { connect: { id: updateTaskDto.projectId } } : undefined, - parent: updateTaskDto.parentId ? { connect: { id: updateTaskDto.parentId } } : undefined, - }; + // Build update data - only include defined fields + const data: Prisma.TaskUpdateInput = {}; + + if (updateTaskDto.title !== undefined) { + data.title = updateTaskDto.title; + } + if (updateTaskDto.description !== undefined) { + data.description = updateTaskDto.description; + } + if (updateTaskDto.status !== undefined) { + data.status = updateTaskDto.status; + } + if (updateTaskDto.priority !== undefined) { + data.priority = updateTaskDto.priority; + } + if (updateTaskDto.dueDate !== undefined) { + data.dueDate = updateTaskDto.dueDate; + } + if (updateTaskDto.sortOrder !== undefined) { + data.sortOrder = updateTaskDto.sortOrder; + } + if (updateTaskDto.metadata !== undefined) { + data.metadata = updateTaskDto.metadata as unknown as Prisma.InputJsonValue; + } + if (updateTaskDto.assigneeId !== undefined && updateTaskDto.assigneeId !== null) { + data.assignee = { connect: { id: updateTaskDto.assigneeId } }; + } + if (updateTaskDto.projectId !== undefined && updateTaskDto.projectId !== null) { + data.project = { connect: { id: updateTaskDto.projectId } }; + } + if (updateTaskDto.parentId !== undefined && updateTaskDto.parentId !== null) { + data.parent = { connect: { id: updateTaskDto.parentId } }; + } // Handle completedAt based on status changes if (updateTaskDto.status) { diff --git a/apps/api/src/valkey/valkey.service.ts b/apps/api/src/valkey/valkey.service.ts index 3f4e276..f20a40a 100644 --- a/apps/api/src/valkey/valkey.service.ts +++ b/apps/api/src/valkey/valkey.service.ts @@ -27,14 +27,14 @@ export class ValkeyService implements OnModuleInit, OnModuleDestroy { this.client = new Redis(valkeyUrl, { maxRetriesPerRequest: 3, - retryStrategy: (times) => { + retryStrategy: (times: number) => { const delay = Math.min(times * 50, 2000); this.logger.warn( `Valkey connection retry attempt ${times.toString()}, waiting ${delay.toString()}ms` ); return delay; }, - reconnectOnError: (err) => { + reconnectOnError: (err: Error) => { this.logger.error("Valkey connection error:", err.message); return true; }, @@ -44,7 +44,7 @@ export class ValkeyService implements OnModuleInit, OnModuleDestroy { this.logger.log("Valkey connected successfully"); }); - this.client.on("error", (err) => { + this.client.on("error", (err: Error) => { this.logger.error("Valkey client error:", err.message); }); diff --git a/apps/api/src/websocket/websocket.gateway.ts b/apps/api/src/websocket/websocket.gateway.ts index 2b34907..db93a1c 100644 --- a/apps/api/src/websocket/websocket.gateway.ts +++ b/apps/api/src/websocket/websocket.gateway.ts @@ -52,19 +52,20 @@ export class WebSocketGateway implements OnGatewayConnection, OnGatewayDisconnec * @param client - The authenticated socket client containing userId and workspaceId in data. * @returns Promise that resolves when the client is joined to the workspace room or disconnected. */ - async handleConnection(client: AuthenticatedSocket): Promise { - const { userId, workspaceId } = client.data; + async handleConnection(client: Socket): Promise { + const authenticatedClient = client as AuthenticatedSocket; + const { userId, workspaceId } = authenticatedClient.data; if (!userId || !workspaceId) { - this.logger.warn(`Client ${client.id} connected without authentication`); - client.disconnect(); + this.logger.warn(`Client ${authenticatedClient.id} connected without authentication`); + authenticatedClient.disconnect(); return; } const room = this.getWorkspaceRoom(workspaceId); - await client.join(room); + await authenticatedClient.join(room); - this.logger.log(`Client ${client.id} joined room ${room}`); + this.logger.log(`Client ${authenticatedClient.id} joined room ${room}`); } /** @@ -72,13 +73,14 @@ export class WebSocketGateway implements OnGatewayConnection, OnGatewayDisconnec * @param client - The socket client containing workspaceId in data. * @returns void */ - handleDisconnect(client: AuthenticatedSocket): void { - const { workspaceId } = client.data; + handleDisconnect(client: Socket): void { + const authenticatedClient = client as AuthenticatedSocket; + const { workspaceId } = authenticatedClient.data; if (workspaceId) { const room = this.getWorkspaceRoom(workspaceId); - void client.leave(room); - this.logger.log(`Client ${client.id} left room ${room}`); + void authenticatedClient.leave(room); + this.logger.log(`Client ${authenticatedClient.id} left room ${room}`); } } diff --git a/apps/api/src/widgets/widget-data.service.ts b/apps/api/src/widgets/widget-data.service.ts index 6f75632..5bffcf8 100644 --- a/apps/api/src/widgets/widget-data.service.ts +++ b/apps/api/src/widgets/widget-data.service.ts @@ -178,7 +178,7 @@ export class WidgetDataService { items.push( ...tasks - .filter((task) => task.dueDate !== null) + .filter((task): task is typeof task & { dueDate: Date } => task.dueDate !== null) .map((task) => ({ id: task.id, title: task.title, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a49f76..53cb5ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,6 +77,9 @@ importers: '@types/marked': specifier: ^6.0.0 version: 6.0.0 + '@types/multer': + specifier: ^2.0.0 + version: 2.0.0 adm-zip: specifier: ^0.5.16 version: 0.5.16 @@ -162,9 +165,6 @@ importers: '@types/highlight.js': specifier: ^10.1.0 version: 10.1.0 - '@types/multer': - specifier: ^2.0.0 - version: 2.0.0 '@types/node': specifier: ^22.13.4 version: 22.19.7