@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/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.org === "string" && value.org.length === 24) {
17413
+ if (typeof value.id === "string" && value.id.length === 24) {
17409
17414
  try {
17410
- value.org = new ObjectId12(value.org);
17415
+ value.id = new ObjectId12(value.id);
17411
17416
  } catch (error) {
17412
- throw new Error("Invalid org.");
17417
+ throw new Error("Invalid id.");
17413
17418
  }
17414
17419
  }
17415
- this.org = value.org ?? "";
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, org: 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
- org = ""
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 (org && typeof org === "string" && org.length === 24) {
17621
+ if (id && typeof id === "string" && id.length === 24) {
17616
17622
  try {
17617
- org = new ObjectId13(org);
17623
+ id = new ObjectId13(id);
17618
17624
  } catch (error) {
17619
- throw new BadRequestError10("Invalid organization ID.");
17625
+ throw new BadRequestError10("Invalid ID.");
17620
17626
  }
17621
17627
  }
17622
- const query = { status: "active", org };
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 (org) {
17629
- cacheKeyOptions.org = String(org);
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
- org: "",
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().optional().allow("", null),
18783
- org: Joi7.string().hex().optional().allow("", null)
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 org = req.query.org ?? "";
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
- org: Joi7.string().hex().optional().allow("", null)
18818
+ id: Joi7.string().hex().optional().allow("", null)
18810
18819
  });
18811
- const { error } = validation.validate({ search, page, limit, type, org });
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, org });
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
- org: orgId.toString(),
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
- org: orgId.toString(),
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,