import { Inject, Injectable, Logger } from '@nestjs/common'; import { eq, and, type Db, teams, teamMembers, projects } from '@mosaic/db'; import { DB } from '../database/database.module.js'; @Injectable() export class TeamsService { private readonly logger = new Logger(TeamsService.name); constructor(@Inject(DB) private readonly db: Db) {} /** * Check if a user is a member of a team. */ async isMember(teamId: string, userId: string): Promise { const rows = await this.db .select({ id: teamMembers.id }) .from(teamMembers) .where(and(eq(teamMembers.teamId, teamId), eq(teamMembers.userId, userId))); return rows.length > 0; } /** * Check project access for a user. * - ownerType === 'user': project.ownerId must equal userId * - ownerType === 'team': userId must be a member of project.teamId */ async canAccessProject(userId: string, projectId: string): Promise { const rows = await this.db .select({ id: projects.id, ownerType: projects.ownerType, ownerId: projects.ownerId, teamId: projects.teamId, }) .from(projects) .where(eq(projects.id, projectId)); const project = rows[0]; if (!project) return false; if (project.ownerType === 'user') { return project.ownerId === userId; } if (project.ownerType === 'team' && project.teamId) { return this.isMember(project.teamId, userId); } return false; } /** * List all teams (for admin/listing endpoints). */ async findAll() { return this.db.select().from(teams); } /** * Find a team by ID. */ async findById(id: string) { const rows = await this.db.select().from(teams).where(eq(teams.id, id)); return rows[0]; } /** * List members of a team. */ async listMembers(teamId: string) { return this.db.select().from(teamMembers).where(eq(teamMembers.teamId, teamId)); } }