Skip to content
This repository was archived by the owner on Nov 16, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions rest_server/src/controllers/item_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,152 @@ const del = asyncHandler(async (req, res, next) => {
}
});

const listTags = asyncHandler(async (req, res, next) => {
try {
let result = await MarketplaceItem.get(req.params.itemId);
if (checkReadPermission(req.userInfo, result)) {
result = await MarketplaceItem.getTags(result);
if (isNil(result)) {
return next(error.createNotFound());
} else {
res.status(200).json(result);
}
} else {
return next(
error.createForbidden(
`The access to the item ${req.params.itemId} is forbidden`,
),
);
}
} catch (e) {
return next(error.createInternalServerError(e));
}
});

const addTag = asyncHandler(async (req, res, next) => {
try {
let result = await MarketplaceItem.get(req.params.itemId);
if (checkReadPermission(req.userInfo, result)) {
result = await MarketplaceItem.addTag(result, req.params.tagId);
if (isNil(result)) {
return next(error.createNotFound());
} else {
res.status(200).send('added');
}
} else {
return next(
error.createForbidden(
`The access to the item ${req.params.itemId} is forbidden`,
),
);
}
} catch (e) {
return next(error.createInternalServerError(e));
}
});

const deleteTag = asyncHandler(async (req, res, next) => {
try {
let result = await MarketplaceItem.get(req.params.itemId);
if (checkReadPermission(req.userInfo, result)) {
result = await MarketplaceItem.deleteTag(result, req.params.tagId);
if (isNil(result)) {
return next(error.createNotFound());
} else {
res.status(200).json('deleted');
}
} else {
return next(
error.createForbidden(
`The access to the item ${req.params.itemId} is forbidden`,
),
);
}
} catch (e) {
return next(error.createInternalServerError(e));
}
});

const listCategories = asyncHandler(async (req, res, next) => {
try {
let result = await MarketplaceItem.get(req.params.itemId);
if (checkReadPermission(req.userInfo, result)) {
result = await MarketplaceItem.getCategories(result);
if (isNil(result)) {
return next(error.createNotFound());
} else {
res.status(200).json(result);
}
} else {
return next(
error.createForbidden(
`The access to the item ${req.params.itemId} is forbidden`,
),
);
}
} catch (e) {
return next(error.createInternalServerError(e));
}
});

const addCategory = asyncHandler(async (req, res, next) => {
try {
let result = await MarketplaceItem.get(req.params.itemId);
if (checkReadPermission(req.userInfo, result)) {
result = await MarketplaceItem.addCategory(result, req.params.categoryId);
if (isNil(result)) {
return next(error.createNotFound());
} else {
res.status(200).send('added');
}
} else {
return next(
error.createForbidden(
`The access to the item ${req.params.itemId} is forbidden`,
),
);
}
} catch (e) {
return next(error.createInternalServerError(e));
}
});

const deleteCategory = asyncHandler(async (req, res, next) => {
try {
let result = await MarketplaceItem.get(req.params.itemId);
if (checkReadPermission(req.userInfo, result)) {
result = await MarketplaceItem.deleteCategory(
result,
req.params.categoryId,
);
if (isNil(result)) {
return next(error.createNotFound());
} else {
res.status(200).json('deleted');
}
} else {
return next(
error.createForbidden(
`The access to the item ${req.params.itemId} is forbidden`,
),
);
}
} catch (e) {
return next(error.createInternalServerError(e));
}
});

// module exports
module.exports = {
list,
create,
get,
update,
del,
listTags,
addTag,
deleteTag,
listCategories,
addCategory,
deleteCategory,
};
9 changes: 6 additions & 3 deletions rest_server/src/models/item_category.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ class ItemCategory {
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: DataTypes.STRING,
name: {
type: DataTypes.STRING,
allowNull: false,
},
description: DataTypes.STRING,
extras: DataTypes.TEXT,
});
}

associate(models) {
this.orm.belongsToMany(models.User.orm, {
through: 'StarRelation',
this.orm.belongsToMany(models.MarketplaceItem.orm, {
through: 'ItemCategoryRelation',
});
this.models = models;
}
Expand Down
9 changes: 6 additions & 3 deletions rest_server/src/models/item_tag.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,19 @@ class ItemTag {
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: DataTypes.STRING,
name: {
type: DataTypes.STRING,
allowNull: false,
},
color: DataTypes.STRING,
description: DataTypes.STRING,
extras: DataTypes.TEXT,
});
}

associate(models) {
this.orm.belongsToMany(models.User.orm, {
through: 'StarRelation',
this.orm.belongsToMany(models.MarketplaceItem.orm, {
through: 'ItemTagRelation',
});
this.models = models;
}
Expand Down
114 changes: 101 additions & 13 deletions rest_server/src/models/marketplace_item.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
const { isNil, toLower } = require('lodash');
const { Op, fn, col, where } = require('sequelize');
const { Op, fn, col, where, cast } = require('sequelize');
const modelSyncHandler = require('./model_init_handler');

class MarketplaceItem {
Expand Down Expand Up @@ -72,6 +72,12 @@ class MarketplaceItem {
this.orm.belongsToMany(models.User.orm, {
through: 'StarRelation',
});
this.orm.belongsToMany(models.ItemTag.orm, {
through: 'ItemTagRelation',
});
this.orm.belongsToMany(models.ItemCategory.orm, {
through: 'ItemCategoryRelation',
});
this.models = models;
}

Expand All @@ -91,16 +97,6 @@ class MarketplaceItem {
if (source) {
filterStatement.source = source;
}
if (tags) {
filterStatement.tags = {
[Op.contains]: tags,
};
}
if (categories) {
filterStatement.categories = {
[Op.contains]: categories,
};
}
if (keyword) {
const lowerKeyword = toLower(keyword);
filterStatement[Op.or] = [
Expand All @@ -122,7 +118,7 @@ class MarketplaceItem {
summary: where(
fn('LOWER', col('summary')),
Op.substring,
`%${lowerKeyword}%`,
```%${lowerKeyword}%`,
),
},
];
Expand Down Expand Up @@ -165,7 +161,51 @@ class MarketplaceItem {
},
];
}
const items = await this.orm.findAll({ where: filterStatement });

const havings = [];
const havingArrayAgg = (queryParameter, colName) => {
if (queryParameter) {
havings.push({
where: where(
fn('array_agg', col(colName)),
Op.contains,
cast(queryParameter, 'varchar[]'),
),
});
}
};
havingArrayAgg(tags, 'ItemTags.name');
havingArrayAgg(categories, 'ItemCategories.name');

const items = await this.orm.findAll({
where: filterStatement,
having: havings,
group: [
'MarketplaceItem.id',
'ItemTags.id',
'ItemTags->ItemTagRelation.createdAt',
'ItemTags->ItemTagRelation.updatedAt',
'ItemTags->ItemTagRelation.MarketplaceItemId',
'ItemTags->ItemTagRelation.ItemTagId',
'ItemCategories.id',
'ItemCategories->ItemCategoryRelation.createdAt',
'ItemCategories->ItemCategoryRelation.updatedAt',
'ItemCategories->ItemCategoryRelation.MarketplaceItemId',
'ItemCategories->ItemCategoryRelation.ItemCategoryId',
],
include: [
{
attributes: ['id', 'name', 'color'],
model: this.models.ItemTag.orm,
through: { attributes: [] },
},
{
attributes: ['id', 'name'],
model: this.models.ItemCategory.orm,
through: { attributes: [] },
},
],
});
return items;
},
);
Expand Down Expand Up @@ -300,6 +340,54 @@ class MarketplaceItem {

return await handler(itemId, this.models);
}

async getTags(item) {
const handler = modelSyncHandler(async item => {
return await item.getItemTags();
});

return await handler(item, this.models);
}

async addTag(item, tagId) {
const handler = modelSyncHandler(async item => {
return await item.addItemTag(tagId);
});

return await handler(item, this.models);
}

async deleteTag(item, tagId) {
const handler = modelSyncHandler(async item => {
return await item.removeItemTag(tagId);
});

return await handler(item, this.models);
}

async getCategories(item) {
const handler = modelSyncHandler(async item => {
return await item.getItemCategories();
});

return await handler(item, this.models);
}

async addCategory(item, tagId) {
const handler = modelSyncHandler(async item => {
return await item.addItemCategory(tagId);
});

return await handler(item, this.models);
}

async deleteCategory(item, tagId) {
const handler = modelSyncHandler(async item => {
return await item.removeItemCategory(tagId);
});

return await handler(item, this.models);
}
}

module.exports = MarketplaceItem;
Loading