diff --git a/routes/api/group.js b/routes/api/group.js index d33532008..f8fbdd80e 100644 --- a/routes/api/group.js +++ b/routes/api/group.js @@ -2177,6 +2177,8 @@ module.exports = function (app) { const limitMax = 100; const limitDefault = 26; const userId = req.user?.userId; + const country = req.query.country; + const language = req.query.language; const orderBy = req.query.orderBy || 'updatedAt'; const order = (req.query.order && req.query.order.toLowerCase() === 'asc') ? 'ASC' : 'DESC'; let orderBySql = ` ORDER BY`; @@ -2184,6 +2186,9 @@ module.exports = function (app) { case 'name': orderBySql += ` g.name ` break; + case 'activity': + orderBySql += `ga.updatedAt` + break; default: orderBySql += ` g."updatedAt" ` @@ -2198,7 +2203,7 @@ module.exports = function (app) { AND g."deletedAt" IS NULL `; const name = req.query.name; if (name) { - where += ` AND g.name ILIKE %:name% `; + where += ` AND g.name ILIKE :search `; } const sourcePartnerId = req.query.sourcePartnerId; @@ -2211,6 +2216,13 @@ module.exports = function (app) { memberLevel = ` gmu.level AS "userLevel", `; memberJoin = ` LEFT JOIN "GroupMemberUsers" gmu ON gmu."groupId" = g.id AND gmu."userId" = :userId ` } + if (country) { + where += ` AND g.country=:country `; + } + + if (language) { + where += ` AND g.language=:language `; + } const groups = await db .query(` SELECT @@ -2223,6 +2235,7 @@ module.exports = function (app) { g.country, g.language, g.contact, + ga."updatedAt" AS "latestActivity", g.visibility, gj.token as "join.token", gj.level as "join.level", @@ -2230,10 +2243,47 @@ module.exports = function (app) { c.id as "creator.id", c.name as "creator.name", c.company as "creator.company", + mc.count as "members.users.count", + COALESCE(gtc.count, 0) as "members.topics.count", + gt."topicId" as "members.topics.latest.id", + gt.title as "members.topics.latest.title", count(*) OVER()::integer AS "countTotal" FROM "Groups" g JOIN "Users" c ON c.id = g."creatorId" + LEFT JOIN ( + SELECT + MAX("updatedAt") as "updatedAt", + "groupIds" + FROM + "Activities" WHERE array_length("groupIds", 1) > 0 + GROUP BY "groupIds" ORDER BY "groupIds" + ) ga ON g.id::text = ANY(ga."groupIds") LEFT JOIN "GroupJoins" gj ON gj."groupId" = g.id + JOIN ( + SELECT "groupId", count("userId") AS "count" + FROM "GroupMemberUsers" + WHERE "deletedAt" IS NULL + GROUP BY "groupId" + ) AS mc ON (mc."groupId" = g.id) + LEFT JOIN ( + SELECT + tmg."groupId", + count(tmg."topicId") AS "count" + FROM "TopicMemberGroups" tmg + WHERE tmg."deletedAt" IS NULL + GROUP BY tmg."groupId" + ) AS gtc ON (gtc."groupId" = g.id) + LEFT JOIN ( + SELECT + tmg."groupId", + tmg."topicId", + t.title + FROM "TopicMemberGroups" tmg + LEFT JOIN "Topics" t ON (t.id = tmg."topicId") + WHERE tmg."deletedAt" IS NULL + AND t.visibility = 'public' + ORDER BY t."updatedAt" ASC + ) AS gt ON (gt."groupId" = g.id) ${memberJoin} WHERE ${where} ${orderBySql} @@ -2247,6 +2297,9 @@ module.exports = function (app) { sourcePartnerId, orderBy, order, + country, + language, + search: `%${name}%`, offset }, type: db.QueryTypes.SELECT, diff --git a/routes/api/topic.js b/routes/api/topic.js index ed280bf8c..a94e194eb 100644 --- a/routes/api/topic.js +++ b/routes/api/topic.js @@ -2071,6 +2071,8 @@ module.exports = function (app) { const creatorId = req.query.creatorId; let statuses = req.query.statuses; const favourite = req.query.favourite; + const country = req.query.country; + const language = req.query.language; const hasVoted = req.query.hasVoted; // Filter out Topics where User has participated in the voting process. const showModerated = req.query.showModerated || false; if (statuses && !Array.isArray(statuses)) { @@ -2132,6 +2134,14 @@ module.exports = function (app) { AND t.title IS NOT NULL AND COALESCE(tmup.level, tmgp.level, 'none')::"enum_TopicMemberUsers_level" > 'none' `; + let categories = req.query.categories; + if (categories && !Array.isArray(categories)) { + categories = [categories]; + } + + if (categories && categories.length) { + where += `AND t."categories" @> ARRAY[:categories]::VARCHAR(255)[] `; + } // All partners should see only Topics created by their site, but our own app sees all. if (partnerId) { where += ` AND t."sourcePartnerId" = :partnerId `; @@ -2149,6 +2159,14 @@ module.exports = function (app) { where += ` AND tf."topicId" = t.id AND tf."userId" = :userId`; } + if (country) { + where += ` AND t.country=:country `; + } + + if (language) { + where += ` AND t.language=:language `; + } + if (['true', '1'].includes(hasVoted)) { where += ` AND EXISTS (SELECT TRUE FROM "VoteLists" vl WHERE vl."voteId" = tv."voteId" AND vl."userId" = :userId LIMIT 1)`; } else if (['false', '0'].includes(hasVoted)) { @@ -2171,6 +2189,12 @@ module.exports = function (app) { } } + let title = req.query.title || req.query.search; + if (title) { + title = `%${title}%`; + where += ` AND t.title ILIKE :title `; + } + // TODO: NOT THE MOST EFFICIENT QUERY IN THE WORLD, tune it when time. // TODO: That casting to "enum_TopicMemberUsers_level". Sequelize does not support naming enums, through inheritance I have 2 enums that are the same but with different name thus different type in PG. Feature request - https://github.com/sequelize/sequelize/issues/2577 const query = ` @@ -2331,11 +2355,15 @@ module.exports = function (app) { query, { replacements: { + categories: categories, userId: userId, partnerId: partnerId, visibility: visibility, statuses: statuses, - creatorId: creatorId + creatorId: creatorId, + title: title, + language: language, + country: country }, type: db.QueryTypes.SELECT, raw: true, @@ -2409,6 +2437,8 @@ module.exports = function (app) { let returncolumns = ''; let voteResults = false; let showModerated = req.query.showModerated || false; + let country = req.query.country; + let language = req.query.language; const offset = parseInt(req.query.offset, 10) ? parseInt(req.query.offset, 10) : 0; let limit = parseInt(req.query.limit, 10) ? parseInt(req.query.limit, 10) : limitDefault; @@ -2491,6 +2521,14 @@ module.exports = function (app) { where += ' AND t.status IN (:statuses)'; } + if (country) { + where += ` AND t.country=:country `; + } + + if (language) { + where += ` AND t.language=:language `; + } + let sourcePartnerId = req.query.sourcePartnerId; if (sourcePartnerId) { if (!Array.isArray(sourcePartnerId)) { @@ -2637,7 +2675,9 @@ module.exports = function (app) { statuses: statuses, limit: limit, title: title, - offset: offset + offset: offset, + country: country, + language: language }, type: db.QueryTypes.SELECT, raw: true, @@ -2844,7 +2884,7 @@ module.exports = function (app) { /** * Get all member Users of the Topic */ - app.get('/api/users/:userId/topics/:topicId/members/users', loginCheck(['partner']), isModerator(), hasPermission(TopicMemberUser.LEVELS.read), async function (req, res, next) { + app.get('/api/users/:userId/topics/:topicId/members/users', loginCheck(['partner']), isModerator(), hasPermission(TopicMemberUser.LEVELS.read, true), async function (req, res, next) { const limitDefault = 10; const offset = parseInt(req.query.offset, 10) ? parseInt(req.query.offset, 10) : 0; let limit = parseInt(req.query.limit, 10) ? parseInt(req.query.limit, 10) : limitDefault; @@ -2997,7 +3037,116 @@ module.exports = function (app) { /** * Get all member Groups of the Topic */ - app.get('/api/users/:userId/topics/:topicId/members/groups', loginCheck(['partner']), hasPermission(TopicMemberUser.LEVELS.read), async function (req, res, next) { + app.get('/api/topics/:topicId/members/groups', async function (req, res, next) { + const limitDefault = 10; + const offset = parseInt(req.query.offset, 10) ? parseInt(req.query.offset, 10) : 0; + let limit = parseInt(req.query.limit, 10) ? parseInt(req.query.limit, 10) : limitDefault; + const search = req.query.search; + const order = req.query.order; + let sortOrder = req.query.sortOrder || 'ASC'; + + if (sortOrder && ['asc', 'desc'].indexOf(sortOrder.toLowerCase()) === -1) { + sortOrder = 'ASC'; + } + + let sortSql = ` ORDER BY `; + + if (order) { + switch (order) { + case 'name': + sortSql += ` mg.name ${sortOrder} `; + break; + case 'level': + sortSql += ` mg."level"::"enum_TopicMemberGroups_level" ${sortOrder} `; + break; + case 'members.users.count': + sortSql += ` mg."members.users.count" ${sortOrder} `; + break; + default: + sortSql = ` ` + } + } else { + sortSql = ` `; + } + + let where = ''; + if (search) { + where = `WHERE mg.name ILIKE :search` + } + let userLevelField = ''; + let userLevelJoin =''; + if (req.user?.id) { + userLevelField = ` gmu.level as "permission.level", `, + userLevelJoin = ` LEFT JOIN "GroupMemberUsers" gmu ON (gmu."groupId" = g.id AND gmu."userId" = :userId AND gmu."deletedAt" IS NULL) `; + } + try { + const groups = await db + .query( + ` + SELECT mg.*,count(*) OVER()::integer AS "countTotal" FROM ( + SELECT + g.id, + g.name, + tmg.level, + ${userLevelField} + g.visibility, + gmuc.count as "members.users.count" + FROM "TopicMemberGroups" tmg + JOIN "Groups" g ON (tmg."groupId" = g.id) + JOIN ( + SELECT + "groupId", + COUNT(*) as count + FROM "GroupMemberUsers" + WHERE "deletedAt" IS NULL + GROUP BY 1 + ) as gmuc ON (gmuc."groupId" = g.id) + ${userLevelJoin} + WHERE tmg."topicId" = :topicId AND g."visibility" = 'public' + AND tmg."deletedAt" IS NULL + AND g."deletedAt" IS NULL + ORDER BY level DESC + ) mg + ${where} + ${sortSql} + LIMIT :limit + OFFSET :offset;`, + { + replacements: { + topicId: req.params.topicId, + userId: req.user?.userId, + search: `%${search}%`, + limit, + offset + }, + type: db.QueryTypes.SELECT, + raw: true, + nest: true + } + ); + + let countTotal = 0; + if (groups && groups.length) { + countTotal = groups[0].countTotal; + } + groups.forEach(function (group) { + delete group.countTotal; + }); + + return res.ok({ + countTotal, + count: groups.length, + rows: groups + }); + } catch (err) { + return next(err); + } + }); + + /** + * Get all member Groups of the Topic + */ + app.get('/api/users/:userId/topics/:topicId/members/groups', loginCheck(['partner']), hasPermission(TopicMemberUser.LEVELS.read, true), async function (req, res, next) { const limitDefault = 10; const offset = parseInt(req.query.offset, 10) ? parseInt(req.query.offset, 10) : 0; let limit = parseInt(req.query.limit, 10) ? parseInt(req.query.limit, 10) : limitDefault;