added project service
This commit is contained in:
parent
2129ff798b
commit
f900ecb3dd
@ -55,6 +55,7 @@
|
||||
"react-router-dom": "^7.14.2",
|
||||
"rotating-file-stream": "^3.2.6",
|
||||
"serve-favicon": "^2.5.1",
|
||||
"slug": "^11.0.1",
|
||||
"socket.io": "^4.8.3",
|
||||
"socket.io-client": "^4.8.3",
|
||||
"uikit": "^3.23.11",
|
||||
@ -81,6 +82,7 @@
|
||||
"@types/numeral": "^2.0.5",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/serve-favicon": "^2.5.7",
|
||||
"@types/slug": "^5.0.9",
|
||||
"@types/uikit": "^3.14.5",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
|
||||
@ -4,11 +4,12 @@
|
||||
|
||||
import { Types, Schema, model } from "mongoose";
|
||||
|
||||
import { IProject } from "@gadget/api";
|
||||
import { ProjectStatus, IProject } from "@gadget/api";
|
||||
|
||||
export const ProjectSchema = new Schema<IProject>({
|
||||
createdAt: { type: Date, default: Date.now, required: true },
|
||||
user: { type: Types.ObjectId, required: true, index: 1, ref: "User" },
|
||||
status: { type: String, enum: ProjectStatus, required: true },
|
||||
name: { type: String, default: "New Project", required: true },
|
||||
slug: {
|
||||
type: String,
|
||||
|
||||
153
gadget-code/src/services/project.ts
Normal file
153
gadget-code/src/services/project.ts
Normal file
@ -0,0 +1,153 @@
|
||||
// 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.toObject ? project.toObject() : 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.toHexString(),
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
export default new ProjectService();
|
||||
@ -5,9 +5,16 @@
|
||||
import { Document, Types } from "mongoose";
|
||||
import type { IUser } from "./user.js";
|
||||
|
||||
export enum ProjectStatus {
|
||||
Active = "active",
|
||||
Inactive = "inactive",
|
||||
Archived = "archived",
|
||||
}
|
||||
|
||||
export interface IProject extends Document {
|
||||
createdAt: Date;
|
||||
user: IUser | Types.ObjectId;
|
||||
status: ProjectStatus;
|
||||
name: string;
|
||||
slug: string;
|
||||
gitUrl?: string;
|
||||
|
||||
@ -112,6 +112,9 @@ importers:
|
||||
serve-favicon:
|
||||
specifier: ^2.5.1
|
||||
version: 2.5.1
|
||||
slug:
|
||||
specifier: ^11.0.1
|
||||
version: 11.0.1
|
||||
socket.io:
|
||||
specifier: ^4.8.3
|
||||
version: 4.8.3
|
||||
@ -185,6 +188,9 @@ importers:
|
||||
'@types/serve-favicon':
|
||||
specifier: ^2.5.7
|
||||
version: 2.5.7
|
||||
'@types/slug':
|
||||
specifier: ^5.0.9
|
||||
version: 5.0.9
|
||||
'@types/uikit':
|
||||
specifier: ^3.14.5
|
||||
version: 3.23.0
|
||||
@ -1159,6 +1165,9 @@ packages:
|
||||
'@types/serve-static@2.2.0':
|
||||
resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==}
|
||||
|
||||
'@types/slug@5.0.9':
|
||||
resolution: {integrity: sha512-6Yp8BSplP35Esa/wOG1wLNKiqXevpQTEF/RcL/NV6BBQaMmZh4YlDwCgrrFSoUE4xAGvnKd5c+lkQJmPrBAzfQ==}
|
||||
|
||||
'@types/uikit@3.23.0':
|
||||
resolution: {integrity: sha512-GTn8/K+f4AjFxtLqRKWzjaVKckQp/rHcl20IO09salh2VjyFb9CqeFugL95skO6qbbJLil3PE1MmNajrSj5gMg==}
|
||||
|
||||
@ -2792,6 +2801,10 @@ packages:
|
||||
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
slug@11.0.1:
|
||||
resolution: {integrity: sha512-VrM060OM/E7rdLQSnp6JHrzFfJFmqQBp0+TMhZStnEB8PfNliaZ9UWYjTHGHLUFVJorZ8TjVd/aKvIxHWU2O7g==}
|
||||
hasBin: true
|
||||
|
||||
socket.io-adapter@2.5.6:
|
||||
resolution: {integrity: sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==}
|
||||
|
||||
@ -3829,6 +3842,8 @@ snapshots:
|
||||
'@types/http-errors': 2.0.5
|
||||
'@types/node': 25.6.0
|
||||
|
||||
'@types/slug@5.0.9': {}
|
||||
|
||||
'@types/uikit@3.23.0': {}
|
||||
|
||||
'@types/uuid@10.0.0': {}
|
||||
@ -5615,6 +5630,8 @@ snapshots:
|
||||
|
||||
slash@3.0.0: {}
|
||||
|
||||
slug@11.0.1: {}
|
||||
|
||||
socket.io-adapter@2.5.6:
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
|
||||
Loading…
Reference in New Issue
Block a user