@loomcore/api 0.0.32 → 0.0.34

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.
@@ -1,5 +1,8 @@
1
1
  import { Db, ObjectId } from 'mongodb';
2
- import { IUser, IUserContext } from '@loomcore/common/models';
2
+ import { Application } from 'express';
3
+ import { IUser, IUserContext, IEntity, IAuditable } from '@loomcore/common/models';
4
+ import { GenericApiService } from '../services/generic-api.service.js';
5
+ import { ApiController } from '../controllers/api.controller.js';
3
6
  declare function initialize(database: Db): void;
4
7
  declare function createIndexes(db: Db): Promise<void>;
5
8
  declare function createMetaOrg(): Promise<void>;
@@ -18,6 +21,40 @@ declare function deleteTestUser(): Promise<any[]>;
18
21
  declare function simulateloginWithTestUser(): Promise<string>;
19
22
  declare function getAuthToken(): string;
20
23
  declare function verifyToken(token: string): any;
24
+ export interface ICategory extends IEntity {
25
+ name: string;
26
+ }
27
+ export interface IProduct extends IEntity, IAuditable {
28
+ name: string;
29
+ internalNumber?: string;
30
+ categoryId: string;
31
+ category?: ICategory;
32
+ }
33
+ export declare const CategorySchema: import("@sinclair/typebox").TObject<{
34
+ name: import("@sinclair/typebox").TString;
35
+ }>;
36
+ export declare const CategorySpec: import("@loomcore/common/models").IModelSpec<import("@sinclair/typebox").TSchema>;
37
+ export declare const ProductSchema: import("@sinclair/typebox").TObject<{
38
+ name: import("@sinclair/typebox").TString;
39
+ internalNumber: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
40
+ categoryId: import("@sinclair/typebox").TString;
41
+ }>;
42
+ export declare const ProductSpec: import("@loomcore/common/models").IModelSpec<import("@sinclair/typebox").TSchema>;
43
+ export declare const PublicProductSchema: import("@sinclair/typebox").TObject<{}>;
44
+ export declare class CategoryService extends GenericApiService<ICategory> {
45
+ constructor(db: Db);
46
+ }
47
+ export declare class CategoryController extends ApiController<ICategory> {
48
+ constructor(app: Application, db: Db);
49
+ }
50
+ export declare class ProductService extends GenericApiService<IProduct> {
51
+ constructor(db: Db);
52
+ protected getAdditionalPipelineStages(): any[];
53
+ transformSingle(single: any): any;
54
+ }
55
+ export declare class ProductsController extends ApiController<IProduct> {
56
+ constructor(app: Application, db: Db);
57
+ }
21
58
  declare function getTestUser(): Partial<IUser>;
22
59
  declare function configureJwtSecret(): void;
23
60
  declare function loginWithTestUser(agent: any): Promise<string>;
@@ -1,9 +1,13 @@
1
1
  import { ObjectId } from 'mongodb';
2
2
  import crypto from 'crypto';
3
3
  import jwt from 'jsonwebtoken';
4
+ import { Type } from '@sinclair/typebox';
4
5
  import { JwtService } from '../services/jwt.service.js';
5
6
  import { passwordUtils } from '../utils/password.utils.js';
6
7
  import { AuthService } from '../services/auth.service.js';
8
+ import { GenericApiService } from '../services/generic-api.service.js';
9
+ import { ApiController } from '../controllers/api.controller.js';
10
+ import { entityUtils } from '@loomcore/common/utils';
7
11
  let db;
8
12
  let collections = {};
9
13
  let deviceIdCookie;
@@ -169,6 +173,71 @@ function getAuthToken() {
169
173
  function verifyToken(token) {
170
174
  return JwtService.verify(token, JWT_SECRET);
171
175
  }
176
+ export const CategorySchema = Type.Object({
177
+ name: Type.String(),
178
+ });
179
+ export const CategorySpec = entityUtils.getModelSpec(CategorySchema);
180
+ export const ProductSchema = Type.Object({
181
+ name: Type.String(),
182
+ internalNumber: Type.Optional(Type.String()),
183
+ categoryId: Type.String({ format: 'objectid' }),
184
+ });
185
+ export const ProductSpec = entityUtils.getModelSpec(ProductSchema, { isAuditable: true });
186
+ export const PublicProductSchema = Type.Omit(ProductSpec.fullSchema, ['internalNumber']);
187
+ export class CategoryService extends GenericApiService {
188
+ constructor(db) {
189
+ super(db, 'categories', 'category', CategorySpec);
190
+ }
191
+ }
192
+ export class CategoryController extends ApiController {
193
+ constructor(app, db) {
194
+ const categoryService = new CategoryService(db);
195
+ super('categories', app, categoryService, 'category', CategorySpec);
196
+ }
197
+ }
198
+ export class ProductService extends GenericApiService {
199
+ constructor(db) {
200
+ super(db, 'products', 'product', ProductSpec);
201
+ }
202
+ getAdditionalPipelineStages() {
203
+ return [
204
+ {
205
+ $lookup: {
206
+ from: 'categories',
207
+ localField: 'categoryId',
208
+ foreignField: '_id',
209
+ as: 'category'
210
+ }
211
+ },
212
+ {
213
+ $unwind: {
214
+ path: '$category',
215
+ preserveNullAndEmptyArrays: true
216
+ }
217
+ }
218
+ ];
219
+ }
220
+ transformSingle(single) {
221
+ if (single && single.category) {
222
+ const categoryService = new CategoryService(this.db);
223
+ single.category = categoryService.transformSingle(single.category);
224
+ }
225
+ return super.transformSingle(single);
226
+ }
227
+ }
228
+ export class ProductsController extends ApiController {
229
+ constructor(app, db) {
230
+ const productService = new ProductService(db);
231
+ const AggregatedProductSchema = Type.Intersect([
232
+ ProductSpec.fullSchema,
233
+ Type.Partial(Type.Object({
234
+ category: CategorySpec.fullSchema
235
+ }))
236
+ ]);
237
+ const PublicAggregatedProductSchema = Type.Omit(AggregatedProductSchema, ['internalNumber']);
238
+ super('products', app, productService, 'product', ProductSpec, PublicAggregatedProductSchema);
239
+ }
240
+ }
172
241
  function getTestUser() {
173
242
  return testUser;
174
243
  }
@@ -129,7 +129,10 @@ export class GenericApiService {
129
129
  const query = this.prepareQuery(userContext, baseQuery);
130
130
  let entity = null;
131
131
  if (this.getAdditionalPipelineStages().length > 0) {
132
- const pipeline = this.createAggregationPipeline(userContext, query);
132
+ const pipeline = [
133
+ { $match: query },
134
+ ...this.getAdditionalPipelineStages()
135
+ ];
133
136
  entity = await this.collection.aggregate(pipeline).next();
134
137
  }
135
138
  else {
@@ -1,18 +1,20 @@
1
+ import { entityUtils } from '@loomcore/common/utils';
1
2
  import { DefaultQueryOptions } from '@loomcore/common/models';
2
3
  function apiResponse(response, status, options = {}, modelSpec, publicSchema) {
3
4
  const success = status >= 200 && status < 300;
4
5
  let apiResponse;
5
- if (modelSpec && options.data) {
6
+ const specForEncoding = publicSchema ? entityUtils.getModelSpec(publicSchema) : modelSpec;
7
+ if (specForEncoding && options.data) {
6
8
  if (Array.isArray(options.data)) {
7
- options.data = options.data.map((item) => modelSpec.encode(item, publicSchema));
9
+ options.data = options.data.map((item) => specForEncoding.encode(item));
8
10
  }
9
11
  else if (typeof options.data === 'object' && options.data !== null && 'entities' in options.data && Array.isArray(options.data.entities)) {
10
12
  const pagedResult = options.data;
11
- pagedResult.entities = pagedResult.entities.map((item) => modelSpec.encode(item, publicSchema));
13
+ pagedResult.entities = pagedResult.entities.map((item) => specForEncoding.encode(item));
12
14
  options.data = pagedResult;
13
15
  }
14
16
  else {
15
- options.data = modelSpec.encode(options.data, publicSchema);
17
+ options.data = specForEncoding.encode(options.data);
16
18
  }
17
19
  }
18
20
  if (success) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loomcore/api",
3
- "version": "0.0.32",
3
+ "version": "0.0.34",
4
4
  "private": false,
5
5
  "description": "Loom Core Api - An opinionated Node.js api using Typescript, Express, and MongoDb",
6
6
  "scripts": {