Avnyr 2697c7ebdd feat: add WebSocket module for real-time functionalities
- Implemented `WebSocketsGateway` for handling Socket.IO connections, events, and rooms.
- Added `WebSocketsService` to act as a facade for emitting WebSocket events (projects, groups, notifications).
- Registered `WebSocketsModule` and integrated it into `AppModule`.
- Enabled real-time updates in `ProjectsService` and `GroupsService` with relevant WebSocket events (create, update, delete, collaborator/group actions).
2025-05-16 16:42:15 +02:00

245 lines
5.9 KiB
TypeScript

import { Injectable, NotFoundException, Inject } from '@nestjs/common';
import { eq, and } from 'drizzle-orm';
import { DRIZZLE } from '../../../database/database.module';
import * as schema from '../../../database/schema';
import { CreateProjectDto } from '../dto/create-project.dto';
import { UpdateProjectDto } from '../dto/update-project.dto';
import { WebSocketsService } from '../../websockets/websockets.service';
@Injectable()
export class ProjectsService {
constructor(
@Inject(DRIZZLE) private readonly db: any,
private readonly websocketsService: WebSocketsService,
) {}
/**
* Create a new project
*/
async create(createProjectDto: CreateProjectDto) {
const [project] = await this.db
.insert(schema.projects)
.values(createProjectDto)
.returning();
// Emit project created event
this.websocketsService.emitProjectUpdated(project.id, {
action: 'created',
project,
});
return project;
}
/**
* Find all projects
*/
async findAll() {
return this.db.select().from(schema.projects);
}
/**
* Find projects by owner ID
*/
async findByOwnerId(ownerId: string) {
return this.db
.select()
.from(schema.projects)
.where(eq(schema.projects.ownerId, ownerId));
}
/**
* Find a project by ID
*/
async findById(id: string) {
const [project] = await this.db
.select()
.from(schema.projects)
.where(eq(schema.projects.id, id));
if (!project) {
throw new NotFoundException(`Project with ID ${id} not found`);
}
return project;
}
/**
* Update a project
*/
async update(id: string, updateProjectDto: UpdateProjectDto) {
const [project] = await this.db
.update(schema.projects)
.set({
...updateProjectDto,
updatedAt: new Date(),
})
.where(eq(schema.projects.id, id))
.returning();
if (!project) {
throw new NotFoundException(`Project with ID ${id} not found`);
}
// Emit project updated event
this.websocketsService.emitProjectUpdated(project.id, {
action: 'updated',
project,
});
return project;
}
/**
* Delete a project
*/
async remove(id: string) {
const [project] = await this.db
.delete(schema.projects)
.where(eq(schema.projects.id, id))
.returning();
if (!project) {
throw new NotFoundException(`Project with ID ${id} not found`);
}
// Emit project deleted event
this.websocketsService.emitProjectUpdated(project.id, {
action: 'deleted',
project,
});
return project;
}
/**
* Check if a user has access to a project
*/
async checkUserAccess(projectId: string, userId: string) {
// Check if the user is the owner of the project
const [project] = await this.db
.select()
.from(schema.projects)
.where(
and(
eq(schema.projects.id, projectId),
eq(schema.projects.ownerId, userId)
)
);
if (project) {
return true;
}
// Check if the user is a collaborator on the project
const [collaboration] = await this.db
.select()
.from(schema.projectCollaborators)
.where(
and(
eq(schema.projectCollaborators.projectId, projectId),
eq(schema.projectCollaborators.userId, userId)
)
);
return !!collaboration;
}
/**
* Add a collaborator to a project
*/
async addCollaborator(projectId: string, userId: string) {
// Check if the project exists
const project = await this.findById(projectId);
// Check if the user exists
const [user] = await this.db
.select()
.from(schema.users)
.where(eq(schema.users.id, userId));
if (!user) {
throw new NotFoundException(`User with ID ${userId} not found`);
}
// Check if the user is already a collaborator on the project
const [existingCollaboration] = await this.db
.select()
.from(schema.projectCollaborators)
.where(
and(
eq(schema.projectCollaborators.projectId, projectId),
eq(schema.projectCollaborators.userId, userId)
)
);
if (existingCollaboration) {
return existingCollaboration;
}
// Add the user as a collaborator on the project
const [collaboration] = await this.db
.insert(schema.projectCollaborators)
.values({
projectId,
userId,
})
.returning();
// Emit collaborator added event
this.websocketsService.emitCollaboratorAdded(projectId, {
project,
user,
collaboration,
});
// Emit notification to the user
this.websocketsService.emitNotification(userId, {
type: 'project_invitation',
message: `You have been added as a collaborator to the project "${project.name}"`,
projectId,
projectName: project.name,
});
return collaboration;
}
/**
* Remove a collaborator from a project
*/
async removeCollaborator(projectId: string, userId: string) {
const [collaboration] = await this.db
.delete(schema.projectCollaborators)
.where(
and(
eq(schema.projectCollaborators.projectId, projectId),
eq(schema.projectCollaborators.userId, userId)
)
)
.returning();
if (!collaboration) {
throw new NotFoundException(`User with ID ${userId} is not a collaborator on project with ID ${projectId}`);
}
return collaboration;
}
/**
* Get all collaborators for a project
*/
async getCollaborators(projectId: string) {
// Check if the project exists
await this.findById(projectId);
// Get all collaborators for the project
return this.db
.select({
user: schema.users,
})
.from(schema.projectCollaborators)
.innerJoin(schema.users, eq(schema.projectCollaborators.userId, schema.users.id))
.where(eq(schema.projectCollaborators.projectId, projectId));
}
}