Skip to content
This repository was archived by the owner on Nov 16, 2023. It is now read-only.

Commit 1373e13

Browse files
authored
new Item tag/category relations and APIs (#305)
* add association * add new apis and new get items * resolve comments
1 parent ca09b40 commit 1373e13

File tree

6 files changed

+311
-21
lines changed

6 files changed

+311
-21
lines changed

rest_server/src/controllers/item_controller.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,152 @@ const del = asyncHandler(async (req, res, next) => {
155155
}
156156
});
157157

158+
const listTags = asyncHandler(async (req, res, next) => {
159+
try {
160+
let result = await MarketplaceItem.get(req.params.itemId);
161+
if (checkReadPermission(req.userInfo, result)) {
162+
result = await MarketplaceItem.getTags(result);
163+
if (isNil(result)) {
164+
return next(error.createNotFound());
165+
} else {
166+
res.status(200).json(result);
167+
}
168+
} else {
169+
return next(
170+
error.createForbidden(
171+
`The access to the item ${req.params.itemId} is forbidden`,
172+
),
173+
);
174+
}
175+
} catch (e) {
176+
return next(error.createInternalServerError(e));
177+
}
178+
});
179+
180+
const addTag = asyncHandler(async (req, res, next) => {
181+
try {
182+
let result = await MarketplaceItem.get(req.params.itemId);
183+
if (checkReadPermission(req.userInfo, result)) {
184+
result = await MarketplaceItem.addTag(result, req.params.tagId);
185+
if (isNil(result)) {
186+
return next(error.createNotFound());
187+
} else {
188+
res.status(200).send('added');
189+
}
190+
} else {
191+
return next(
192+
error.createForbidden(
193+
`The access to the item ${req.params.itemId} is forbidden`,
194+
),
195+
);
196+
}
197+
} catch (e) {
198+
return next(error.createInternalServerError(e));
199+
}
200+
});
201+
202+
const deleteTag = asyncHandler(async (req, res, next) => {
203+
try {
204+
let result = await MarketplaceItem.get(req.params.itemId);
205+
if (checkReadPermission(req.userInfo, result)) {
206+
result = await MarketplaceItem.deleteTag(result, req.params.tagId);
207+
if (isNil(result)) {
208+
return next(error.createNotFound());
209+
} else {
210+
res.status(200).json('deleted');
211+
}
212+
} else {
213+
return next(
214+
error.createForbidden(
215+
`The access to the item ${req.params.itemId} is forbidden`,
216+
),
217+
);
218+
}
219+
} catch (e) {
220+
return next(error.createInternalServerError(e));
221+
}
222+
});
223+
224+
const listCategories = asyncHandler(async (req, res, next) => {
225+
try {
226+
let result = await MarketplaceItem.get(req.params.itemId);
227+
if (checkReadPermission(req.userInfo, result)) {
228+
result = await MarketplaceItem.getCategories(result);
229+
if (isNil(result)) {
230+
return next(error.createNotFound());
231+
} else {
232+
res.status(200).json(result);
233+
}
234+
} else {
235+
return next(
236+
error.createForbidden(
237+
`The access to the item ${req.params.itemId} is forbidden`,
238+
),
239+
);
240+
}
241+
} catch (e) {
242+
return next(error.createInternalServerError(e));
243+
}
244+
});
245+
246+
const addCategory = asyncHandler(async (req, res, next) => {
247+
try {
248+
let result = await MarketplaceItem.get(req.params.itemId);
249+
if (checkReadPermission(req.userInfo, result)) {
250+
result = await MarketplaceItem.addCategory(result, req.params.categoryId);
251+
if (isNil(result)) {
252+
return next(error.createNotFound());
253+
} else {
254+
res.status(200).send('added');
255+
}
256+
} else {
257+
return next(
258+
error.createForbidden(
259+
`The access to the item ${req.params.itemId} is forbidden`,
260+
),
261+
);
262+
}
263+
} catch (e) {
264+
return next(error.createInternalServerError(e));
265+
}
266+
});
267+
268+
const deleteCategory = asyncHandler(async (req, res, next) => {
269+
try {
270+
let result = await MarketplaceItem.get(req.params.itemId);
271+
if (checkReadPermission(req.userInfo, result)) {
272+
result = await MarketplaceItem.deleteCategory(
273+
result,
274+
req.params.categoryId,
275+
);
276+
if (isNil(result)) {
277+
return next(error.createNotFound());
278+
} else {
279+
res.status(200).json('deleted');
280+
}
281+
} else {
282+
return next(
283+
error.createForbidden(
284+
`The access to the item ${req.params.itemId} is forbidden`,
285+
),
286+
);
287+
}
288+
} catch (e) {
289+
return next(error.createInternalServerError(e));
290+
}
291+
});
292+
158293
// module exports
159294
module.exports = {
160295
list,
161296
create,
162297
get,
163298
update,
164299
del,
300+
listTags,
301+
addTag,
302+
deleteTag,
303+
listCategories,
304+
addCategory,
305+
deleteCategory,
165306
};

rest_server/src/models/item_category.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@ class ItemCategory {
1111
defaultValue: DataTypes.UUIDV4,
1212
primaryKey: true,
1313
},
14-
name: DataTypes.STRING,
14+
name: {
15+
type: DataTypes.STRING,
16+
allowNull: false,
17+
},
1518
description: DataTypes.STRING,
1619
extras: DataTypes.TEXT,
1720
});
1821
}
1922

2023
associate(models) {
21-
this.orm.belongsToMany(models.User.orm, {
22-
through: 'StarRelation',
24+
this.orm.belongsToMany(models.MarketplaceItem.orm, {
25+
through: 'ItemCategoryRelation',
2326
});
2427
this.models = models;
2528
}

rest_server/src/models/item_tag.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,19 @@ class ItemTag {
1111
defaultValue: DataTypes.UUIDV4,
1212
primaryKey: true,
1313
},
14-
name: DataTypes.STRING,
14+
name: {
15+
type: DataTypes.STRING,
16+
allowNull: false,
17+
},
1518
color: DataTypes.STRING,
1619
description: DataTypes.STRING,
1720
extras: DataTypes.TEXT,
1821
});
1922
}
2023

2124
associate(models) {
22-
this.orm.belongsToMany(models.User.orm, {
23-
through: 'StarRelation',
25+
this.orm.belongsToMany(models.MarketplaceItem.orm, {
26+
through: 'ItemTagRelation',
2427
});
2528
this.models = models;
2629
}

rest_server/src/models/marketplace_item.js

Lines changed: 101 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33
const { isNil, toLower } = require('lodash');
4-
const { Op, fn, col, where } = require('sequelize');
4+
const { Op, fn, col, where, cast } = require('sequelize');
55
const modelSyncHandler = require('./model_init_handler');
66

77
class MarketplaceItem {
@@ -72,6 +72,12 @@ class MarketplaceItem {
7272
this.orm.belongsToMany(models.User.orm, {
7373
through: 'StarRelation',
7474
});
75+
this.orm.belongsToMany(models.ItemTag.orm, {
76+
through: 'ItemTagRelation',
77+
});
78+
this.orm.belongsToMany(models.ItemCategory.orm, {
79+
through: 'ItemCategoryRelation',
80+
});
7581
this.models = models;
7682
}
7783

@@ -91,16 +97,6 @@ class MarketplaceItem {
9197
if (source) {
9298
filterStatement.source = source;
9399
}
94-
if (tags) {
95-
filterStatement.tags = {
96-
[Op.contains]: tags,
97-
};
98-
}
99-
if (categories) {
100-
filterStatement.categories = {
101-
[Op.contains]: categories,
102-
};
103-
}
104100
if (keyword) {
105101
const lowerKeyword = toLower(keyword);
106102
filterStatement[Op.or] = [
@@ -122,7 +118,7 @@ class MarketplaceItem {
122118
summary: where(
123119
fn('LOWER', col('summary')),
124120
Op.substring,
125-
`%${lowerKeyword}%`,
121+
```%${lowerKeyword}%`,
126122
),
127123
},
128124
];
@@ -165,7 +161,51 @@ class MarketplaceItem {
165161
},
166162
];
167163
}
168-
const items = await this.orm.findAll({ where: filterStatement });
164+
165+
const havings = [];
166+
const havingArrayAgg = (queryParameter, colName) => {
167+
if (queryParameter) {
168+
havings.push({
169+
where: where(
170+
fn('array_agg', col(colName)),
171+
Op.contains,
172+
cast(queryParameter, 'varchar[]'),
173+
),
174+
});
175+
}
176+
};
177+
havingArrayAgg(tags, 'ItemTags.name');
178+
havingArrayAgg(categories, 'ItemCategories.name');
179+
180+
const items = await this.orm.findAll({
181+
where: filterStatement,
182+
having: havings,
183+
group: [
184+
'MarketplaceItem.id',
185+
'ItemTags.id',
186+
'ItemTags->ItemTagRelation.createdAt',
187+
'ItemTags->ItemTagRelation.updatedAt',
188+
'ItemTags->ItemTagRelation.MarketplaceItemId',
189+
'ItemTags->ItemTagRelation.ItemTagId',
190+
'ItemCategories.id',
191+
'ItemCategories->ItemCategoryRelation.createdAt',
192+
'ItemCategories->ItemCategoryRelation.updatedAt',
193+
'ItemCategories->ItemCategoryRelation.MarketplaceItemId',
194+
'ItemCategories->ItemCategoryRelation.ItemCategoryId',
195+
],
196+
include: [
197+
{
198+
attributes: ['id', 'name', 'color'],
199+
model: this.models.ItemTag.orm,
200+
through: { attributes: [] },
201+
},
202+
{
203+
attributes: ['id', 'name'],
204+
model: this.models.ItemCategory.orm,
205+
through: { attributes: [] },
206+
},
207+
],
208+
});
169209
return items;
170210
},
171211
);
@@ -300,6 +340,54 @@ class MarketplaceItem {
300340

301341
return await handler(itemId, this.models);
302342
}
343+
344+
async getTags(item) {
345+
const handler = modelSyncHandler(async item => {
346+
return await item.getItemTags();
347+
});
348+
349+
return await handler(item, this.models);
350+
}
351+
352+
async addTag(item, tagId) {
353+
const handler = modelSyncHandler(async item => {
354+
return await item.addItemTag(tagId);
355+
});
356+
357+
return await handler(item, this.models);
358+
}
359+
360+
async deleteTag(item, tagId) {
361+
const handler = modelSyncHandler(async item => {
362+
return await item.removeItemTag(tagId);
363+
});
364+
365+
return await handler(item, this.models);
366+
}
367+
368+
async getCategories(item) {
369+
const handler = modelSyncHandler(async item => {
370+
return await item.getItemCategories();
371+
});
372+
373+
return await handler(item, this.models);
374+
}
375+
376+
async addCategory(item, tagId) {
377+
const handler = modelSyncHandler(async item => {
378+
return await item.addItemCategory(tagId);
379+
});
380+
381+
return await handler(item, this.models);
382+
}
383+
384+
async deleteCategory(item, tagId) {
385+
const handler = modelSyncHandler(async item => {
386+
return await item.removeItemCategory(tagId);
387+
});
388+
389+
return await handler(item, this.models);
390+
}
303391
}
304392

305393
module.exports = MarketplaceItem;

0 commit comments

Comments
 (0)