@eeplatform/core 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/index.d.ts +154 -5
- package/dist/index.js +1739 -23
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1751 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -12188,7 +12188,8 @@ function useMemberRepo() {
|
|
|
12188
12188
|
await collection.createIndex(
|
|
12189
12189
|
{
|
|
12190
12190
|
org: 1,
|
|
12191
|
-
user: 1
|
|
12191
|
+
user: 1,
|
|
12192
|
+
type: 1
|
|
12192
12193
|
},
|
|
12193
12194
|
{ partialFilterExpression: { deletedAt: "" }, unique: true }
|
|
12194
12195
|
);
|
|
@@ -12217,6 +12218,10 @@ function useMemberRepo() {
|
|
|
12217
12218
|
delCachedData();
|
|
12218
12219
|
return "Successfully added member.";
|
|
12219
12220
|
} catch (error) {
|
|
12221
|
+
logger3.log({
|
|
12222
|
+
level: "error",
|
|
12223
|
+
message: `Failed to create member: ${error}`
|
|
12224
|
+
});
|
|
12220
12225
|
if (error instanceof AppError) {
|
|
12221
12226
|
throw error;
|
|
12222
12227
|
} else {
|
|
@@ -17405,14 +17410,14 @@ var MRole = class {
|
|
|
17405
17410
|
throw new Error("Invalid _id.");
|
|
17406
17411
|
}
|
|
17407
17412
|
}
|
|
17408
|
-
if (typeof value.
|
|
17413
|
+
if (typeof value.id === "string" && value.id.length === 24) {
|
|
17409
17414
|
try {
|
|
17410
|
-
value.
|
|
17415
|
+
value.id = new ObjectId12(value.id);
|
|
17411
17416
|
} catch (error) {
|
|
17412
|
-
throw new Error("Invalid
|
|
17417
|
+
throw new Error("Invalid id.");
|
|
17413
17418
|
}
|
|
17414
17419
|
}
|
|
17415
|
-
this.
|
|
17420
|
+
this.id = value.id ?? "";
|
|
17416
17421
|
this._id = value._id ?? new ObjectId12();
|
|
17417
17422
|
this.name = value.name ?? "";
|
|
17418
17423
|
this.description = value.description ?? "";
|
|
@@ -17462,6 +17467,7 @@ function useRoleRepo() {
|
|
|
17462
17467
|
await collection.createIndex({ name: 1 });
|
|
17463
17468
|
await collection.createIndex({ type: 1 });
|
|
17464
17469
|
await collection.createIndex({ status: 1 });
|
|
17470
|
+
await collection.createIndex({ id: 1 });
|
|
17465
17471
|
} catch (error) {
|
|
17466
17472
|
throw new InternalServerError8("Failed to create index on role.");
|
|
17467
17473
|
}
|
|
@@ -17476,7 +17482,7 @@ function useRoleRepo() {
|
|
|
17476
17482
|
async function createUniqueIndex() {
|
|
17477
17483
|
try {
|
|
17478
17484
|
await collection.createIndex(
|
|
17479
|
-
{ name: 1, type: 1,
|
|
17485
|
+
{ name: 1, type: 1, id: 1 },
|
|
17480
17486
|
{ unique: true }
|
|
17481
17487
|
);
|
|
17482
17488
|
} catch (error) {
|
|
@@ -17607,26 +17613,29 @@ function useRoleRepo() {
|
|
|
17607
17613
|
limit = 10,
|
|
17608
17614
|
sort = {},
|
|
17609
17615
|
type = "",
|
|
17610
|
-
|
|
17616
|
+
id = ""
|
|
17611
17617
|
} = {}) {
|
|
17612
17618
|
limit = limit > 0 ? limit : 10;
|
|
17613
17619
|
search = search || "";
|
|
17614
17620
|
page = page > 0 ? page - 1 : 0;
|
|
17615
|
-
if (
|
|
17621
|
+
if (id && typeof id === "string" && id.length === 24) {
|
|
17616
17622
|
try {
|
|
17617
|
-
|
|
17623
|
+
id = new ObjectId13(id);
|
|
17618
17624
|
} catch (error) {
|
|
17619
|
-
throw new BadRequestError10("Invalid
|
|
17625
|
+
throw new BadRequestError10("Invalid ID.");
|
|
17620
17626
|
}
|
|
17621
17627
|
}
|
|
17622
|
-
const query = { status: "active"
|
|
17628
|
+
const query = { status: "active" };
|
|
17629
|
+
if (id) {
|
|
17630
|
+
query.id = id;
|
|
17631
|
+
}
|
|
17623
17632
|
const cacheKeyOptions = {
|
|
17624
17633
|
status: "active",
|
|
17625
17634
|
limit,
|
|
17626
17635
|
page
|
|
17627
17636
|
};
|
|
17628
|
-
if (
|
|
17629
|
-
cacheKeyOptions.
|
|
17637
|
+
if (id) {
|
|
17638
|
+
cacheKeyOptions.id = String(id);
|
|
17630
17639
|
}
|
|
17631
17640
|
if (type) {
|
|
17632
17641
|
cacheKeyOptions.type = type;
|
|
@@ -17813,7 +17822,7 @@ function useUserService() {
|
|
|
17813
17822
|
await _createUser(user, session);
|
|
17814
17823
|
const roleId = await addRole(
|
|
17815
17824
|
{
|
|
17816
|
-
|
|
17825
|
+
id: "",
|
|
17817
17826
|
name: "Super Admin",
|
|
17818
17827
|
type: "admin",
|
|
17819
17828
|
permissions: ["*"],
|
|
@@ -18779,8 +18788,8 @@ function useRoleController() {
|
|
|
18779
18788
|
const validation = Joi7.object({
|
|
18780
18789
|
name: Joi7.string().required(),
|
|
18781
18790
|
permissions: Joi7.array().items(Joi7.string()).required(),
|
|
18782
|
-
type: Joi7.string().
|
|
18783
|
-
|
|
18791
|
+
type: Joi7.string().valid("school", "division", "region", "account").required(),
|
|
18792
|
+
id: Joi7.string().hex().optional().allow("", null)
|
|
18784
18793
|
});
|
|
18785
18794
|
const { error } = validation.validate(payload);
|
|
18786
18795
|
if (error) {
|
|
@@ -18800,21 +18809,21 @@ function useRoleController() {
|
|
|
18800
18809
|
const page = parseInt(req.query.page ?? "1");
|
|
18801
18810
|
const limit = parseInt(req.query.limit ?? "10");
|
|
18802
18811
|
const type = req.query.type ?? "";
|
|
18803
|
-
const
|
|
18812
|
+
const id = req.query.id ?? "";
|
|
18804
18813
|
const validation = Joi7.object({
|
|
18805
18814
|
search: Joi7.string().optional().allow("", null),
|
|
18806
18815
|
page: Joi7.number().required(),
|
|
18807
18816
|
limit: Joi7.number().required(),
|
|
18808
18817
|
type: Joi7.string().optional().allow("", null),
|
|
18809
|
-
|
|
18818
|
+
id: Joi7.string().hex().optional().allow("", null)
|
|
18810
18819
|
});
|
|
18811
|
-
const { error } = validation.validate({ search, page, limit, type,
|
|
18820
|
+
const { error } = validation.validate({ search, page, limit, type, id });
|
|
18812
18821
|
if (error) {
|
|
18813
18822
|
next(new BadRequestError16(error.message));
|
|
18814
18823
|
return;
|
|
18815
18824
|
}
|
|
18816
18825
|
try {
|
|
18817
|
-
const data = await _getRoles({ search, page, limit, type,
|
|
18826
|
+
const data = await _getRoles({ search, page, limit, type, id });
|
|
18818
18827
|
res.json(data);
|
|
18819
18828
|
return;
|
|
18820
18829
|
} catch (error2) {
|
|
@@ -21731,7 +21740,7 @@ function useSubscriptionService() {
|
|
|
21731
21740
|
const roleName = "owner";
|
|
21732
21741
|
const role = await addRole(
|
|
21733
21742
|
{
|
|
21734
|
-
|
|
21743
|
+
id: orgId.toString(),
|
|
21735
21744
|
name: roleName,
|
|
21736
21745
|
permissions: ["*"],
|
|
21737
21746
|
type: "organization",
|
|
@@ -21920,7 +21929,7 @@ function useSubscriptionService() {
|
|
|
21920
21929
|
}
|
|
21921
21930
|
const role = await addRole(
|
|
21922
21931
|
{
|
|
21923
|
-
|
|
21932
|
+
id: orgId.toString(),
|
|
21924
21933
|
name: "owner",
|
|
21925
21934
|
permissions: ["*"],
|
|
21926
21935
|
type: "organization",
|
|
@@ -24174,7 +24183,7 @@ function useOrgService() {
|
|
|
24174
24183
|
);
|
|
24175
24184
|
const role = await addRole(
|
|
24176
24185
|
{
|
|
24177
|
-
org,
|
|
24186
|
+
id: org,
|
|
24178
24187
|
name: "Owner",
|
|
24179
24188
|
description: "Owner of the organization",
|
|
24180
24189
|
permissions: ["all"]
|
|
@@ -25335,6 +25344,1712 @@ function usePriceController() {
|
|
|
25335
25344
|
getByNameType
|
|
25336
25345
|
};
|
|
25337
25346
|
}
|
|
25347
|
+
|
|
25348
|
+
// src/models/region.model.ts
|
|
25349
|
+
import { BadRequestError as BadRequestError50 } from "@eeplatform/nodejs-utils";
|
|
25350
|
+
import Joi27 from "joi";
|
|
25351
|
+
import { ObjectId as ObjectId33 } from "mongodb";
|
|
25352
|
+
var schemaRegion = Joi27.object({
|
|
25353
|
+
_id: Joi27.string().hex().optional().allow(null, ""),
|
|
25354
|
+
name: Joi27.string().min(1).max(100).required(),
|
|
25355
|
+
director: Joi27.string().hex().optional().allow(null, ""),
|
|
25356
|
+
directorName: Joi27.string().min(1).max(100).optional().allow(null, ""),
|
|
25357
|
+
createdAt: Joi27.string().isoDate().optional(),
|
|
25358
|
+
updatedAt: Joi27.string().isoDate().optional(),
|
|
25359
|
+
deletedAt: Joi27.string().isoDate().optional().allow(null, "")
|
|
25360
|
+
});
|
|
25361
|
+
function MRegion(value) {
|
|
25362
|
+
const { error } = schemaRegion.validate(value);
|
|
25363
|
+
if (error) {
|
|
25364
|
+
throw new BadRequestError50(`Invalid region data: ${error.message}`);
|
|
25365
|
+
}
|
|
25366
|
+
if (value._id && typeof value._id === "string") {
|
|
25367
|
+
try {
|
|
25368
|
+
value._id = new ObjectId33(value._id);
|
|
25369
|
+
} catch (error2) {
|
|
25370
|
+
throw new Error("Invalid _id.");
|
|
25371
|
+
}
|
|
25372
|
+
}
|
|
25373
|
+
if (value.director && typeof value.director === "string") {
|
|
25374
|
+
try {
|
|
25375
|
+
value.director = new ObjectId33(value.director);
|
|
25376
|
+
} catch (error2) {
|
|
25377
|
+
throw new Error("Invalid director.");
|
|
25378
|
+
}
|
|
25379
|
+
}
|
|
25380
|
+
return {
|
|
25381
|
+
_id: value._id,
|
|
25382
|
+
name: value.name,
|
|
25383
|
+
director: value.director ?? "",
|
|
25384
|
+
directorName: value.directorName ?? "",
|
|
25385
|
+
createdAt: value.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
25386
|
+
updatedAt: value.updatedAt ?? "",
|
|
25387
|
+
deletedAt: value.deletedAt ?? ""
|
|
25388
|
+
};
|
|
25389
|
+
}
|
|
25390
|
+
|
|
25391
|
+
// src/repositories/region.repository.ts
|
|
25392
|
+
import {
|
|
25393
|
+
AppError as AppError12,
|
|
25394
|
+
BadRequestError as BadRequestError51,
|
|
25395
|
+
InternalServerError as InternalServerError23,
|
|
25396
|
+
logger as logger24,
|
|
25397
|
+
makeCacheKey as makeCacheKey16,
|
|
25398
|
+
paginate as paginate12,
|
|
25399
|
+
useAtlas as useAtlas25,
|
|
25400
|
+
useCache as useCache16
|
|
25401
|
+
} from "@eeplatform/nodejs-utils";
|
|
25402
|
+
import { ObjectId as ObjectId34 } from "mongodb";
|
|
25403
|
+
function useRegionRepo() {
|
|
25404
|
+
const db = useAtlas25.getDb();
|
|
25405
|
+
if (!db) {
|
|
25406
|
+
throw new Error("Unable to connect to server.");
|
|
25407
|
+
}
|
|
25408
|
+
const namespace_collection = "regions";
|
|
25409
|
+
const collection = db.collection(namespace_collection);
|
|
25410
|
+
const { getCache, setCache, delNamespace } = useCache16();
|
|
25411
|
+
async function createIndex() {
|
|
25412
|
+
try {
|
|
25413
|
+
await collection.createIndex([
|
|
25414
|
+
{ name: 1 },
|
|
25415
|
+
{ director: 1 },
|
|
25416
|
+
{ createdAt: 1 }
|
|
25417
|
+
]);
|
|
25418
|
+
} catch (error) {
|
|
25419
|
+
throw new Error("Failed to create index on regions.");
|
|
25420
|
+
}
|
|
25421
|
+
}
|
|
25422
|
+
async function createTextIndex() {
|
|
25423
|
+
try {
|
|
25424
|
+
await collection.createIndex({
|
|
25425
|
+
name: "text",
|
|
25426
|
+
directorName: "text"
|
|
25427
|
+
});
|
|
25428
|
+
} catch (error) {
|
|
25429
|
+
throw new Error(
|
|
25430
|
+
"Failed to create text index on region name and director name."
|
|
25431
|
+
);
|
|
25432
|
+
}
|
|
25433
|
+
}
|
|
25434
|
+
async function createUniqueIndex() {
|
|
25435
|
+
try {
|
|
25436
|
+
await collection.createIndex(
|
|
25437
|
+
{
|
|
25438
|
+
name: 1
|
|
25439
|
+
},
|
|
25440
|
+
{ unique: true }
|
|
25441
|
+
);
|
|
25442
|
+
} catch (error) {
|
|
25443
|
+
throw new Error("Failed to create unique index on region name.");
|
|
25444
|
+
}
|
|
25445
|
+
}
|
|
25446
|
+
function delCachedData() {
|
|
25447
|
+
delNamespace(namespace_collection).then(() => {
|
|
25448
|
+
logger24.log({
|
|
25449
|
+
level: "info",
|
|
25450
|
+
message: `Cache namespace cleared for ${namespace_collection}`
|
|
25451
|
+
});
|
|
25452
|
+
}).catch((err) => {
|
|
25453
|
+
logger24.log({
|
|
25454
|
+
level: "error",
|
|
25455
|
+
message: `Failed to clear cache namespace for ${namespace_collection}: ${err.message}`
|
|
25456
|
+
});
|
|
25457
|
+
});
|
|
25458
|
+
}
|
|
25459
|
+
async function add(value, session) {
|
|
25460
|
+
try {
|
|
25461
|
+
value = MRegion(value);
|
|
25462
|
+
const res = await collection.insertOne(value, { session });
|
|
25463
|
+
delCachedData();
|
|
25464
|
+
return res.insertedId;
|
|
25465
|
+
} catch (error) {
|
|
25466
|
+
logger24.log({
|
|
25467
|
+
level: "error",
|
|
25468
|
+
message: error.message
|
|
25469
|
+
});
|
|
25470
|
+
if (error instanceof AppError12) {
|
|
25471
|
+
throw error;
|
|
25472
|
+
} else {
|
|
25473
|
+
const isDuplicated = error.message.includes("duplicate");
|
|
25474
|
+
if (isDuplicated) {
|
|
25475
|
+
throw new BadRequestError51("Region already exists.");
|
|
25476
|
+
}
|
|
25477
|
+
throw new Error("Failed to create region.");
|
|
25478
|
+
}
|
|
25479
|
+
}
|
|
25480
|
+
}
|
|
25481
|
+
async function getAll({ search = "", page = 1, limit = 10, sort = {} } = {}) {
|
|
25482
|
+
page = page > 0 ? page - 1 : 0;
|
|
25483
|
+
const query = { deletedAt: { $in: ["", null] } };
|
|
25484
|
+
sort = Object.keys(sort).length > 0 ? sort : { _id: 1 };
|
|
25485
|
+
if (search) {
|
|
25486
|
+
query.$text = { $search: search };
|
|
25487
|
+
}
|
|
25488
|
+
const cacheKey = makeCacheKey16(namespace_collection, {
|
|
25489
|
+
search,
|
|
25490
|
+
page,
|
|
25491
|
+
limit,
|
|
25492
|
+
sort: JSON.stringify(sort)
|
|
25493
|
+
});
|
|
25494
|
+
logger24.log({
|
|
25495
|
+
level: "info",
|
|
25496
|
+
message: `Cache key for getAll regions: ${cacheKey}`
|
|
25497
|
+
});
|
|
25498
|
+
try {
|
|
25499
|
+
const cached = await getCache(cacheKey);
|
|
25500
|
+
if (cached) {
|
|
25501
|
+
logger24.log({
|
|
25502
|
+
level: "info",
|
|
25503
|
+
message: `Cache hit for getAll regions: ${cacheKey}`
|
|
25504
|
+
});
|
|
25505
|
+
return cached;
|
|
25506
|
+
}
|
|
25507
|
+
const items = await collection.aggregate([
|
|
25508
|
+
{ $match: query },
|
|
25509
|
+
{ $sort: sort },
|
|
25510
|
+
{ $skip: page * limit },
|
|
25511
|
+
{ $limit: limit },
|
|
25512
|
+
{
|
|
25513
|
+
$project: {
|
|
25514
|
+
_id: 1,
|
|
25515
|
+
name: 1,
|
|
25516
|
+
director: 1,
|
|
25517
|
+
directorName: 1,
|
|
25518
|
+
createdAt: 1,
|
|
25519
|
+
updatedAt: 1
|
|
25520
|
+
}
|
|
25521
|
+
}
|
|
25522
|
+
]).toArray();
|
|
25523
|
+
const length = await collection.countDocuments(query);
|
|
25524
|
+
const data = paginate12(items, page, limit, length);
|
|
25525
|
+
setCache(cacheKey, data, 600, namespace_collection).then(() => {
|
|
25526
|
+
logger24.log({
|
|
25527
|
+
level: "info",
|
|
25528
|
+
message: `Cache set for getAll regions: ${cacheKey}`
|
|
25529
|
+
});
|
|
25530
|
+
}).catch((err) => {
|
|
25531
|
+
logger24.log({
|
|
25532
|
+
level: "error",
|
|
25533
|
+
message: `Failed to set cache for getAll regions: ${err.message}`
|
|
25534
|
+
});
|
|
25535
|
+
});
|
|
25536
|
+
return data;
|
|
25537
|
+
} catch (error) {
|
|
25538
|
+
logger24.log({ level: "error", message: `${error}` });
|
|
25539
|
+
throw error;
|
|
25540
|
+
}
|
|
25541
|
+
}
|
|
25542
|
+
async function getById(_id) {
|
|
25543
|
+
try {
|
|
25544
|
+
_id = new ObjectId34(_id);
|
|
25545
|
+
} catch (error) {
|
|
25546
|
+
throw new BadRequestError51("Invalid ID.");
|
|
25547
|
+
}
|
|
25548
|
+
const cacheKey = makeCacheKey16(namespace_collection, { _id: String(_id) });
|
|
25549
|
+
try {
|
|
25550
|
+
const cached = await getCache(cacheKey);
|
|
25551
|
+
if (cached) {
|
|
25552
|
+
logger24.log({
|
|
25553
|
+
level: "info",
|
|
25554
|
+
message: `Cache hit for getById region: ${cacheKey}`
|
|
25555
|
+
});
|
|
25556
|
+
return cached;
|
|
25557
|
+
}
|
|
25558
|
+
const result = await collection.findOne({
|
|
25559
|
+
_id,
|
|
25560
|
+
deletedAt: { $in: ["", null] }
|
|
25561
|
+
});
|
|
25562
|
+
if (!result) {
|
|
25563
|
+
throw new BadRequestError51("Region not found.");
|
|
25564
|
+
}
|
|
25565
|
+
setCache(cacheKey, result, 300, namespace_collection).then(() => {
|
|
25566
|
+
logger24.log({
|
|
25567
|
+
level: "info",
|
|
25568
|
+
message: `Cache set for region by id: ${cacheKey}`
|
|
25569
|
+
});
|
|
25570
|
+
}).catch((err) => {
|
|
25571
|
+
logger24.log({
|
|
25572
|
+
level: "error",
|
|
25573
|
+
message: `Failed to set cache for region by id: ${err.message}`
|
|
25574
|
+
});
|
|
25575
|
+
});
|
|
25576
|
+
return result;
|
|
25577
|
+
} catch (error) {
|
|
25578
|
+
if (error instanceof AppError12) {
|
|
25579
|
+
throw error;
|
|
25580
|
+
} else {
|
|
25581
|
+
throw new InternalServerError23("Failed to get region.");
|
|
25582
|
+
}
|
|
25583
|
+
}
|
|
25584
|
+
}
|
|
25585
|
+
async function getByName(name) {
|
|
25586
|
+
const cacheKey = makeCacheKey16(namespace_collection, { name });
|
|
25587
|
+
try {
|
|
25588
|
+
const cached = await getCache(cacheKey);
|
|
25589
|
+
if (cached) {
|
|
25590
|
+
logger24.log({
|
|
25591
|
+
level: "info",
|
|
25592
|
+
message: `Cache hit for getByName region: ${cacheKey}`
|
|
25593
|
+
});
|
|
25594
|
+
return cached;
|
|
25595
|
+
}
|
|
25596
|
+
const result = await collection.findOne({
|
|
25597
|
+
name,
|
|
25598
|
+
deletedAt: { $in: ["", null] }
|
|
25599
|
+
});
|
|
25600
|
+
if (!result) {
|
|
25601
|
+
throw new BadRequestError51("Region not found.");
|
|
25602
|
+
}
|
|
25603
|
+
setCache(cacheKey, result, 300, namespace_collection).then(() => {
|
|
25604
|
+
logger24.log({
|
|
25605
|
+
level: "info",
|
|
25606
|
+
message: `Cache set for region by name: ${cacheKey}`
|
|
25607
|
+
});
|
|
25608
|
+
}).catch((err) => {
|
|
25609
|
+
logger24.log({
|
|
25610
|
+
level: "error",
|
|
25611
|
+
message: `Failed to set cache for region by name: ${err.message}`
|
|
25612
|
+
});
|
|
25613
|
+
});
|
|
25614
|
+
return result;
|
|
25615
|
+
} catch (error) {
|
|
25616
|
+
if (error instanceof AppError12) {
|
|
25617
|
+
throw error;
|
|
25618
|
+
} else {
|
|
25619
|
+
throw new InternalServerError23("Failed to get region.");
|
|
25620
|
+
}
|
|
25621
|
+
}
|
|
25622
|
+
}
|
|
25623
|
+
async function updateFieldById({ _id, field, value } = {}, session) {
|
|
25624
|
+
const allowedFields = ["name", "director", "directorName"];
|
|
25625
|
+
if (!allowedFields.includes(field)) {
|
|
25626
|
+
throw new BadRequestError51(
|
|
25627
|
+
`Field "${field}" is not allowed to be updated.`
|
|
25628
|
+
);
|
|
25629
|
+
}
|
|
25630
|
+
try {
|
|
25631
|
+
_id = new ObjectId34(_id);
|
|
25632
|
+
} catch (error) {
|
|
25633
|
+
throw new BadRequestError51("Invalid ID.");
|
|
25634
|
+
}
|
|
25635
|
+
try {
|
|
25636
|
+
await collection.updateOne(
|
|
25637
|
+
{ _id, deletedAt: { $in: ["", null] } },
|
|
25638
|
+
{ $set: { [field]: value, updatedAt: (/* @__PURE__ */ new Date()).toISOString() } },
|
|
25639
|
+
{ session }
|
|
25640
|
+
);
|
|
25641
|
+
delCachedData();
|
|
25642
|
+
return `Successfully updated region ${field}.`;
|
|
25643
|
+
} catch (error) {
|
|
25644
|
+
throw new InternalServerError23(`Failed to update region ${field}.`);
|
|
25645
|
+
}
|
|
25646
|
+
}
|
|
25647
|
+
async function deleteById(_id) {
|
|
25648
|
+
try {
|
|
25649
|
+
_id = new ObjectId34(_id);
|
|
25650
|
+
} catch (error) {
|
|
25651
|
+
throw new BadRequestError51("Invalid ID.");
|
|
25652
|
+
}
|
|
25653
|
+
try {
|
|
25654
|
+
await collection.updateOne(
|
|
25655
|
+
{ _id },
|
|
25656
|
+
{ $set: { deletedAt: (/* @__PURE__ */ new Date()).toISOString() } }
|
|
25657
|
+
);
|
|
25658
|
+
delCachedData();
|
|
25659
|
+
return "Successfully deleted region.";
|
|
25660
|
+
} catch (error) {
|
|
25661
|
+
throw new InternalServerError23("Failed to delete region.");
|
|
25662
|
+
}
|
|
25663
|
+
}
|
|
25664
|
+
return {
|
|
25665
|
+
createIndex,
|
|
25666
|
+
createTextIndex,
|
|
25667
|
+
createUniqueIndex,
|
|
25668
|
+
add,
|
|
25669
|
+
getAll,
|
|
25670
|
+
getById,
|
|
25671
|
+
updateFieldById,
|
|
25672
|
+
deleteById,
|
|
25673
|
+
getByName
|
|
25674
|
+
};
|
|
25675
|
+
}
|
|
25676
|
+
|
|
25677
|
+
// src/controllers/region.controller.ts
|
|
25678
|
+
import { BadRequestError as BadRequestError52 } from "@eeplatform/nodejs-utils";
|
|
25679
|
+
import Joi28 from "joi";
|
|
25680
|
+
|
|
25681
|
+
// src/services/region.service.ts
|
|
25682
|
+
import { useAtlas as useAtlas26 } from "@eeplatform/nodejs-utils";
|
|
25683
|
+
function useRegionService() {
|
|
25684
|
+
const { add: _add } = useRegionRepo();
|
|
25685
|
+
const { addRole } = useRoleRepo();
|
|
25686
|
+
async function add(value) {
|
|
25687
|
+
const session = useAtlas26.getClient()?.startSession();
|
|
25688
|
+
if (!session) {
|
|
25689
|
+
throw new Error("Unable to start session for region service.");
|
|
25690
|
+
}
|
|
25691
|
+
try {
|
|
25692
|
+
session.startTransaction();
|
|
25693
|
+
const region = await _add(value, session);
|
|
25694
|
+
await addRole(
|
|
25695
|
+
{
|
|
25696
|
+
id: region.toString(),
|
|
25697
|
+
name: "Admin",
|
|
25698
|
+
type: "region",
|
|
25699
|
+
permissions: ["*"],
|
|
25700
|
+
status: "active",
|
|
25701
|
+
default: true
|
|
25702
|
+
},
|
|
25703
|
+
session
|
|
25704
|
+
);
|
|
25705
|
+
await session.commitTransaction();
|
|
25706
|
+
return "Region and admin role created successfully.";
|
|
25707
|
+
} catch (error) {
|
|
25708
|
+
await session.abortTransaction();
|
|
25709
|
+
throw error;
|
|
25710
|
+
} finally {
|
|
25711
|
+
session.endSession();
|
|
25712
|
+
}
|
|
25713
|
+
}
|
|
25714
|
+
return {
|
|
25715
|
+
add
|
|
25716
|
+
};
|
|
25717
|
+
}
|
|
25718
|
+
|
|
25719
|
+
// src/controllers/region.controller.ts
|
|
25720
|
+
function useRegionController() {
|
|
25721
|
+
const {
|
|
25722
|
+
getAll: _getAll,
|
|
25723
|
+
getById: _getById,
|
|
25724
|
+
getByName: _getByName,
|
|
25725
|
+
updateFieldById: _updateFieldById,
|
|
25726
|
+
deleteById: _deleteById
|
|
25727
|
+
} = useRegionRepo();
|
|
25728
|
+
const { add: _createRegion } = useRegionService();
|
|
25729
|
+
async function createRegion(req, res, next) {
|
|
25730
|
+
const value = req.body;
|
|
25731
|
+
const validation = Joi28.object({
|
|
25732
|
+
name: Joi28.string().min(1).max(100).required(),
|
|
25733
|
+
director: Joi28.string().hex().optional().allow("", null),
|
|
25734
|
+
directorName: Joi28.string().min(1).max(100).optional().allow("", null)
|
|
25735
|
+
});
|
|
25736
|
+
const { error } = validation.validate(value);
|
|
25737
|
+
if (error) {
|
|
25738
|
+
next(new BadRequestError52(error.message));
|
|
25739
|
+
return;
|
|
25740
|
+
}
|
|
25741
|
+
try {
|
|
25742
|
+
const regionId = await _createRegion(value);
|
|
25743
|
+
res.json({
|
|
25744
|
+
message: "Successfully created region.",
|
|
25745
|
+
data: { regionId }
|
|
25746
|
+
});
|
|
25747
|
+
return;
|
|
25748
|
+
} catch (error2) {
|
|
25749
|
+
next(error2);
|
|
25750
|
+
}
|
|
25751
|
+
}
|
|
25752
|
+
async function getAll(req, res, next) {
|
|
25753
|
+
const query = req.query;
|
|
25754
|
+
const validation = Joi28.object({
|
|
25755
|
+
page: Joi28.number().min(1).optional().allow("", null),
|
|
25756
|
+
limit: Joi28.number().min(1).optional().allow("", null),
|
|
25757
|
+
search: Joi28.string().optional().allow("", null)
|
|
25758
|
+
});
|
|
25759
|
+
const { error } = validation.validate(query);
|
|
25760
|
+
const page = typeof req.query.page === "string" ? Number(req.query.page) : 1;
|
|
25761
|
+
const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : 10;
|
|
25762
|
+
const search = req.query.search ?? "";
|
|
25763
|
+
const isPageNumber = isFinite(page);
|
|
25764
|
+
if (!isPageNumber) {
|
|
25765
|
+
next(new BadRequestError52("Invalid page number."));
|
|
25766
|
+
return;
|
|
25767
|
+
}
|
|
25768
|
+
const isLimitNumber = isFinite(limit);
|
|
25769
|
+
if (!isLimitNumber) {
|
|
25770
|
+
next(new BadRequestError52("Invalid limit number."));
|
|
25771
|
+
return;
|
|
25772
|
+
}
|
|
25773
|
+
if (error) {
|
|
25774
|
+
next(new BadRequestError52(error.message));
|
|
25775
|
+
return;
|
|
25776
|
+
}
|
|
25777
|
+
try {
|
|
25778
|
+
const regions = await _getAll({ page, limit, search });
|
|
25779
|
+
res.json(regions);
|
|
25780
|
+
return;
|
|
25781
|
+
} catch (error2) {
|
|
25782
|
+
next(error2);
|
|
25783
|
+
}
|
|
25784
|
+
}
|
|
25785
|
+
async function getById(req, res, next) {
|
|
25786
|
+
const id = req.params.id;
|
|
25787
|
+
const validation = Joi28.object({
|
|
25788
|
+
id: Joi28.string().hex().required()
|
|
25789
|
+
});
|
|
25790
|
+
const { error } = validation.validate({ id });
|
|
25791
|
+
if (error) {
|
|
25792
|
+
next(new BadRequestError52(error.message));
|
|
25793
|
+
return;
|
|
25794
|
+
}
|
|
25795
|
+
try {
|
|
25796
|
+
const region = await _getById(id);
|
|
25797
|
+
res.json({
|
|
25798
|
+
message: "Successfully retrieved region.",
|
|
25799
|
+
data: { region }
|
|
25800
|
+
});
|
|
25801
|
+
return;
|
|
25802
|
+
} catch (error2) {
|
|
25803
|
+
next(error2);
|
|
25804
|
+
}
|
|
25805
|
+
}
|
|
25806
|
+
async function getByName(req, res, next) {
|
|
25807
|
+
const name = req.params.name;
|
|
25808
|
+
const validation = Joi28.object({
|
|
25809
|
+
name: Joi28.string().required()
|
|
25810
|
+
});
|
|
25811
|
+
const { error } = validation.validate({ name });
|
|
25812
|
+
if (error) {
|
|
25813
|
+
next(new BadRequestError52(error.message));
|
|
25814
|
+
return;
|
|
25815
|
+
}
|
|
25816
|
+
try {
|
|
25817
|
+
const region = await _getByName(name);
|
|
25818
|
+
res.json({
|
|
25819
|
+
message: "Successfully retrieved region.",
|
|
25820
|
+
data: { region }
|
|
25821
|
+
});
|
|
25822
|
+
return;
|
|
25823
|
+
} catch (error2) {
|
|
25824
|
+
next(error2);
|
|
25825
|
+
}
|
|
25826
|
+
}
|
|
25827
|
+
async function updateField(req, res, next) {
|
|
25828
|
+
const _id = req.params.id;
|
|
25829
|
+
const { field, value } = req.body;
|
|
25830
|
+
const validation = Joi28.object({
|
|
25831
|
+
_id: Joi28.string().hex().required(),
|
|
25832
|
+
field: Joi28.string().valid("name", "director", "directorName").required(),
|
|
25833
|
+
value: Joi28.string().required()
|
|
25834
|
+
});
|
|
25835
|
+
const { error } = validation.validate({ _id, field, value });
|
|
25836
|
+
if (error) {
|
|
25837
|
+
next(new BadRequestError52(error.message));
|
|
25838
|
+
return;
|
|
25839
|
+
}
|
|
25840
|
+
try {
|
|
25841
|
+
const message = await _updateFieldById({ _id, field, value });
|
|
25842
|
+
res.json({ message });
|
|
25843
|
+
return;
|
|
25844
|
+
} catch (error2) {
|
|
25845
|
+
next(error2);
|
|
25846
|
+
}
|
|
25847
|
+
}
|
|
25848
|
+
async function deleteRegion(req, res, next) {
|
|
25849
|
+
const _id = req.params.id;
|
|
25850
|
+
const validation = Joi28.object({
|
|
25851
|
+
_id: Joi28.string().hex().required()
|
|
25852
|
+
});
|
|
25853
|
+
const { error } = validation.validate({ _id });
|
|
25854
|
+
if (error) {
|
|
25855
|
+
next(new BadRequestError52(error.message));
|
|
25856
|
+
return;
|
|
25857
|
+
}
|
|
25858
|
+
try {
|
|
25859
|
+
const message = await _deleteById(_id);
|
|
25860
|
+
res.json({ message });
|
|
25861
|
+
return;
|
|
25862
|
+
} catch (error2) {
|
|
25863
|
+
next(error2);
|
|
25864
|
+
}
|
|
25865
|
+
}
|
|
25866
|
+
return {
|
|
25867
|
+
createRegion,
|
|
25868
|
+
getAll,
|
|
25869
|
+
getById,
|
|
25870
|
+
getByName,
|
|
25871
|
+
updateField,
|
|
25872
|
+
deleteRegion
|
|
25873
|
+
};
|
|
25874
|
+
}
|
|
25875
|
+
|
|
25876
|
+
// src/models/division.model.ts
|
|
25877
|
+
import { BadRequestError as BadRequestError53 } from "@eeplatform/nodejs-utils";
|
|
25878
|
+
import Joi29 from "joi";
|
|
25879
|
+
import { ObjectId as ObjectId35 } from "mongodb";
|
|
25880
|
+
var schemaDivision = Joi29.object({
|
|
25881
|
+
_id: Joi29.string().hex().optional().allow(null, ""),
|
|
25882
|
+
name: Joi29.string().min(1).max(100).required(),
|
|
25883
|
+
region: Joi29.string().hex().optional().allow(null, ""),
|
|
25884
|
+
regionName: Joi29.string().min(1).max(100).optional().allow(null, ""),
|
|
25885
|
+
superintendent: Joi29.string().hex().optional().allow(null, ""),
|
|
25886
|
+
superintendentName: Joi29.string().min(1).max(100).optional().allow(null, ""),
|
|
25887
|
+
createdAt: Joi29.string().isoDate().optional(),
|
|
25888
|
+
updatedAt: Joi29.string().isoDate().optional(),
|
|
25889
|
+
deletedAt: Joi29.string().isoDate().optional().allow(null, "")
|
|
25890
|
+
});
|
|
25891
|
+
function MDivision(value) {
|
|
25892
|
+
const { error } = schemaDivision.validate(value);
|
|
25893
|
+
if (error) {
|
|
25894
|
+
throw new BadRequestError53(`Invalid division data: ${error.message}`);
|
|
25895
|
+
}
|
|
25896
|
+
if (value._id && typeof value._id === "string") {
|
|
25897
|
+
try {
|
|
25898
|
+
value._id = new ObjectId35(value._id);
|
|
25899
|
+
} catch (error2) {
|
|
25900
|
+
throw new Error("Invalid _id.");
|
|
25901
|
+
}
|
|
25902
|
+
}
|
|
25903
|
+
if (value.region && typeof value.region === "string") {
|
|
25904
|
+
try {
|
|
25905
|
+
value.region = new ObjectId35(value.region);
|
|
25906
|
+
} catch (error2) {
|
|
25907
|
+
throw new Error("Invalid region.");
|
|
25908
|
+
}
|
|
25909
|
+
}
|
|
25910
|
+
if (value.superintendent && typeof value.superintendent === "string") {
|
|
25911
|
+
try {
|
|
25912
|
+
value.superintendent = new ObjectId35(value.superintendent);
|
|
25913
|
+
} catch (error2) {
|
|
25914
|
+
throw new Error("Invalid superintendent.");
|
|
25915
|
+
}
|
|
25916
|
+
}
|
|
25917
|
+
return {
|
|
25918
|
+
_id: value._id,
|
|
25919
|
+
name: value.name,
|
|
25920
|
+
region: value.region ?? "",
|
|
25921
|
+
regionName: value.regionName ?? "",
|
|
25922
|
+
superintendent: value.superintendent ?? "",
|
|
25923
|
+
superintendentName: value.superintendentName ?? "",
|
|
25924
|
+
createdAt: value.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
25925
|
+
updatedAt: value.updatedAt ?? "",
|
|
25926
|
+
deletedAt: value.deletedAt ?? ""
|
|
25927
|
+
};
|
|
25928
|
+
}
|
|
25929
|
+
|
|
25930
|
+
// src/repositories/division.repository.ts
|
|
25931
|
+
import {
|
|
25932
|
+
AppError as AppError13,
|
|
25933
|
+
BadRequestError as BadRequestError54,
|
|
25934
|
+
InternalServerError as InternalServerError24,
|
|
25935
|
+
logger as logger25,
|
|
25936
|
+
makeCacheKey as makeCacheKey17,
|
|
25937
|
+
paginate as paginate13,
|
|
25938
|
+
useAtlas as useAtlas27,
|
|
25939
|
+
useCache as useCache17
|
|
25940
|
+
} from "@eeplatform/nodejs-utils";
|
|
25941
|
+
import { ObjectId as ObjectId36 } from "mongodb";
|
|
25942
|
+
function useDivisionRepo() {
|
|
25943
|
+
const db = useAtlas27.getDb();
|
|
25944
|
+
if (!db) {
|
|
25945
|
+
throw new Error("Unable to connect to server.");
|
|
25946
|
+
}
|
|
25947
|
+
const namespace_collection = "divisions";
|
|
25948
|
+
const collection = db.collection(namespace_collection);
|
|
25949
|
+
const { getCache, setCache, delNamespace } = useCache17();
|
|
25950
|
+
async function createIndex() {
|
|
25951
|
+
try {
|
|
25952
|
+
await collection.createIndex([
|
|
25953
|
+
{ name: 1 },
|
|
25954
|
+
{ region: 1 },
|
|
25955
|
+
{ superintendent: 1 },
|
|
25956
|
+
{ createdAt: 1 }
|
|
25957
|
+
]);
|
|
25958
|
+
} catch (error) {
|
|
25959
|
+
throw new Error("Failed to create index on divisions.");
|
|
25960
|
+
}
|
|
25961
|
+
}
|
|
25962
|
+
async function createTextIndex() {
|
|
25963
|
+
try {
|
|
25964
|
+
await collection.createIndex({
|
|
25965
|
+
name: "text",
|
|
25966
|
+
regionName: "text",
|
|
25967
|
+
superintendentName: "text"
|
|
25968
|
+
});
|
|
25969
|
+
} catch (error) {
|
|
25970
|
+
throw new Error(
|
|
25971
|
+
"Failed to create text index on division name and superintendent name."
|
|
25972
|
+
);
|
|
25973
|
+
}
|
|
25974
|
+
}
|
|
25975
|
+
async function createUniqueIndex() {
|
|
25976
|
+
try {
|
|
25977
|
+
await collection.createIndex(
|
|
25978
|
+
{
|
|
25979
|
+
name: 1
|
|
25980
|
+
},
|
|
25981
|
+
{ unique: true }
|
|
25982
|
+
);
|
|
25983
|
+
} catch (error) {
|
|
25984
|
+
throw new Error("Failed to create unique index on division name.");
|
|
25985
|
+
}
|
|
25986
|
+
}
|
|
25987
|
+
function delCachedData() {
|
|
25988
|
+
delNamespace(namespace_collection).then(() => {
|
|
25989
|
+
logger25.log({
|
|
25990
|
+
level: "info",
|
|
25991
|
+
message: `Cache namespace cleared for ${namespace_collection}`
|
|
25992
|
+
});
|
|
25993
|
+
}).catch((err) => {
|
|
25994
|
+
logger25.log({
|
|
25995
|
+
level: "error",
|
|
25996
|
+
message: `Failed to clear cache namespace for ${namespace_collection}: ${err.message}`
|
|
25997
|
+
});
|
|
25998
|
+
});
|
|
25999
|
+
}
|
|
26000
|
+
async function add(value, session) {
|
|
26001
|
+
try {
|
|
26002
|
+
value = MDivision(value);
|
|
26003
|
+
const res = await collection.insertOne(value, { session });
|
|
26004
|
+
delCachedData();
|
|
26005
|
+
return res.insertedId;
|
|
26006
|
+
} catch (error) {
|
|
26007
|
+
logger25.log({
|
|
26008
|
+
level: "error",
|
|
26009
|
+
message: error.message
|
|
26010
|
+
});
|
|
26011
|
+
if (error instanceof AppError13) {
|
|
26012
|
+
throw error;
|
|
26013
|
+
} else {
|
|
26014
|
+
const isDuplicated = error.message.includes("duplicate");
|
|
26015
|
+
if (isDuplicated) {
|
|
26016
|
+
throw new BadRequestError54("Division already exists.");
|
|
26017
|
+
}
|
|
26018
|
+
throw new Error("Failed to create division.");
|
|
26019
|
+
}
|
|
26020
|
+
}
|
|
26021
|
+
}
|
|
26022
|
+
async function getAll({
|
|
26023
|
+
search = "",
|
|
26024
|
+
page = 1,
|
|
26025
|
+
limit = 10,
|
|
26026
|
+
sort = {},
|
|
26027
|
+
region = ""
|
|
26028
|
+
} = {}) {
|
|
26029
|
+
page = page > 0 ? page - 1 : 0;
|
|
26030
|
+
const query = { deletedAt: { $in: ["", null] } };
|
|
26031
|
+
sort = Object.keys(sort).length > 0 ? sort : { _id: -1 };
|
|
26032
|
+
if (search) {
|
|
26033
|
+
query.$text = { $search: search };
|
|
26034
|
+
}
|
|
26035
|
+
if (region) {
|
|
26036
|
+
try {
|
|
26037
|
+
query.region = new ObjectId36(region);
|
|
26038
|
+
} catch (error) {
|
|
26039
|
+
throw new BadRequestError54("Invalid region ID.");
|
|
26040
|
+
}
|
|
26041
|
+
}
|
|
26042
|
+
const cacheKey = makeCacheKey17(namespace_collection, {
|
|
26043
|
+
search,
|
|
26044
|
+
page,
|
|
26045
|
+
limit,
|
|
26046
|
+
sort: JSON.stringify(sort),
|
|
26047
|
+
region
|
|
26048
|
+
});
|
|
26049
|
+
logger25.log({
|
|
26050
|
+
level: "info",
|
|
26051
|
+
message: `Cache key for getAll divisions: ${cacheKey}`
|
|
26052
|
+
});
|
|
26053
|
+
try {
|
|
26054
|
+
const cached = await getCache(cacheKey);
|
|
26055
|
+
if (cached) {
|
|
26056
|
+
logger25.log({
|
|
26057
|
+
level: "info",
|
|
26058
|
+
message: `Cache hit for getAll divisions: ${cacheKey}`
|
|
26059
|
+
});
|
|
26060
|
+
return cached;
|
|
26061
|
+
}
|
|
26062
|
+
const items = await collection.aggregate([
|
|
26063
|
+
{ $match: query },
|
|
26064
|
+
{ $sort: sort },
|
|
26065
|
+
{ $skip: page * limit },
|
|
26066
|
+
{ $limit: limit },
|
|
26067
|
+
{
|
|
26068
|
+
$project: {
|
|
26069
|
+
_id: 1,
|
|
26070
|
+
name: 1,
|
|
26071
|
+
region: 1,
|
|
26072
|
+
regionName: 1,
|
|
26073
|
+
superintendent: 1,
|
|
26074
|
+
superintendentName: 1,
|
|
26075
|
+
createdAt: 1,
|
|
26076
|
+
updatedAt: 1
|
|
26077
|
+
}
|
|
26078
|
+
}
|
|
26079
|
+
]).toArray();
|
|
26080
|
+
const length = await collection.countDocuments(query);
|
|
26081
|
+
const data = paginate13(items, page, limit, length);
|
|
26082
|
+
setCache(cacheKey, data, 600, namespace_collection).then(() => {
|
|
26083
|
+
logger25.log({
|
|
26084
|
+
level: "info",
|
|
26085
|
+
message: `Cache set for getAll divisions: ${cacheKey}`
|
|
26086
|
+
});
|
|
26087
|
+
}).catch((err) => {
|
|
26088
|
+
logger25.log({
|
|
26089
|
+
level: "error",
|
|
26090
|
+
message: `Failed to set cache for getAll divisions: ${err.message}`
|
|
26091
|
+
});
|
|
26092
|
+
});
|
|
26093
|
+
return data;
|
|
26094
|
+
} catch (error) {
|
|
26095
|
+
logger25.log({ level: "error", message: `${error}` });
|
|
26096
|
+
throw error;
|
|
26097
|
+
}
|
|
26098
|
+
}
|
|
26099
|
+
async function getById(_id) {
|
|
26100
|
+
try {
|
|
26101
|
+
_id = new ObjectId36(_id);
|
|
26102
|
+
} catch (error) {
|
|
26103
|
+
throw new BadRequestError54("Invalid ID.");
|
|
26104
|
+
}
|
|
26105
|
+
const cacheKey = makeCacheKey17(namespace_collection, { _id: String(_id) });
|
|
26106
|
+
try {
|
|
26107
|
+
const cached = await getCache(cacheKey);
|
|
26108
|
+
if (cached) {
|
|
26109
|
+
logger25.log({
|
|
26110
|
+
level: "info",
|
|
26111
|
+
message: `Cache hit for getById division: ${cacheKey}`
|
|
26112
|
+
});
|
|
26113
|
+
return cached;
|
|
26114
|
+
}
|
|
26115
|
+
const result = await collection.findOne({
|
|
26116
|
+
_id,
|
|
26117
|
+
deletedAt: { $in: ["", null] }
|
|
26118
|
+
});
|
|
26119
|
+
if (!result) {
|
|
26120
|
+
throw new BadRequestError54("Division not found.");
|
|
26121
|
+
}
|
|
26122
|
+
setCache(cacheKey, result, 300, namespace_collection).then(() => {
|
|
26123
|
+
logger25.log({
|
|
26124
|
+
level: "info",
|
|
26125
|
+
message: `Cache set for division by id: ${cacheKey}`
|
|
26126
|
+
});
|
|
26127
|
+
}).catch((err) => {
|
|
26128
|
+
logger25.log({
|
|
26129
|
+
level: "error",
|
|
26130
|
+
message: `Failed to set cache for division by id: ${err.message}`
|
|
26131
|
+
});
|
|
26132
|
+
});
|
|
26133
|
+
return result;
|
|
26134
|
+
} catch (error) {
|
|
26135
|
+
if (error instanceof AppError13) {
|
|
26136
|
+
throw error;
|
|
26137
|
+
} else {
|
|
26138
|
+
throw new InternalServerError24("Failed to get division.");
|
|
26139
|
+
}
|
|
26140
|
+
}
|
|
26141
|
+
}
|
|
26142
|
+
async function getByName(name) {
|
|
26143
|
+
const cacheKey = makeCacheKey17(namespace_collection, { name });
|
|
26144
|
+
try {
|
|
26145
|
+
const cached = await getCache(cacheKey);
|
|
26146
|
+
if (cached) {
|
|
26147
|
+
logger25.log({
|
|
26148
|
+
level: "info",
|
|
26149
|
+
message: `Cache hit for getByName division: ${cacheKey}`
|
|
26150
|
+
});
|
|
26151
|
+
return cached;
|
|
26152
|
+
}
|
|
26153
|
+
const result = await collection.findOne({
|
|
26154
|
+
name,
|
|
26155
|
+
deletedAt: { $in: ["", null] }
|
|
26156
|
+
});
|
|
26157
|
+
if (!result) {
|
|
26158
|
+
throw new BadRequestError54("Division not found.");
|
|
26159
|
+
}
|
|
26160
|
+
setCache(cacheKey, result, 300, namespace_collection).then(() => {
|
|
26161
|
+
logger25.log({
|
|
26162
|
+
level: "info",
|
|
26163
|
+
message: `Cache set for division by name: ${cacheKey}`
|
|
26164
|
+
});
|
|
26165
|
+
}).catch((err) => {
|
|
26166
|
+
logger25.log({
|
|
26167
|
+
level: "error",
|
|
26168
|
+
message: `Failed to set cache for division by name: ${err.message}`
|
|
26169
|
+
});
|
|
26170
|
+
});
|
|
26171
|
+
return result;
|
|
26172
|
+
} catch (error) {
|
|
26173
|
+
if (error instanceof AppError13) {
|
|
26174
|
+
throw error;
|
|
26175
|
+
} else {
|
|
26176
|
+
throw new InternalServerError24("Failed to get division.");
|
|
26177
|
+
}
|
|
26178
|
+
}
|
|
26179
|
+
}
|
|
26180
|
+
async function updateFieldById({ _id, field, value } = {}, session) {
|
|
26181
|
+
const allowedFields = [
|
|
26182
|
+
"name",
|
|
26183
|
+
"region",
|
|
26184
|
+
"regionName",
|
|
26185
|
+
"superintendent",
|
|
26186
|
+
"superintendentName"
|
|
26187
|
+
];
|
|
26188
|
+
if (!allowedFields.includes(field)) {
|
|
26189
|
+
throw new BadRequestError54(
|
|
26190
|
+
`Field "${field}" is not allowed to be updated.`
|
|
26191
|
+
);
|
|
26192
|
+
}
|
|
26193
|
+
try {
|
|
26194
|
+
_id = new ObjectId36(_id);
|
|
26195
|
+
} catch (error) {
|
|
26196
|
+
throw new BadRequestError54("Invalid ID.");
|
|
26197
|
+
}
|
|
26198
|
+
try {
|
|
26199
|
+
await collection.updateOne(
|
|
26200
|
+
{ _id, deletedAt: { $in: ["", null] } },
|
|
26201
|
+
{ $set: { [field]: value, updatedAt: (/* @__PURE__ */ new Date()).toISOString() } },
|
|
26202
|
+
{ session }
|
|
26203
|
+
);
|
|
26204
|
+
delCachedData();
|
|
26205
|
+
return `Successfully updated division ${field}.`;
|
|
26206
|
+
} catch (error) {
|
|
26207
|
+
throw new InternalServerError24(`Failed to update division ${field}.`);
|
|
26208
|
+
}
|
|
26209
|
+
}
|
|
26210
|
+
async function deleteById(_id) {
|
|
26211
|
+
try {
|
|
26212
|
+
_id = new ObjectId36(_id);
|
|
26213
|
+
} catch (error) {
|
|
26214
|
+
throw new BadRequestError54("Invalid ID.");
|
|
26215
|
+
}
|
|
26216
|
+
try {
|
|
26217
|
+
await collection.updateOne(
|
|
26218
|
+
{ _id },
|
|
26219
|
+
{ $set: { deletedAt: (/* @__PURE__ */ new Date()).toISOString() } }
|
|
26220
|
+
);
|
|
26221
|
+
delCachedData();
|
|
26222
|
+
return "Successfully deleted division.";
|
|
26223
|
+
} catch (error) {
|
|
26224
|
+
throw new InternalServerError24("Failed to delete division.");
|
|
26225
|
+
}
|
|
26226
|
+
}
|
|
26227
|
+
return {
|
|
26228
|
+
createIndex,
|
|
26229
|
+
createTextIndex,
|
|
26230
|
+
createUniqueIndex,
|
|
26231
|
+
add,
|
|
26232
|
+
getAll,
|
|
26233
|
+
getById,
|
|
26234
|
+
updateFieldById,
|
|
26235
|
+
deleteById,
|
|
26236
|
+
getByName
|
|
26237
|
+
};
|
|
26238
|
+
}
|
|
26239
|
+
|
|
26240
|
+
// src/controllers/division.controller.ts
|
|
26241
|
+
import { BadRequestError as BadRequestError55 } from "@eeplatform/nodejs-utils";
|
|
26242
|
+
import Joi30 from "joi";
|
|
26243
|
+
|
|
26244
|
+
// src/services/division.service.ts
|
|
26245
|
+
import { useAtlas as useAtlas28 } from "@eeplatform/nodejs-utils";
|
|
26246
|
+
function useDivisionService() {
|
|
26247
|
+
const { add: _add } = useDivisionRepo();
|
|
26248
|
+
const { addRole } = useRoleRepo();
|
|
26249
|
+
async function add(value) {
|
|
26250
|
+
const session = useAtlas28.getClient()?.startSession();
|
|
26251
|
+
if (!session) {
|
|
26252
|
+
throw new Error("Unable to start session for division service.");
|
|
26253
|
+
}
|
|
26254
|
+
try {
|
|
26255
|
+
session.startTransaction();
|
|
26256
|
+
const division = await _add(value, session);
|
|
26257
|
+
await addRole(
|
|
26258
|
+
{
|
|
26259
|
+
id: division.toString(),
|
|
26260
|
+
name: "Admin",
|
|
26261
|
+
type: "division",
|
|
26262
|
+
permissions: ["*"],
|
|
26263
|
+
status: "active",
|
|
26264
|
+
default: true
|
|
26265
|
+
},
|
|
26266
|
+
session
|
|
26267
|
+
);
|
|
26268
|
+
await session.commitTransaction();
|
|
26269
|
+
return "Division and admin role created successfully.";
|
|
26270
|
+
} catch (error) {
|
|
26271
|
+
session.abortTransaction();
|
|
26272
|
+
throw error;
|
|
26273
|
+
} finally {
|
|
26274
|
+
session.endSession();
|
|
26275
|
+
}
|
|
26276
|
+
}
|
|
26277
|
+
return {
|
|
26278
|
+
add
|
|
26279
|
+
};
|
|
26280
|
+
}
|
|
26281
|
+
|
|
26282
|
+
// src/controllers/division.controller.ts
|
|
26283
|
+
function useDivisionController() {
|
|
26284
|
+
const {
|
|
26285
|
+
getAll: _getAll,
|
|
26286
|
+
getById: _getById,
|
|
26287
|
+
getByName: _getByName,
|
|
26288
|
+
updateFieldById: _updateFieldById,
|
|
26289
|
+
deleteById: _deleteById
|
|
26290
|
+
} = useDivisionRepo();
|
|
26291
|
+
const { add: _createDivision } = useDivisionService();
|
|
26292
|
+
async function createDivision(req, res, next) {
|
|
26293
|
+
const value = req.body;
|
|
26294
|
+
const validation = Joi30.object({
|
|
26295
|
+
name: Joi30.string().min(1).max(100).required(),
|
|
26296
|
+
region: Joi30.string().hex().optional().allow("", null),
|
|
26297
|
+
regionName: Joi30.string().min(1).max(100).optional().allow("", null),
|
|
26298
|
+
superintendent: Joi30.string().hex().optional().allow("", null),
|
|
26299
|
+
superintendentName: Joi30.string().min(1).max(100).optional().allow("", null)
|
|
26300
|
+
});
|
|
26301
|
+
const { error } = validation.validate(value);
|
|
26302
|
+
if (error) {
|
|
26303
|
+
next(new BadRequestError55(error.message));
|
|
26304
|
+
return;
|
|
26305
|
+
}
|
|
26306
|
+
try {
|
|
26307
|
+
const divisionId = await _createDivision(value);
|
|
26308
|
+
res.json({
|
|
26309
|
+
message: "Successfully created division.",
|
|
26310
|
+
data: { divisionId }
|
|
26311
|
+
});
|
|
26312
|
+
return;
|
|
26313
|
+
} catch (error2) {
|
|
26314
|
+
next(error2);
|
|
26315
|
+
}
|
|
26316
|
+
}
|
|
26317
|
+
async function getAll(req, res, next) {
|
|
26318
|
+
const query = req.query;
|
|
26319
|
+
const validation = Joi30.object({
|
|
26320
|
+
page: Joi30.number().min(1).optional().allow("", null),
|
|
26321
|
+
limit: Joi30.number().min(1).optional().allow("", null),
|
|
26322
|
+
search: Joi30.string().optional().allow("", null),
|
|
26323
|
+
region: Joi30.string().hex().optional().allow("", null)
|
|
26324
|
+
});
|
|
26325
|
+
const { error } = validation.validate(query);
|
|
26326
|
+
const page = typeof req.query.page === "string" ? Number(req.query.page) : 1;
|
|
26327
|
+
const limit = typeof req.query.limit === "string" ? Number(req.query.limit) : 10;
|
|
26328
|
+
const search = req.query.search ?? "";
|
|
26329
|
+
const region = req.query.region ?? "";
|
|
26330
|
+
const isPageNumber = isFinite(page);
|
|
26331
|
+
if (!isPageNumber) {
|
|
26332
|
+
next(new BadRequestError55("Invalid page number."));
|
|
26333
|
+
return;
|
|
26334
|
+
}
|
|
26335
|
+
const isLimitNumber = isFinite(limit);
|
|
26336
|
+
if (!isLimitNumber) {
|
|
26337
|
+
next(new BadRequestError55("Invalid limit number."));
|
|
26338
|
+
return;
|
|
26339
|
+
}
|
|
26340
|
+
if (error) {
|
|
26341
|
+
next(new BadRequestError55(error.message));
|
|
26342
|
+
return;
|
|
26343
|
+
}
|
|
26344
|
+
try {
|
|
26345
|
+
const divisions = await _getAll({ page, limit, search, region });
|
|
26346
|
+
res.json(divisions);
|
|
26347
|
+
return;
|
|
26348
|
+
} catch (error2) {
|
|
26349
|
+
next(error2);
|
|
26350
|
+
}
|
|
26351
|
+
}
|
|
26352
|
+
async function getById(req, res, next) {
|
|
26353
|
+
const id = req.params.id;
|
|
26354
|
+
const validation = Joi30.object({
|
|
26355
|
+
id: Joi30.string().hex().required()
|
|
26356
|
+
});
|
|
26357
|
+
const { error } = validation.validate({ id });
|
|
26358
|
+
if (error) {
|
|
26359
|
+
next(new BadRequestError55(error.message));
|
|
26360
|
+
return;
|
|
26361
|
+
}
|
|
26362
|
+
try {
|
|
26363
|
+
const division = await _getById(id);
|
|
26364
|
+
res.json({
|
|
26365
|
+
message: "Successfully retrieved division.",
|
|
26366
|
+
data: { division }
|
|
26367
|
+
});
|
|
26368
|
+
return;
|
|
26369
|
+
} catch (error2) {
|
|
26370
|
+
next(error2);
|
|
26371
|
+
}
|
|
26372
|
+
}
|
|
26373
|
+
async function getByName(req, res, next) {
|
|
26374
|
+
const name = req.params.name;
|
|
26375
|
+
const validation = Joi30.object({
|
|
26376
|
+
name: Joi30.string().required()
|
|
26377
|
+
});
|
|
26378
|
+
const { error } = validation.validate({ name });
|
|
26379
|
+
if (error) {
|
|
26380
|
+
next(new BadRequestError55(error.message));
|
|
26381
|
+
return;
|
|
26382
|
+
}
|
|
26383
|
+
try {
|
|
26384
|
+
const division = await _getByName(name);
|
|
26385
|
+
res.json({
|
|
26386
|
+
message: "Successfully retrieved division.",
|
|
26387
|
+
data: { division }
|
|
26388
|
+
});
|
|
26389
|
+
return;
|
|
26390
|
+
} catch (error2) {
|
|
26391
|
+
next(error2);
|
|
26392
|
+
}
|
|
26393
|
+
}
|
|
26394
|
+
async function updateField(req, res, next) {
|
|
26395
|
+
const _id = req.params.id;
|
|
26396
|
+
const { field, value } = req.body;
|
|
26397
|
+
const validation = Joi30.object({
|
|
26398
|
+
_id: Joi30.string().hex().required(),
|
|
26399
|
+
field: Joi30.string().valid(
|
|
26400
|
+
"name",
|
|
26401
|
+
"region",
|
|
26402
|
+
"regionName",
|
|
26403
|
+
"superintendent",
|
|
26404
|
+
"superintendentName"
|
|
26405
|
+
).required(),
|
|
26406
|
+
value: Joi30.string().required()
|
|
26407
|
+
});
|
|
26408
|
+
const { error } = validation.validate({ _id, field, value });
|
|
26409
|
+
if (error) {
|
|
26410
|
+
next(new BadRequestError55(error.message));
|
|
26411
|
+
return;
|
|
26412
|
+
}
|
|
26413
|
+
try {
|
|
26414
|
+
const message = await _updateFieldById({ _id, field, value });
|
|
26415
|
+
res.json({ message });
|
|
26416
|
+
return;
|
|
26417
|
+
} catch (error2) {
|
|
26418
|
+
next(error2);
|
|
26419
|
+
}
|
|
26420
|
+
}
|
|
26421
|
+
async function deleteDivision(req, res, next) {
|
|
26422
|
+
const _id = req.params.id;
|
|
26423
|
+
const validation = Joi30.object({
|
|
26424
|
+
_id: Joi30.string().hex().required()
|
|
26425
|
+
});
|
|
26426
|
+
const { error } = validation.validate({ _id });
|
|
26427
|
+
if (error) {
|
|
26428
|
+
next(new BadRequestError55(error.message));
|
|
26429
|
+
return;
|
|
26430
|
+
}
|
|
26431
|
+
try {
|
|
26432
|
+
const message = await _deleteById(_id);
|
|
26433
|
+
res.json({ message });
|
|
26434
|
+
return;
|
|
26435
|
+
} catch (error2) {
|
|
26436
|
+
next(error2);
|
|
26437
|
+
}
|
|
26438
|
+
}
|
|
26439
|
+
return {
|
|
26440
|
+
createDivision,
|
|
26441
|
+
getAll,
|
|
26442
|
+
getById,
|
|
26443
|
+
getByName,
|
|
26444
|
+
updateField,
|
|
26445
|
+
deleteDivision
|
|
26446
|
+
};
|
|
26447
|
+
}
|
|
26448
|
+
|
|
26449
|
+
// src/models/school.model.ts
|
|
26450
|
+
import Joi31 from "joi";
|
|
26451
|
+
import { ObjectId as ObjectId37 } from "mongodb";
|
|
26452
|
+
var schemaSchool = Joi31.object({
|
|
26453
|
+
_id: Joi31.string().hex().optional().allow("", null),
|
|
26454
|
+
id: Joi31.string().required(),
|
|
26455
|
+
name: Joi31.string().required(),
|
|
26456
|
+
country: Joi31.string().required(),
|
|
26457
|
+
address: Joi31.string().required(),
|
|
26458
|
+
continuedAddress: Joi31.string().optional().allow("", null),
|
|
26459
|
+
city: Joi31.string().required(),
|
|
26460
|
+
province: Joi31.string().required(),
|
|
26461
|
+
postalCode: Joi31.string().required(),
|
|
26462
|
+
courses: Joi31.array().items(Joi31.string()).required(),
|
|
26463
|
+
principalName: Joi31.string().required(),
|
|
26464
|
+
principalEmail: Joi31.string().email().optional().allow("", null),
|
|
26465
|
+
principalNumber: Joi31.string().optional().allow("", null),
|
|
26466
|
+
region: Joi31.string().hex().required(),
|
|
26467
|
+
regionName: Joi31.string().optional().allow("", null),
|
|
26468
|
+
division: Joi31.string().hex().required(),
|
|
26469
|
+
divisionName: Joi31.string().optional().allow("", null),
|
|
26470
|
+
status: Joi31.string().optional().allow(null, ""),
|
|
26471
|
+
createdAt: Joi31.date().optional().allow("", null),
|
|
26472
|
+
updatedAt: Joi31.date().optional().allow("", null),
|
|
26473
|
+
createdBy: Joi31.string().hex().required()
|
|
26474
|
+
});
|
|
26475
|
+
function MSchool(value) {
|
|
26476
|
+
const { error } = schemaSchool.validate(value);
|
|
26477
|
+
if (error) {
|
|
26478
|
+
throw new Error(`Validation error: ${error.message}`);
|
|
26479
|
+
}
|
|
26480
|
+
if (value._id) {
|
|
26481
|
+
try {
|
|
26482
|
+
value._id = new ObjectId37(value._id);
|
|
26483
|
+
} catch (error2) {
|
|
26484
|
+
throw new Error("Invalid _id.");
|
|
26485
|
+
}
|
|
26486
|
+
}
|
|
26487
|
+
if (value.region) {
|
|
26488
|
+
try {
|
|
26489
|
+
value.region = new ObjectId37(value.region);
|
|
26490
|
+
} catch (error2) {
|
|
26491
|
+
throw new Error("Invalid region.");
|
|
26492
|
+
}
|
|
26493
|
+
}
|
|
26494
|
+
if (value.division) {
|
|
26495
|
+
try {
|
|
26496
|
+
value.division = new ObjectId37(value.division);
|
|
26497
|
+
} catch (error2) {
|
|
26498
|
+
throw new Error("Invalid division.");
|
|
26499
|
+
}
|
|
26500
|
+
}
|
|
26501
|
+
if (value.createdBy) {
|
|
26502
|
+
try {
|
|
26503
|
+
value.createdBy = new ObjectId37(value.createdBy);
|
|
26504
|
+
} catch (error2) {
|
|
26505
|
+
throw new Error("Invalid createdBy.");
|
|
26506
|
+
}
|
|
26507
|
+
}
|
|
26508
|
+
return {
|
|
26509
|
+
_id: value._id ? value._id : new ObjectId37(),
|
|
26510
|
+
id: value.id,
|
|
26511
|
+
name: value.name,
|
|
26512
|
+
country: value.country,
|
|
26513
|
+
address: value.address,
|
|
26514
|
+
continuedAddress: value.continuedAddress ?? "",
|
|
26515
|
+
city: value.city,
|
|
26516
|
+
province: value.province,
|
|
26517
|
+
postalCode: value.postalCode,
|
|
26518
|
+
courses: value.courses || [],
|
|
26519
|
+
principalName: value.principalName,
|
|
26520
|
+
principalEmail: value.principalEmail,
|
|
26521
|
+
principalNumber: value.principalNumber ?? "",
|
|
26522
|
+
region: value.region,
|
|
26523
|
+
regionName: value.regionName ?? "",
|
|
26524
|
+
division: value.division,
|
|
26525
|
+
divisionName: value.divisionName ?? "",
|
|
26526
|
+
createdAt: value.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
26527
|
+
updatedAt: value.updatedAt ?? "",
|
|
26528
|
+
status: value.status ?? "pending",
|
|
26529
|
+
createdBy: value.createdBy
|
|
26530
|
+
};
|
|
26531
|
+
}
|
|
26532
|
+
|
|
26533
|
+
// src/repositories/school.repository.ts
|
|
26534
|
+
import {
|
|
26535
|
+
BadRequestError as BadRequestError56,
|
|
26536
|
+
logger as logger26,
|
|
26537
|
+
makeCacheKey as makeCacheKey18,
|
|
26538
|
+
paginate as paginate14,
|
|
26539
|
+
useAtlas as useAtlas29,
|
|
26540
|
+
useCache as useCache18
|
|
26541
|
+
} from "@eeplatform/nodejs-utils";
|
|
26542
|
+
import { ObjectId as ObjectId38 } from "mongodb";
|
|
26543
|
+
function useSchoolRepo() {
|
|
26544
|
+
const db = useAtlas29.getDb();
|
|
26545
|
+
if (!db) {
|
|
26546
|
+
throw new BadRequestError56("Unable to connect to server.");
|
|
26547
|
+
}
|
|
26548
|
+
const namespace_collection = "schools";
|
|
26549
|
+
const collection = db.collection(namespace_collection);
|
|
26550
|
+
const { getCache, setCache, delNamespace } = useCache18();
|
|
26551
|
+
function delCachedData() {
|
|
26552
|
+
delNamespace(namespace_collection).then(() => {
|
|
26553
|
+
logger26.log({
|
|
26554
|
+
level: "info",
|
|
26555
|
+
message: `Cache namespace cleared for ${namespace_collection}`
|
|
26556
|
+
});
|
|
26557
|
+
}).catch((err) => {
|
|
26558
|
+
logger26.log({
|
|
26559
|
+
level: "error",
|
|
26560
|
+
message: `Failed to clear cache namespace for ${namespace_collection}: ${err.message}`
|
|
26561
|
+
});
|
|
26562
|
+
});
|
|
26563
|
+
}
|
|
26564
|
+
async function createIndex() {
|
|
26565
|
+
try {
|
|
26566
|
+
await collection.createIndexes([
|
|
26567
|
+
{ key: { name: 1 } },
|
|
26568
|
+
{ key: { id: 1 }, unique: true },
|
|
26569
|
+
{ key: { region: 1 } },
|
|
26570
|
+
{ key: { division: 1 } },
|
|
26571
|
+
{
|
|
26572
|
+
key: {
|
|
26573
|
+
name: "text",
|
|
26574
|
+
address: "text",
|
|
26575
|
+
continuedAddress: "text",
|
|
26576
|
+
city: "text",
|
|
26577
|
+
province: "text",
|
|
26578
|
+
postalCode: "text",
|
|
26579
|
+
regionName: "text",
|
|
26580
|
+
divisionName: "text"
|
|
26581
|
+
}
|
|
26582
|
+
}
|
|
26583
|
+
]);
|
|
26584
|
+
} catch (error) {
|
|
26585
|
+
throw new BadRequestError56("Failed to create index on school.");
|
|
26586
|
+
}
|
|
26587
|
+
}
|
|
26588
|
+
async function add(value, session) {
|
|
26589
|
+
try {
|
|
26590
|
+
value = MSchool(value);
|
|
26591
|
+
const res = await collection.insertOne(value, { session });
|
|
26592
|
+
delCachedData();
|
|
26593
|
+
return res.insertedId;
|
|
26594
|
+
} catch (error) {
|
|
26595
|
+
logger26.log({
|
|
26596
|
+
level: "error",
|
|
26597
|
+
message: `Failed to add school: ${error}`
|
|
26598
|
+
});
|
|
26599
|
+
const isDuplicated = error.message.includes("duplicate");
|
|
26600
|
+
if (isDuplicated) {
|
|
26601
|
+
throw new BadRequestError56("School already exist.");
|
|
26602
|
+
}
|
|
26603
|
+
throw error;
|
|
26604
|
+
}
|
|
26605
|
+
}
|
|
26606
|
+
async function getAll({
|
|
26607
|
+
page = 1,
|
|
26608
|
+
limit = 20,
|
|
26609
|
+
sort = {},
|
|
26610
|
+
status = "active",
|
|
26611
|
+
region = "",
|
|
26612
|
+
division = "",
|
|
26613
|
+
search = ""
|
|
26614
|
+
} = {}) {
|
|
26615
|
+
page = Math.max(0, page - 1);
|
|
26616
|
+
if (sort && Object.keys(sort).length === 0) {
|
|
26617
|
+
sort = { name: 1 };
|
|
26618
|
+
}
|
|
26619
|
+
const query = { status };
|
|
26620
|
+
const cacheKeyOptions = {
|
|
26621
|
+
page,
|
|
26622
|
+
limit,
|
|
26623
|
+
sort: JSON.stringify(sort),
|
|
26624
|
+
status
|
|
26625
|
+
};
|
|
26626
|
+
if (search) {
|
|
26627
|
+
query.$text = { $search: search };
|
|
26628
|
+
cacheKeyOptions.search = search;
|
|
26629
|
+
}
|
|
26630
|
+
if (region) {
|
|
26631
|
+
try {
|
|
26632
|
+
query.region = new ObjectId38(region);
|
|
26633
|
+
cacheKeyOptions.region = region;
|
|
26634
|
+
} catch (error) {
|
|
26635
|
+
throw new BadRequestError56("Invalid region.");
|
|
26636
|
+
}
|
|
26637
|
+
}
|
|
26638
|
+
if (division) {
|
|
26639
|
+
try {
|
|
26640
|
+
query.division = new ObjectId38(division);
|
|
26641
|
+
cacheKeyOptions.division = division;
|
|
26642
|
+
} catch (error) {
|
|
26643
|
+
throw new BadRequestError56("Invalid division.");
|
|
26644
|
+
}
|
|
26645
|
+
}
|
|
26646
|
+
try {
|
|
26647
|
+
const cacheKey = makeCacheKey18(namespace_collection, cacheKeyOptions);
|
|
26648
|
+
const cachedData = await getCache(cacheKey);
|
|
26649
|
+
if (cachedData) {
|
|
26650
|
+
return cachedData;
|
|
26651
|
+
}
|
|
26652
|
+
const items = await collection.aggregate([
|
|
26653
|
+
{ $match: query },
|
|
26654
|
+
{ $sort: sort },
|
|
26655
|
+
{ $skip: page * limit },
|
|
26656
|
+
{ $limit: limit }
|
|
26657
|
+
]).toArray();
|
|
26658
|
+
const length = await collection.countDocuments(query);
|
|
26659
|
+
const data = paginate14(items, page, limit, length);
|
|
26660
|
+
setCache(cacheKey, data, 600, namespace_collection).then(() => {
|
|
26661
|
+
logger26.log({
|
|
26662
|
+
level: "info",
|
|
26663
|
+
message: `Cache set for key ${cacheKey}`
|
|
26664
|
+
});
|
|
26665
|
+
}).catch((err) => {
|
|
26666
|
+
logger26.log({
|
|
26667
|
+
level: "error",
|
|
26668
|
+
message: `Failed to set cache for key ${cacheKey}: ${err.message}`
|
|
26669
|
+
});
|
|
26670
|
+
});
|
|
26671
|
+
return data;
|
|
26672
|
+
} catch (error) {
|
|
26673
|
+
logger26.log({
|
|
26674
|
+
level: "error",
|
|
26675
|
+
message: `Failed to get all schools: ${error}`
|
|
26676
|
+
});
|
|
26677
|
+
throw error;
|
|
26678
|
+
}
|
|
26679
|
+
}
|
|
26680
|
+
async function updateStatusById(_id, status, session) {
|
|
26681
|
+
try {
|
|
26682
|
+
_id = new ObjectId38(_id);
|
|
26683
|
+
} catch (error) {
|
|
26684
|
+
throw new BadRequestError56("Invalid school ID.");
|
|
26685
|
+
}
|
|
26686
|
+
const result = await collection.updateOne(
|
|
26687
|
+
{ _id },
|
|
26688
|
+
{ $set: { status, updatedAt: /* @__PURE__ */ new Date() } },
|
|
26689
|
+
{ session }
|
|
26690
|
+
);
|
|
26691
|
+
delCachedData();
|
|
26692
|
+
return result;
|
|
26693
|
+
}
|
|
26694
|
+
async function updateFieldById({ _id, field, value } = {}, session) {
|
|
26695
|
+
const allowedFields = [
|
|
26696
|
+
"name",
|
|
26697
|
+
"country",
|
|
26698
|
+
"address",
|
|
26699
|
+
"continuedAddress",
|
|
26700
|
+
"city",
|
|
26701
|
+
"province",
|
|
26702
|
+
"postalCode",
|
|
26703
|
+
"courses",
|
|
26704
|
+
"email",
|
|
26705
|
+
"principalName",
|
|
26706
|
+
"principalEmail",
|
|
26707
|
+
"principalNumber",
|
|
26708
|
+
"region",
|
|
26709
|
+
"regionName",
|
|
26710
|
+
"division",
|
|
26711
|
+
"divisionName"
|
|
26712
|
+
];
|
|
26713
|
+
if (!allowedFields.includes(field)) {
|
|
26714
|
+
throw new BadRequestError56(
|
|
26715
|
+
`Field "${field}" is not allowed to be updated.`
|
|
26716
|
+
);
|
|
26717
|
+
}
|
|
26718
|
+
try {
|
|
26719
|
+
_id = new ObjectId38(_id);
|
|
26720
|
+
} catch (error) {
|
|
26721
|
+
throw new BadRequestError56("Invalid ID.");
|
|
26722
|
+
}
|
|
26723
|
+
try {
|
|
26724
|
+
const result = await collection.updateOne(
|
|
26725
|
+
{ _id },
|
|
26726
|
+
{ $set: { [field]: value } },
|
|
26727
|
+
{ session }
|
|
26728
|
+
);
|
|
26729
|
+
delCachedData();
|
|
26730
|
+
return result;
|
|
26731
|
+
} catch (error) {
|
|
26732
|
+
throw new BadRequestError56(`Failed to update school ${field}.`);
|
|
26733
|
+
}
|
|
26734
|
+
}
|
|
26735
|
+
async function getPendingByCreatedBy(createdBy) {
|
|
26736
|
+
try {
|
|
26737
|
+
createdBy = new ObjectId38(createdBy);
|
|
26738
|
+
} catch (error) {
|
|
26739
|
+
throw new BadRequestError56("Invalid createdBy ID.");
|
|
26740
|
+
}
|
|
26741
|
+
const cacheKey = makeCacheKey18(namespace_collection, {
|
|
26742
|
+
createdBy,
|
|
26743
|
+
status: "pending"
|
|
26744
|
+
});
|
|
26745
|
+
const cachedData = await getCache(cacheKey);
|
|
26746
|
+
if (cachedData) {
|
|
26747
|
+
return cachedData;
|
|
26748
|
+
}
|
|
26749
|
+
try {
|
|
26750
|
+
const school = await collection.findOne({ createdBy, status: "pending" });
|
|
26751
|
+
setCache(cacheKey, school, 600, namespace_collection).then(() => {
|
|
26752
|
+
logger26.log({
|
|
26753
|
+
level: "info",
|
|
26754
|
+
message: `Cache set for school by createdBy: ${cacheKey}`
|
|
26755
|
+
});
|
|
26756
|
+
}).catch((err) => {
|
|
26757
|
+
logger26.log({
|
|
26758
|
+
level: "error",
|
|
26759
|
+
message: `Failed to set cache for school by createdBy: ${err.message}`
|
|
26760
|
+
});
|
|
26761
|
+
});
|
|
26762
|
+
return school;
|
|
26763
|
+
} catch (error) {
|
|
26764
|
+
throw error;
|
|
26765
|
+
}
|
|
26766
|
+
}
|
|
26767
|
+
async function getPendingById(_id) {
|
|
26768
|
+
try {
|
|
26769
|
+
_id = new ObjectId38(_id);
|
|
26770
|
+
} catch (error) {
|
|
26771
|
+
throw new BadRequestError56("Invalid ID.");
|
|
26772
|
+
}
|
|
26773
|
+
const cacheKey = makeCacheKey18(namespace_collection, {
|
|
26774
|
+
_id,
|
|
26775
|
+
status: "pending"
|
|
26776
|
+
});
|
|
26777
|
+
const cachedData = await getCache(cacheKey);
|
|
26778
|
+
if (cachedData) {
|
|
26779
|
+
return cachedData;
|
|
26780
|
+
}
|
|
26781
|
+
try {
|
|
26782
|
+
const school = await collection.findOne({ _id, status: "pending" });
|
|
26783
|
+
setCache(cacheKey, school, 600, namespace_collection).then(() => {
|
|
26784
|
+
logger26.log({
|
|
26785
|
+
level: "info",
|
|
26786
|
+
message: `Cache set for school by ID: ${cacheKey}`
|
|
26787
|
+
});
|
|
26788
|
+
}).catch((err) => {
|
|
26789
|
+
logger26.log({
|
|
26790
|
+
level: "error",
|
|
26791
|
+
message: `Failed to set cache for school by ID: ${err.message}`
|
|
26792
|
+
});
|
|
26793
|
+
});
|
|
26794
|
+
return school;
|
|
26795
|
+
} catch (error) {
|
|
26796
|
+
throw error;
|
|
26797
|
+
}
|
|
26798
|
+
}
|
|
26799
|
+
return {
|
|
26800
|
+
createIndex,
|
|
26801
|
+
add,
|
|
26802
|
+
getAll,
|
|
26803
|
+
updateStatusById,
|
|
26804
|
+
updateFieldById,
|
|
26805
|
+
getPendingByCreatedBy,
|
|
26806
|
+
getPendingById
|
|
26807
|
+
};
|
|
26808
|
+
}
|
|
26809
|
+
|
|
26810
|
+
// src/services/school.service.ts
|
|
26811
|
+
import { BadRequestError as BadRequestError57, useAtlas as useAtlas30, logger as logger27 } from "@eeplatform/nodejs-utils";
|
|
26812
|
+
function useSchoolService() {
|
|
26813
|
+
const { add, getPendingByCreatedBy, updateStatusById, getPendingById } = useSchoolRepo();
|
|
26814
|
+
const { addRole } = useRoleRepo();
|
|
26815
|
+
const { getUserById } = useUserRepo();
|
|
26816
|
+
const { add: addMember } = useMemberRepo();
|
|
26817
|
+
async function register(value) {
|
|
26818
|
+
const { error } = schemaSchool.validate(value);
|
|
26819
|
+
if (error) {
|
|
26820
|
+
throw new BadRequestError57(error.message);
|
|
26821
|
+
}
|
|
26822
|
+
const existingSchool = await getPendingByCreatedBy(value.createdBy ?? "");
|
|
26823
|
+
if (existingSchool) {
|
|
26824
|
+
throw new BadRequestError57(
|
|
26825
|
+
"You already have a pending school registration."
|
|
26826
|
+
);
|
|
26827
|
+
}
|
|
26828
|
+
try {
|
|
26829
|
+
value.status = "pending";
|
|
26830
|
+
await add(value);
|
|
26831
|
+
return "Request to register school has been sent successfully. Please wait for approval.";
|
|
26832
|
+
} catch (error2) {
|
|
26833
|
+
throw error2;
|
|
26834
|
+
}
|
|
26835
|
+
}
|
|
26836
|
+
async function approve(id) {
|
|
26837
|
+
const school = await getPendingById(id);
|
|
26838
|
+
if (!school) {
|
|
26839
|
+
throw new BadRequestError57("School registration not found.");
|
|
26840
|
+
}
|
|
26841
|
+
const session = useAtlas30.getClient()?.startSession();
|
|
26842
|
+
if (!session) {
|
|
26843
|
+
throw new Error("Unable to start session for school service.");
|
|
26844
|
+
}
|
|
26845
|
+
try {
|
|
26846
|
+
session.startTransaction();
|
|
26847
|
+
school.status = "approved";
|
|
26848
|
+
await updateStatusById(id, "active", session);
|
|
26849
|
+
const roleType = "school";
|
|
26850
|
+
const roleName = "Admin";
|
|
26851
|
+
const roleId = await addRole(
|
|
26852
|
+
{
|
|
26853
|
+
id,
|
|
26854
|
+
type: roleType,
|
|
26855
|
+
name: roleName,
|
|
26856
|
+
permissions: ["*"],
|
|
26857
|
+
status: "active",
|
|
26858
|
+
default: true
|
|
26859
|
+
},
|
|
26860
|
+
session
|
|
26861
|
+
);
|
|
26862
|
+
if (!school.createdBy) {
|
|
26863
|
+
throw new BadRequestError57("School must have a creator.");
|
|
26864
|
+
}
|
|
26865
|
+
const user = await getUserById(school.createdBy ?? "");
|
|
26866
|
+
if (!user) {
|
|
26867
|
+
throw new BadRequestError57("User not found for the school creator.");
|
|
26868
|
+
}
|
|
26869
|
+
await addMember(
|
|
26870
|
+
{
|
|
26871
|
+
org: id,
|
|
26872
|
+
orgName: school.name,
|
|
26873
|
+
user: school.createdBy.toString(),
|
|
26874
|
+
name: `${user.firstName} ${user.lastName}`,
|
|
26875
|
+
role: roleId.toString(),
|
|
26876
|
+
roleName,
|
|
26877
|
+
type: roleType
|
|
26878
|
+
},
|
|
26879
|
+
session
|
|
26880
|
+
);
|
|
26881
|
+
await session.commitTransaction();
|
|
26882
|
+
return "School registration has been approved.";
|
|
26883
|
+
} catch (error) {
|
|
26884
|
+
logger27.log({
|
|
26885
|
+
level: "error",
|
|
26886
|
+
message: `Error approving school registration: ${error.message}`
|
|
26887
|
+
});
|
|
26888
|
+
await session.abortTransaction();
|
|
26889
|
+
throw error;
|
|
26890
|
+
} finally {
|
|
26891
|
+
await session.endSession();
|
|
26892
|
+
}
|
|
26893
|
+
}
|
|
26894
|
+
return {
|
|
26895
|
+
register,
|
|
26896
|
+
approve
|
|
26897
|
+
};
|
|
26898
|
+
}
|
|
26899
|
+
|
|
26900
|
+
// src/controllers/school.controller.ts
|
|
26901
|
+
import { BadRequestError as BadRequestError58 } from "@eeplatform/nodejs-utils";
|
|
26902
|
+
import Joi32 from "joi";
|
|
26903
|
+
function useSchoolController() {
|
|
26904
|
+
const {
|
|
26905
|
+
add: _add,
|
|
26906
|
+
getAll: _getAll,
|
|
26907
|
+
getPendingByCreatedBy: _getPendingByCreatedBy,
|
|
26908
|
+
updateStatusById: _updateStatusById
|
|
26909
|
+
} = useSchoolRepo();
|
|
26910
|
+
async function add(req, res, next) {
|
|
26911
|
+
const payload = req.body;
|
|
26912
|
+
const { error } = schemaSchool.validate(payload);
|
|
26913
|
+
if (error) {
|
|
26914
|
+
next(new BadRequestError58(`Validation error: ${error.message}`));
|
|
26915
|
+
return;
|
|
26916
|
+
}
|
|
26917
|
+
try {
|
|
26918
|
+
const school = await _add(payload);
|
|
26919
|
+
res.status(201).json(school);
|
|
26920
|
+
return;
|
|
26921
|
+
} catch (error2) {
|
|
26922
|
+
next(error2);
|
|
26923
|
+
return;
|
|
26924
|
+
}
|
|
26925
|
+
}
|
|
26926
|
+
async function getAll(req, res, next) {
|
|
26927
|
+
const validation = Joi32.object({
|
|
26928
|
+
page: Joi32.number().optional().allow(null, ""),
|
|
26929
|
+
limit: Joi32.number().optional().allow(null, ""),
|
|
26930
|
+
sort: Joi32.string().optional().allow(null, ""),
|
|
26931
|
+
sortOrder: Joi32.string().optional().allow(null, ""),
|
|
26932
|
+
status: Joi32.string().optional().allow(null, ""),
|
|
26933
|
+
region: Joi32.string().hex().optional().allow(null, ""),
|
|
26934
|
+
division: Joi32.string().hex().optional().allow(null, ""),
|
|
26935
|
+
search: Joi32.string().optional().allow(null, "")
|
|
26936
|
+
});
|
|
26937
|
+
const { error } = validation.validate(req.query);
|
|
26938
|
+
if (error) {
|
|
26939
|
+
next(new BadRequestError58(`Validation error: ${error.message}`));
|
|
26940
|
+
return;
|
|
26941
|
+
}
|
|
26942
|
+
const page = parseInt(req.query.page) ?? 1;
|
|
26943
|
+
let limit = parseInt(req.query.limit) ?? 20;
|
|
26944
|
+
limit = isNaN(limit) ? 20 : limit;
|
|
26945
|
+
const sort = req.query.sort ? String(req.query.sort).split(",") : "";
|
|
26946
|
+
const sortOrder = req.query.sortOrder ? String(req.query.sortOrder).split(",") : "";
|
|
26947
|
+
const sortObj = {};
|
|
26948
|
+
if (sort && Array.isArray(sort) && sort.length && sortOrder && Array.isArray(sortOrder) && sortOrder.length) {
|
|
26949
|
+
sort.forEach((field, index) => {
|
|
26950
|
+
sortObj[field] = sortOrder[index] === "desc" ? -1 : 1;
|
|
26951
|
+
});
|
|
26952
|
+
}
|
|
26953
|
+
const status = req.query.status ?? "active";
|
|
26954
|
+
const region = req.query.region ?? "";
|
|
26955
|
+
const division = req.query.division ?? "";
|
|
26956
|
+
const search = req.query.search ?? "";
|
|
26957
|
+
try {
|
|
26958
|
+
const schools = await _getAll({
|
|
26959
|
+
page,
|
|
26960
|
+
limit,
|
|
26961
|
+
sort: sortObj,
|
|
26962
|
+
status,
|
|
26963
|
+
region,
|
|
26964
|
+
division,
|
|
26965
|
+
search
|
|
26966
|
+
});
|
|
26967
|
+
res.status(200).json(schools);
|
|
26968
|
+
return;
|
|
26969
|
+
} catch (error2) {
|
|
26970
|
+
next(error2);
|
|
26971
|
+
}
|
|
26972
|
+
}
|
|
26973
|
+
async function getByCreatedBy(req, res, next) {
|
|
26974
|
+
const createdBy = req.params.createdBy;
|
|
26975
|
+
const validation = Joi32.string().hex().required();
|
|
26976
|
+
const { error } = validation.validate(createdBy);
|
|
26977
|
+
if (error) {
|
|
26978
|
+
next(new BadRequestError58(`Validation error: ${error.message}`));
|
|
26979
|
+
return;
|
|
26980
|
+
}
|
|
26981
|
+
try {
|
|
26982
|
+
const school = await _getPendingByCreatedBy(createdBy);
|
|
26983
|
+
res.status(200).json(school);
|
|
26984
|
+
return;
|
|
26985
|
+
} catch (error2) {
|
|
26986
|
+
next(error2);
|
|
26987
|
+
}
|
|
26988
|
+
}
|
|
26989
|
+
async function updateStatusById(req, res, next) {
|
|
26990
|
+
const schoolId = req.params.id;
|
|
26991
|
+
const status = req.params.status;
|
|
26992
|
+
const validation = Joi32.object({
|
|
26993
|
+
id: Joi32.string().hex().required(),
|
|
26994
|
+
status: Joi32.string().valid("active", "deleted", "suspended").required()
|
|
26995
|
+
});
|
|
26996
|
+
const { error } = validation.validate({ id: schoolId, status });
|
|
26997
|
+
if (error) {
|
|
26998
|
+
next(new BadRequestError58(`Validation error: ${error.message}`));
|
|
26999
|
+
return;
|
|
27000
|
+
}
|
|
27001
|
+
try {
|
|
27002
|
+
const updatedSchool = await _updateStatusById(schoolId, status);
|
|
27003
|
+
res.status(200).json(updatedSchool);
|
|
27004
|
+
return;
|
|
27005
|
+
} catch (error2) {
|
|
27006
|
+
next(error2);
|
|
27007
|
+
}
|
|
27008
|
+
}
|
|
27009
|
+
const { register: _registerSchool, approve } = useSchoolService();
|
|
27010
|
+
async function registerSchool(req, res, next) {
|
|
27011
|
+
const payload = req.body;
|
|
27012
|
+
const { error } = schemaSchool.validate(payload);
|
|
27013
|
+
if (error) {
|
|
27014
|
+
next(new BadRequestError58(`Validation error: ${error.message}`));
|
|
27015
|
+
return;
|
|
27016
|
+
}
|
|
27017
|
+
try {
|
|
27018
|
+
const schoolId = await _registerSchool(payload);
|
|
27019
|
+
res.status(201).json({ schoolId });
|
|
27020
|
+
return;
|
|
27021
|
+
} catch (error2) {
|
|
27022
|
+
next(error2);
|
|
27023
|
+
return;
|
|
27024
|
+
}
|
|
27025
|
+
}
|
|
27026
|
+
async function approveSchool(req, res, next) {
|
|
27027
|
+
const schoolId = req.params.id;
|
|
27028
|
+
const validation = Joi32.object({
|
|
27029
|
+
id: Joi32.string().hex().required()
|
|
27030
|
+
});
|
|
27031
|
+
const { error } = validation.validate({ id: schoolId });
|
|
27032
|
+
if (error) {
|
|
27033
|
+
next(new BadRequestError58(`Validation error: ${error.message}`));
|
|
27034
|
+
return;
|
|
27035
|
+
}
|
|
27036
|
+
try {
|
|
27037
|
+
const updatedSchool = await approve(schoolId);
|
|
27038
|
+
res.status(200).json(updatedSchool);
|
|
27039
|
+
return;
|
|
27040
|
+
} catch (error2) {
|
|
27041
|
+
next(error2);
|
|
27042
|
+
}
|
|
27043
|
+
}
|
|
27044
|
+
return {
|
|
27045
|
+
add,
|
|
27046
|
+
getAll,
|
|
27047
|
+
getByCreatedBy,
|
|
27048
|
+
updateStatusById,
|
|
27049
|
+
registerSchool,
|
|
27050
|
+
approveSchool
|
|
27051
|
+
};
|
|
27052
|
+
}
|
|
25338
27053
|
export {
|
|
25339
27054
|
ACCESS_TOKEN_EXPIRY,
|
|
25340
27055
|
ACCESS_TOKEN_SECRET,
|
|
@@ -25353,6 +27068,7 @@ export {
|
|
|
25353
27068
|
MAILER_TRANSPORT_PORT,
|
|
25354
27069
|
MAILER_TRANSPORT_SECURE,
|
|
25355
27070
|
MAddress,
|
|
27071
|
+
MDivision,
|
|
25356
27072
|
MEntity,
|
|
25357
27073
|
MFile,
|
|
25358
27074
|
MMember,
|
|
@@ -25362,7 +27078,9 @@ export {
|
|
|
25362
27078
|
MOrg,
|
|
25363
27079
|
MPaymentMethod,
|
|
25364
27080
|
MPromoCode,
|
|
27081
|
+
MRegion,
|
|
25365
27082
|
MRole,
|
|
27083
|
+
MSchool,
|
|
25366
27084
|
MSubscription,
|
|
25367
27085
|
MToken,
|
|
25368
27086
|
MUser,
|
|
@@ -25390,12 +27108,17 @@ export {
|
|
|
25390
27108
|
addressSchema,
|
|
25391
27109
|
isDev,
|
|
25392
27110
|
schema,
|
|
27111
|
+
schemaDivision,
|
|
27112
|
+
schemaRegion,
|
|
27113
|
+
schemaSchool,
|
|
25393
27114
|
useAddressController,
|
|
25394
27115
|
useAddressRepo,
|
|
25395
27116
|
useAuthController,
|
|
25396
27117
|
useAuthService,
|
|
25397
27118
|
useCounterModel,
|
|
25398
27119
|
useCounterRepo,
|
|
27120
|
+
useDivisionController,
|
|
27121
|
+
useDivisionRepo,
|
|
25399
27122
|
useEntityController,
|
|
25400
27123
|
useEntityRepo,
|
|
25401
27124
|
useFileController,
|
|
@@ -25424,8 +27147,13 @@ export {
|
|
|
25424
27147
|
usePriceRepo,
|
|
25425
27148
|
usePromoCodeController,
|
|
25426
27149
|
usePromoCodeRepo,
|
|
27150
|
+
useRegionController,
|
|
27151
|
+
useRegionRepo,
|
|
25427
27152
|
useRoleController,
|
|
25428
27153
|
useRoleRepo,
|
|
27154
|
+
useSchoolController,
|
|
27155
|
+
useSchoolRepo,
|
|
27156
|
+
useSchoolService,
|
|
25429
27157
|
useSubscriptionController,
|
|
25430
27158
|
useSubscriptionRepo,
|
|
25431
27159
|
useSubscriptionService,
|