gadget/gadget-code/src/services/project.ts
2026-05-01 14:31:00 -04:00

168 lines
3.9 KiB
TypeScript

// services/project.ts
// Copyright (C) 2026 Robert Colbert <rob.colbert@openplatform.us>
// All Rights Reserved
import slug from "slug";
import { MongooseBaseQueryOptions, PopulateOptions } from "mongoose";
import { IProject, IUser, ProjectStatus } from "@gadget/api";
import Project from "@/models/project.js";
import { DtpService } from "../lib/service.js";
export interface IProjectDefinition {
name: string;
slug: string;
gitUrl?: string;
status?: ProjectStatus;
}
class ProjectService extends DtpService {
private populateProject: PopulateOptions[];
get name(): string {
return "ProjectService";
}
get slug(): string {
return "svc:project";
}
constructor() {
super();
this.populateProject = [
{
path: "user",
select: "-passwordSalt -password",
},
];
}
async start(): Promise<void> {
this.log.info("service started");
}
async stop(): Promise<void> {
this.log.info("service stopped");
}
async create(user: IUser, definition: IProjectDefinition): Promise<IProject> {
const NOW = new Date();
const project = new Project();
project.createdAt = NOW;
project.user = user._id;
project.status = ProjectStatus.Active;
project.name = definition.name;
project.slug = slug(definition.slug.trim().toLowerCase());
if (definition.gitUrl) {
project.gitUrl = definition.gitUrl;
}
this.log.info("creating project", { name: project.name });
await project.save();
return project.populate(this.populateProject);
}
async update(
project: IProject,
definition: IProjectDefinition,
): Promise<IProject> {
const update: MongooseBaseQueryOptions = { $set: {}, $unset: {} };
if (definition.status) {
if (definition.status !== project.status) {
update.$set.status = definition.status;
}
}
if (definition.name) {
if (definition.name !== project.name) {
update.$set.name = definition.name;
}
}
if (definition.slug) {
if (definition.slug !== project.slug) {
update.$set.slug = slug(definition.slug.trim().toLowerCase());
}
}
if (definition.gitUrl) {
if (definition.gitUrl !== project.gitUrl) {
update.$set.gitUrl = definition.gitUrl;
}
} else {
update.$unset.gitUrl = 1;
}
const newProject = await Project.findOneAndUpdate(
{ _id: project._id },
update,
{ new: true, populate: this.populateProject },
);
if (!newProject) {
const error = new Error("project has been removed");
error.statusCode = 404;
throw error;
}
this.log.info("project updated", {
old: project,
new: newProject.toObject(),
});
return newProject;
}
async setStatus(project: IProject, status: ProjectStatus): Promise<IProject> {
const newProject = await Project.findOneAndUpdate(
{ _id: project._id },
{ $set: { status } },
{ new: true, populate: this.populateProject },
);
if (!newProject) {
const error = new Error("project has been removed");
error.statusCode = 404;
throw error;
}
this.log.info("project status updated", {
project: {
_id: project._id,
slug: project.slug,
},
old: project.status,
new: newProject.status,
});
return newProject;
}
async getForUser(user: IUser): Promise<IProject[]> {
const projects = await Project.find({ user: user._id })
.sort({ name: 1 })
.populate(this.populateProject);
return projects;
}
async findById(id: string): Promise<IProject | null> {
return Project.findById(id).populate(this.populateProject);
}
async findBySlug(slug: string, user: IUser): Promise<IProject | null> {
return Project.findOne({ slug, user: user._id }).populate(
this.populateProject,
);
}
async delete(project: IProject): Promise<void> {
await Project.deleteOne({ _id: project._id });
}
}
export default new ProjectService();