Customer notes feature finished
This commit is contained in:
parent
ec357dcccb
commit
547ab0b230
@ -36,5 +36,5 @@ COPY --from=deps --chown=lecoffreuser leCoffre/src/common/databases ./src/common
|
|||||||
RUN apk update && apk add chromium
|
RUN apk update && apk add chromium
|
||||||
USER lecoffreuser
|
USER lecoffreuser
|
||||||
|
|
||||||
CMD ["npm", "run", "start"]
|
CMD ["npm", "run", "api:start"]
|
||||||
EXPOSE 3001
|
EXPOSE 3001
|
@ -59,7 +59,7 @@
|
|||||||
"file-type-checker": "^1.0.8",
|
"file-type-checker": "^1.0.8",
|
||||||
"fp-ts": "^2.16.1",
|
"fp-ts": "^2.16.1",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.139",
|
"le-coffre-resources": "git@github.com:smart-chain-fr/leCoffre-resources.git#v2.151",
|
||||||
"module-alias": "^2.2.2",
|
"module-alias": "^2.2.2",
|
||||||
"monocle-ts": "^2.3.13",
|
"monocle-ts": "^2.3.13",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
|
202
src/app/api/customer/NotesController.ts
Normal file
202
src/app/api/customer/NotesController.ts
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
import { Response, Request } from "express";
|
||||||
|
import { Controller, Get, Post, Put } from "@ControllerPattern/index";
|
||||||
|
import ApiController from "@Common/system/controller-pattern/ApiController";
|
||||||
|
import { Service } from "typedi";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
import authHandler from "@App/middlewares/AuthHandler";
|
||||||
|
import Note from "le-coffre-resources/dist/Customer/Note";
|
||||||
|
import NotesService from "@Services/customer/NotesService/NotesService";
|
||||||
|
|
||||||
|
@Controller()
|
||||||
|
@Service()
|
||||||
|
export default class NotesController extends ApiController {
|
||||||
|
constructor(private notesService: NotesService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Get all Notes
|
||||||
|
* @returns Note[] list of Notes
|
||||||
|
*/
|
||||||
|
@Get("/api/v1/customer/notes", [authHandler])
|
||||||
|
protected async get(req: Request, response: Response) {
|
||||||
|
try {
|
||||||
|
//get query
|
||||||
|
let query: Prisma.NotesFindManyArgs = {};
|
||||||
|
if (req.query["q"]) {
|
||||||
|
query = JSON.parse(req.query["q"] as string);
|
||||||
|
if (query.where?.uid) {
|
||||||
|
this.httpBadRequest(response, "You can't filter by uid");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//call service to get prisma entity
|
||||||
|
const noteEntities = await this.notesService.get(query);
|
||||||
|
|
||||||
|
//Hydrate ressource with prisma entity
|
||||||
|
const notes = Note.hydrateArray<Note>(noteEntities, { strategy: "excludeAll" });
|
||||||
|
|
||||||
|
//success
|
||||||
|
this.httpSuccess(response, notes);
|
||||||
|
} catch (error) {
|
||||||
|
this.httpBadRequest(response, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Get a specific customer note
|
||||||
|
*/
|
||||||
|
@Get("/api/v1/customer/notes/customer/:customer-uid/folders/:folder-uid/", [authHandler])
|
||||||
|
protected async getCustomerNoteByFolderUid(req: Request, response: Response) {
|
||||||
|
try {
|
||||||
|
const customerUid = req.params["customer-uid"];
|
||||||
|
const folderUid = req.params["folder-uid"];
|
||||||
|
if (!customerUid || !folderUid) {
|
||||||
|
this.httpBadRequest(response, "No uid provided");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//get query
|
||||||
|
const query: Prisma.NotesFindManyArgs = {
|
||||||
|
where: {
|
||||||
|
customer: {
|
||||||
|
uid: customerUid
|
||||||
|
},
|
||||||
|
folder: {
|
||||||
|
uid: folderUid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//call service to get prisma entity
|
||||||
|
const noteEntities = await this.notesService.get(query);
|
||||||
|
|
||||||
|
if(noteEntities.length === 0) {
|
||||||
|
this.httpNotFoundRequest(response, "No notes found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Hydrate ressource with prisma entity
|
||||||
|
const notes = Note.hydrateArray<Note>(noteEntities, { strategy: "excludeAll" });
|
||||||
|
|
||||||
|
//success
|
||||||
|
this.httpSuccess(response, notes);
|
||||||
|
} catch (error) {
|
||||||
|
this.httpInternalError(response, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Get a specific note by uid
|
||||||
|
*/
|
||||||
|
@Get("/api/v1/customer/notes/:uid", [authHandler])
|
||||||
|
protected async getOneByUid(req: Request, response: Response) {
|
||||||
|
try {
|
||||||
|
const uid = req.params["uid"];
|
||||||
|
if (!uid) {
|
||||||
|
this.httpBadRequest(response, "No uid provided");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//get query
|
||||||
|
let query;
|
||||||
|
if (req.query["q"]) {
|
||||||
|
query = JSON.parse(req.query["q"] as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
const noteEntity = await this.notesService.getByUid(uid, query);
|
||||||
|
|
||||||
|
if (!noteEntity) {
|
||||||
|
this.httpNotFoundRequest(response, "note not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Hydrate ressource with prisma entity
|
||||||
|
const note = Note.hydrate<Note>(noteEntity);
|
||||||
|
|
||||||
|
//success
|
||||||
|
this.httpSuccess(response, note);
|
||||||
|
} catch (error) {
|
||||||
|
this.httpInternalError(response, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Create a new note
|
||||||
|
*/
|
||||||
|
@Post("/api/v1/customer/notes", [authHandler])
|
||||||
|
protected async post(req: Request, response: Response) {
|
||||||
|
try {
|
||||||
|
//init OfficeFolder resource with request body values
|
||||||
|
const noteRessource = Note.hydrate<Note>(req.body);
|
||||||
|
// noteRessource.validateOrReject?.({ groups: ["createFolder"], forbidUnknownValues: false });
|
||||||
|
|
||||||
|
//call service to get prisma entity
|
||||||
|
try {
|
||||||
|
const noteEntity = await this.notesService.create(noteRessource);
|
||||||
|
//Hydrate ressource with prisma entity
|
||||||
|
const note = Note.hydrate<Note>(noteEntity, {
|
||||||
|
strategy: "excludeAll",
|
||||||
|
});
|
||||||
|
//success
|
||||||
|
this.httpCreated(response, note);
|
||||||
|
} catch (error) {
|
||||||
|
this.httpValidationError(response, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.httpInternalError(response, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Modify a specific note by uid
|
||||||
|
*/
|
||||||
|
@Put("/api/v1/customer/notes/:uid", [authHandler])
|
||||||
|
protected async put(req: Request, response: Response) {
|
||||||
|
try {
|
||||||
|
const uid = req.params["uid"];
|
||||||
|
if (!uid) {
|
||||||
|
this.httpBadRequest(response, "No uid provided");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const noteFound = await this.notesService.getByUid(uid);
|
||||||
|
|
||||||
|
if (!noteFound) {
|
||||||
|
this.httpNotFoundRequest(response, "office folder not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//init OfficeFolder resource with request body values
|
||||||
|
const noteEntity = Note.hydrate<Note>(req.body);
|
||||||
|
|
||||||
|
//validate folder
|
||||||
|
//await validateOrReject(noteEntity, { groups: ["updateFolder"], forbidUnknownValues: false });
|
||||||
|
|
||||||
|
//call service to get prisma entity
|
||||||
|
try {
|
||||||
|
const noteEntityUpdated = await this.notesService.update(uid, noteEntity);
|
||||||
|
|
||||||
|
//Hydrate ressource with prisma entity
|
||||||
|
const note = Note.hydrate<Note>(noteEntityUpdated, {
|
||||||
|
strategy: "excludeAll",
|
||||||
|
});
|
||||||
|
|
||||||
|
//success
|
||||||
|
this.httpSuccess(response, note);
|
||||||
|
} catch (error) {
|
||||||
|
this.httpValidationError(response, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.httpInternalError(response, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -95,7 +95,7 @@ export default class OfficeFoldersController extends ApiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Hydrate ressource with prisma entity
|
//Hydrate ressource with prisma entity
|
||||||
const officeFolder = OfficeFolderNotary.hydrate<OfficeFolderNotary>(officeFolderEntity, { strategy: "excludeAll" });
|
const officeFolder = OfficeFolderNotary.hydrate<OfficeFolderNotary>(officeFolderEntity);
|
||||||
|
|
||||||
if(officeFolder.customers) {
|
if(officeFolder.customers) {
|
||||||
officeFolder.customers = officeFolder.customers!.filter((customer) => customer.contact?.email === email);
|
officeFolder.customers = officeFolder.customers!.filter((customer) => customer.contact?.email === email);
|
||||||
|
@ -271,7 +271,8 @@ export default class OfficeFoldersController extends ApiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Hydrate ressource with prisma entity
|
//Hydrate ressource with prisma entity
|
||||||
const officeFolder = OfficeFolder.hydrate<OfficeFolder>(officeFolderEntity, { strategy: "excludeAll" });
|
const officeFolder = OfficeFolder.hydrate<OfficeFolder>(officeFolderEntity);
|
||||||
|
|
||||||
|
|
||||||
//success
|
//success
|
||||||
this.httpSuccess(response, officeFolder);
|
this.httpSuccess(response, officeFolder);
|
||||||
|
@ -53,6 +53,7 @@ import SubscriptionsController from "./api/admin/SubscriptionsController";
|
|||||||
import StripeController from "./api/admin/StripeController";
|
import StripeController from "./api/admin/StripeController";
|
||||||
import StripeWebhooks from "@Common/webhooks/stripeWebhooks";
|
import StripeWebhooks from "@Common/webhooks/stripeWebhooks";
|
||||||
import RulesGroupsController from "./api/admin/RulesGroupsController";
|
import RulesGroupsController from "./api/admin/RulesGroupsController";
|
||||||
|
import NotesController from "./api/customer/NotesController";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description This allow to declare all controllers used in the application
|
* @description This allow to declare all controllers used in the application
|
||||||
@ -114,5 +115,6 @@ export default {
|
|||||||
Container.get(StripeController);
|
Container.get(StripeController);
|
||||||
Container.get(StripeWebhooks);
|
Container.get(StripeWebhooks);
|
||||||
Container.get(RulesGroupsController);
|
Container.get(RulesGroupsController);
|
||||||
|
Container.get(NotesController);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "notes" (
|
||||||
|
"uid" TEXT NOT NULL,
|
||||||
|
"content" VARCHAR(1000) NOT NULL,
|
||||||
|
"created_at" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updated_at" TIMESTAMP(3),
|
||||||
|
"customer_uid" VARCHAR(255) NOT NULL,
|
||||||
|
"folder_uid" VARCHAR(255) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "notes_pkey" PRIMARY KEY ("uid")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "notes_uid_key" ON "notes"("uid");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "notes" ADD CONSTRAINT "notes_customer_uid_fkey" FOREIGN KEY ("customer_uid") REFERENCES "customers"("uid") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "notes" ADD CONSTRAINT "notes_folder_uid_fkey" FOREIGN KEY ("folder_uid") REFERENCES "office_folders"("uid") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -128,6 +128,7 @@ model Customers {
|
|||||||
totpCodes TotpCodes[]
|
totpCodes TotpCodes[]
|
||||||
office Offices @relation(fields: [office_uid], references: [uid], onDelete: Cascade)
|
office Offices @relation(fields: [office_uid], references: [uid], onDelete: Cascade)
|
||||||
office_uid String @db.VarChar(255)
|
office_uid String @db.VarChar(255)
|
||||||
|
notes Notes[]
|
||||||
@@map("customers")
|
@@map("customers")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,6 +173,7 @@ model OfficeFolders {
|
|||||||
|
|
||||||
folder_anchor OfficeFolderAnchors? @relation(fields: [folder_anchor_uid], references: [uid])
|
folder_anchor OfficeFolderAnchors? @relation(fields: [folder_anchor_uid], references: [uid])
|
||||||
folder_anchor_uid String? @unique @db.VarChar(255)
|
folder_anchor_uid String? @unique @db.VarChar(255)
|
||||||
|
notes Notes[]
|
||||||
|
|
||||||
@@unique([folder_number, office_uid])
|
@@unique([folder_number, office_uid])
|
||||||
@@map("office_folders")
|
@@map("office_folders")
|
||||||
@ -417,6 +419,18 @@ model Seats {
|
|||||||
@@map("seats")
|
@@map("seats")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Notes {
|
||||||
|
uid String @id @unique @default(uuid())
|
||||||
|
content String @db.VarChar(1000)
|
||||||
|
created_at DateTime? @default(now())
|
||||||
|
updated_at DateTime? @updatedAt
|
||||||
|
customer Customers @relation(fields: [customer_uid], references: [uid], onDelete: Cascade)
|
||||||
|
customer_uid String @db.VarChar(255)
|
||||||
|
folder OfficeFolders @relation(fields: [folder_uid], references: [uid], onDelete: Cascade)
|
||||||
|
folder_uid String @db.VarChar(255)
|
||||||
|
@@map("notes")
|
||||||
|
}
|
||||||
|
|
||||||
enum ESubscriptionStatus {
|
enum ESubscriptionStatus {
|
||||||
ACTIVE
|
ACTIVE
|
||||||
INACTIVE
|
INACTIVE
|
||||||
|
74
src/common/repositories/NotesRepository.ts
Normal file
74
src/common/repositories/NotesRepository.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import Database from "@Common/databases/database";
|
||||||
|
import BaseRepository from "@Repositories/BaseRepository";
|
||||||
|
import { Service } from "typedi";
|
||||||
|
import { Notes, Prisma } from "@prisma/client";
|
||||||
|
import Note from "le-coffre-resources/dist/Customer/Note";
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class NotesRepository extends BaseRepository {
|
||||||
|
constructor(private database: Database) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
protected get model() {
|
||||||
|
return this.database.getClient().notes;
|
||||||
|
}
|
||||||
|
protected get instanceDb() {
|
||||||
|
return this.database.getClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Find many notes
|
||||||
|
*/
|
||||||
|
public async findMany(query: Prisma.NotesFindManyArgs) {
|
||||||
|
return this.model.findMany(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Find one note
|
||||||
|
*/
|
||||||
|
public async findOneByUid(uid: string, query?: Prisma.NotesInclude) {
|
||||||
|
return this.model.findUnique({
|
||||||
|
where: { uid },
|
||||||
|
include: query,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Create new note
|
||||||
|
*/
|
||||||
|
public async create(note: Note): Promise<Notes> {
|
||||||
|
const createArgs: Prisma.NotesCreateArgs = {
|
||||||
|
data: {
|
||||||
|
content: note.content || "",
|
||||||
|
customer: {
|
||||||
|
connect: {
|
||||||
|
uid: note.customer!.uid,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
folder: {
|
||||||
|
connect: {
|
||||||
|
uid: note.folder!.uid,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.model.create(createArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Update data of an office folder
|
||||||
|
*/
|
||||||
|
public async update(noteUid: string, note: Note): Promise<Notes> {
|
||||||
|
const updateArgs: Prisma.NotesUpdateArgs = {
|
||||||
|
where: {
|
||||||
|
uid: noteUid,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
content: note.content || "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.model.update(updateArgs);
|
||||||
|
}
|
||||||
|
}
|
48
src/services/customer/NotesService/NotesService.ts
Normal file
48
src/services/customer/NotesService/NotesService.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import BaseService from "@Services/BaseService";
|
||||||
|
import { Service } from "typedi";
|
||||||
|
import { Notes, Prisma } from "@prisma/client";
|
||||||
|
import NotesRepository from "@Repositories/NotesRepository";
|
||||||
|
import Note from "le-coffre-resources/dist/Customer/Note";
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class NotesService extends BaseService {
|
||||||
|
constructor(
|
||||||
|
private notesRepository: NotesRepository,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Get all notes
|
||||||
|
* @throws {Error} If notes cannot be get
|
||||||
|
*/
|
||||||
|
public async get(query: Prisma.NotesFindManyArgs) {
|
||||||
|
return this.notesRepository.findMany(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Get a note by uid
|
||||||
|
* @throws {Error} If note cannot be get
|
||||||
|
*/
|
||||||
|
public async getByUid(uid: string, query?: Prisma.NotesInclude) {
|
||||||
|
return this.notesRepository.findOneByUid(uid, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Create a new note
|
||||||
|
* @throws {Error} If note cannot be created
|
||||||
|
*/
|
||||||
|
public async create(noteEntity: Note): Promise<Notes> {
|
||||||
|
return this.notesRepository.create(noteEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description : Modify a note
|
||||||
|
* @throws {Error} If note cannot be modified
|
||||||
|
*/
|
||||||
|
public async update(noteUid: string, noteEntity: Note): Promise<Notes> {
|
||||||
|
return this.notesRepository.update(noteUid, noteEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user