diff --git a/502.html b/502.html index 53732a6..ccedbbe 100644 --- a/502.html +++ b/502.html @@ -129,7 +129,7 @@

The application is currently launching. The page will automatically refresh once site is available.

-

Adminpro

+

society-community-portal

Admin portal for management team.

diff --git a/README.md b/README.md index c2fb511..4b3e25d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Adminpro +# society-community-portal ## This project was generated by [Flatlogic Platform](https://flatlogic.com). diff --git a/app-shell/src/_schema.json b/app-shell/src/_schema.json index 4009a31..c1141d7 100644 --- a/app-shell/src/_schema.json +++ b/app-shell/src/_schema.json @@ -1,4 +1,5 @@ { "Initial version": "{\"iv\":\"DFwAjJtPtbc8CvjV\",\"encryptedData\":\"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\"}", - "Society admin portal": "{\"iv\":\"3H2yb9Cm1+0ds1Ow\",\"encryptedData\":\"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\"}" + "Society admin portal": "{\"iv\":\"3H2yb9Cm1+0ds1Ow\",\"encryptedData\":\"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\"}", + "Society Community portal": "{\"iv\":\"+rK8HORjiw5Wcjed\",\"encryptedData\":\"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\"}" } \ No newline at end of file diff --git a/backend/README.md b/backend/README.md index a4ebd6c..835df29 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,4 +1,4 @@ -#Adminpro - template backend, +#society-community-portal - template backend, #### Run App on local machine: @@ -38,10 +38,10 @@ - Type this command to creating a new database. - - `postgres=> CREATE DATABASE db_adminpro;` + - `postgres=> CREATE DATABASE db_society_community_portal;` - Then give that new user privileges to the new database then quit the `psql`. - - `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_adminpro TO admin;` + - `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_society_community_portal TO admin;` - `postgres=> \q` --- diff --git a/backend/package.json b/backend/package.json index 285f97c..4eef1f9 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { - "name": "adminpro", - "description": "Adminpro - template backend", + "name": "societycommunityportal", + "description": "society-community-portal - template backend", "scripts": { "start": "npm run db:migrate && npm run db:seed && npm run watch", "db:migrate": "sequelize-cli db:migrate", diff --git a/backend/src/config.js b/backend/src/config.js index e08b7dd..3266419 100644 --- a/backend/src/config.js +++ b/backend/src/config.js @@ -3,7 +3,7 @@ const os = require('os'); const config = { gcloud: { bucket: 'fldemo-files', - hash: '9c474fb5916d5795b943fa0f92fc45ed', + hash: 'afeefb9d49f5b7977577876b99532ac7', }, bcrypt: { saltRounds: 12, @@ -36,7 +36,7 @@ const config = { }, uploadDir: os.tmpdir(), email: { - from: 'Adminpro ', + from: 'society-community-portal ', host: 'email-smtp.us-east-1.amazonaws.com', port: 587, auth: { diff --git a/backend/src/db/api/chatmessages.js b/backend/src/db/api/chatmessages.js new file mode 100644 index 0000000..9c9533a --- /dev/null +++ b/backend/src/db/api/chatmessages.js @@ -0,0 +1,370 @@ +const db = require('../models'); +const FileDBApi = require('./file'); +const crypto = require('crypto'); +const Utils = require('../utils'); + +const Sequelize = db.Sequelize; +const Op = Sequelize.Op; + +module.exports = class ChatmessagesDBApi { + static async create(data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const chatmessages = await db.chatmessages.create( + { + id: data.id || undefined, + + content: data.content || null, + created_date: data.created_date || null, + importHash: data.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + }, + { transaction }, + ); + + await chatmessages.setChatroom(data.chatroom || null, { + transaction, + }); + + await chatmessages.setSender(data.sender || null, { + transaction, + }); + + return chatmessages; + } + + static async bulkImport(data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + // Prepare data - wrapping individual data transformations in a map() method + const chatmessagesData = data.map((item, index) => ({ + id: item.id || undefined, + + content: item.content || null, + created_date: item.created_date || null, + importHash: item.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + createdAt: new Date(Date.now() + index * 1000), + })); + + // Bulk create items + const chatmessages = await db.chatmessages.bulkCreate(chatmessagesData, { + transaction, + }); + + // For each item created, replace relation files + + return chatmessages; + } + + static async update(id, data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const chatmessages = await db.chatmessages.findByPk( + id, + {}, + { transaction }, + ); + + const updatePayload = {}; + + if (data.content !== undefined) updatePayload.content = data.content; + + if (data.created_date !== undefined) + updatePayload.created_date = data.created_date; + + updatePayload.updatedById = currentUser.id; + + await chatmessages.update(updatePayload, { transaction }); + + if (data.chatroom !== undefined) { + await chatmessages.setChatroom( + data.chatroom, + + { transaction }, + ); + } + + if (data.sender !== undefined) { + await chatmessages.setSender( + data.sender, + + { transaction }, + ); + } + + return chatmessages; + } + + static async deleteByIds(ids, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const chatmessages = await db.chatmessages.findAll({ + where: { + id: { + [Op.in]: ids, + }, + }, + transaction, + }); + + await db.sequelize.transaction(async (transaction) => { + for (const record of chatmessages) { + await record.update({ deletedBy: currentUser.id }, { transaction }); + } + for (const record of chatmessages) { + await record.destroy({ transaction }); + } + }); + + return chatmessages; + } + + static async remove(id, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const chatmessages = await db.chatmessages.findByPk(id, options); + + await chatmessages.update( + { + deletedBy: currentUser.id, + }, + { + transaction, + }, + ); + + await chatmessages.destroy({ + transaction, + }); + + return chatmessages; + } + + static async findBy(where, options) { + const transaction = (options && options.transaction) || undefined; + + const chatmessages = await db.chatmessages.findOne( + { where }, + { transaction }, + ); + + if (!chatmessages) { + return chatmessages; + } + + const output = chatmessages.get({ plain: true }); + + output.chatroom = await chatmessages.getChatroom({ + transaction, + }); + + output.sender = await chatmessages.getSender({ + transaction, + }); + + return output; + } + + static async findAll(filter, options) { + const limit = filter.limit || 0; + let offset = 0; + let where = {}; + const currentPage = +filter.page; + + offset = currentPage * limit; + + const orderBy = null; + + const transaction = (options && options.transaction) || undefined; + + let include = [ + { + model: db.chatrooms, + as: 'chatroom', + + where: filter.chatroom + ? { + [Op.or]: [ + { + id: { + [Op.in]: filter.chatroom + .split('|') + .map((term) => Utils.uuid(term)), + }, + }, + { + name: { + [Op.or]: filter.chatroom + .split('|') + .map((term) => ({ [Op.iLike]: `%${term}%` })), + }, + }, + ], + } + : {}, + }, + + { + model: db.users, + as: 'sender', + + where: filter.sender + ? { + [Op.or]: [ + { + id: { + [Op.in]: filter.sender + .split('|') + .map((term) => Utils.uuid(term)), + }, + }, + { + firstName: { + [Op.or]: filter.sender + .split('|') + .map((term) => ({ [Op.iLike]: `%${term}%` })), + }, + }, + ], + } + : {}, + }, + ]; + + if (filter) { + if (filter.id) { + where = { + ...where, + ['id']: Utils.uuid(filter.id), + }; + } + + if (filter.content) { + where = { + ...where, + [Op.and]: Utils.ilike('chatmessages', 'content', filter.content), + }; + } + + if (filter.created_dateRange) { + const [start, end] = filter.created_dateRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + created_date: { + ...where.created_date, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + created_date: { + ...where.created_date, + [Op.lte]: end, + }, + }; + } + } + + if (filter.active !== undefined) { + where = { + ...where, + active: filter.active === true || filter.active === 'true', + }; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + ['createdAt']: { + ...where.createdAt, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + ['createdAt']: { + ...where.createdAt, + [Op.lte]: end, + }, + }; + } + } + } + + const queryOptions = { + where, + include, + distinct: true, + order: + filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options?.transaction, + logging: console.log, + }; + + if (!options?.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await db.chatmessages.findAndCountAll( + queryOptions, + ); + + return { + rows: options?.countOnly ? [] : rows, + count: count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } + + static async findAllAutocomplete(query, limit, offset) { + let where = {}; + + if (query) { + where = { + [Op.or]: [ + { ['id']: Utils.uuid(query) }, + Utils.ilike('chatmessages', 'id', query), + ], + }; + } + + const records = await db.chatmessages.findAll({ + attributes: ['id', 'id'], + where, + limit: limit ? Number(limit) : undefined, + offset: offset ? Number(offset) : undefined, + orderBy: [['id', 'ASC']], + }); + + return records.map((record) => ({ + id: record.id, + label: record.id, + })); + } +}; diff --git a/backend/src/db/api/chatroomparticipants.js b/backend/src/db/api/chatroomparticipants.js new file mode 100644 index 0000000..2bf5208 --- /dev/null +++ b/backend/src/db/api/chatroomparticipants.js @@ -0,0 +1,334 @@ +const db = require('../models'); +const FileDBApi = require('./file'); +const crypto = require('crypto'); +const Utils = require('../utils'); + +const Sequelize = db.Sequelize; +const Op = Sequelize.Op; + +module.exports = class ChatroomparticipantsDBApi { + static async create(data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const chatroomparticipants = await db.chatroomparticipants.create( + { + id: data.id || undefined, + + importHash: data.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + }, + { transaction }, + ); + + await chatroomparticipants.setChatroom(data.chatroom || null, { + transaction, + }); + + await chatroomparticipants.setUser(data.user || null, { + transaction, + }); + + return chatroomparticipants; + } + + static async bulkImport(data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + // Prepare data - wrapping individual data transformations in a map() method + const chatroomparticipantsData = data.map((item, index) => ({ + id: item.id || undefined, + + importHash: item.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + createdAt: new Date(Date.now() + index * 1000), + })); + + // Bulk create items + const chatroomparticipants = await db.chatroomparticipants.bulkCreate( + chatroomparticipantsData, + { transaction }, + ); + + // For each item created, replace relation files + + return chatroomparticipants; + } + + static async update(id, data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const chatroomparticipants = await db.chatroomparticipants.findByPk( + id, + {}, + { transaction }, + ); + + const updatePayload = {}; + + updatePayload.updatedById = currentUser.id; + + await chatroomparticipants.update(updatePayload, { transaction }); + + if (data.chatroom !== undefined) { + await chatroomparticipants.setChatroom( + data.chatroom, + + { transaction }, + ); + } + + if (data.user !== undefined) { + await chatroomparticipants.setUser( + data.user, + + { transaction }, + ); + } + + return chatroomparticipants; + } + + static async deleteByIds(ids, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const chatroomparticipants = await db.chatroomparticipants.findAll({ + where: { + id: { + [Op.in]: ids, + }, + }, + transaction, + }); + + await db.sequelize.transaction(async (transaction) => { + for (const record of chatroomparticipants) { + await record.update({ deletedBy: currentUser.id }, { transaction }); + } + for (const record of chatroomparticipants) { + await record.destroy({ transaction }); + } + }); + + return chatroomparticipants; + } + + static async remove(id, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const chatroomparticipants = await db.chatroomparticipants.findByPk( + id, + options, + ); + + await chatroomparticipants.update( + { + deletedBy: currentUser.id, + }, + { + transaction, + }, + ); + + await chatroomparticipants.destroy({ + transaction, + }); + + return chatroomparticipants; + } + + static async findBy(where, options) { + const transaction = (options && options.transaction) || undefined; + + const chatroomparticipants = await db.chatroomparticipants.findOne( + { where }, + { transaction }, + ); + + if (!chatroomparticipants) { + return chatroomparticipants; + } + + const output = chatroomparticipants.get({ plain: true }); + + output.chatroom = await chatroomparticipants.getChatroom({ + transaction, + }); + + output.user = await chatroomparticipants.getUser({ + transaction, + }); + + return output; + } + + static async findAll(filter, options) { + const limit = filter.limit || 0; + let offset = 0; + let where = {}; + const currentPage = +filter.page; + + offset = currentPage * limit; + + const orderBy = null; + + const transaction = (options && options.transaction) || undefined; + + let include = [ + { + model: db.chatrooms, + as: 'chatroom', + + where: filter.chatroom + ? { + [Op.or]: [ + { + id: { + [Op.in]: filter.chatroom + .split('|') + .map((term) => Utils.uuid(term)), + }, + }, + { + name: { + [Op.or]: filter.chatroom + .split('|') + .map((term) => ({ [Op.iLike]: `%${term}%` })), + }, + }, + ], + } + : {}, + }, + + { + model: db.users, + as: 'user', + + where: filter.user + ? { + [Op.or]: [ + { + id: { + [Op.in]: filter.user + .split('|') + .map((term) => Utils.uuid(term)), + }, + }, + { + firstName: { + [Op.or]: filter.user + .split('|') + .map((term) => ({ [Op.iLike]: `%${term}%` })), + }, + }, + ], + } + : {}, + }, + ]; + + if (filter) { + if (filter.id) { + where = { + ...where, + ['id']: Utils.uuid(filter.id), + }; + } + + if (filter.active !== undefined) { + where = { + ...where, + active: filter.active === true || filter.active === 'true', + }; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + ['createdAt']: { + ...where.createdAt, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + ['createdAt']: { + ...where.createdAt, + [Op.lte]: end, + }, + }; + } + } + } + + const queryOptions = { + where, + include, + distinct: true, + order: + filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options?.transaction, + logging: console.log, + }; + + if (!options?.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await db.chatroomparticipants.findAndCountAll( + queryOptions, + ); + + return { + rows: options?.countOnly ? [] : rows, + count: count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } + + static async findAllAutocomplete(query, limit, offset) { + let where = {}; + + if (query) { + where = { + [Op.or]: [ + { ['id']: Utils.uuid(query) }, + Utils.ilike('chatroomparticipants', 'id', query), + ], + }; + } + + const records = await db.chatroomparticipants.findAll({ + attributes: ['id', 'id'], + where, + limit: limit ? Number(limit) : undefined, + offset: offset ? Number(offset) : undefined, + orderBy: [['id', 'ASC']], + }); + + return records.map((record) => ({ + id: record.id, + label: record.id, + })); + } +}; diff --git a/backend/src/db/api/chatrooms.js b/backend/src/db/api/chatrooms.js new file mode 100644 index 0000000..3ab189f --- /dev/null +++ b/backend/src/db/api/chatrooms.js @@ -0,0 +1,297 @@ +const db = require('../models'); +const FileDBApi = require('./file'); +const crypto = require('crypto'); +const Utils = require('../utils'); + +const Sequelize = db.Sequelize; +const Op = Sequelize.Op; + +module.exports = class ChatroomsDBApi { + static async create(data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const chatrooms = await db.chatrooms.create( + { + id: data.id || undefined, + + name: data.name || null, + type: data.type || null, + created_date: data.created_date || null, + importHash: data.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + }, + { transaction }, + ); + + return chatrooms; + } + + static async bulkImport(data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + // Prepare data - wrapping individual data transformations in a map() method + const chatroomsData = data.map((item, index) => ({ + id: item.id || undefined, + + name: item.name || null, + type: item.type || null, + created_date: item.created_date || null, + importHash: item.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + createdAt: new Date(Date.now() + index * 1000), + })); + + // Bulk create items + const chatrooms = await db.chatrooms.bulkCreate(chatroomsData, { + transaction, + }); + + // For each item created, replace relation files + + return chatrooms; + } + + static async update(id, data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const chatrooms = await db.chatrooms.findByPk(id, {}, { transaction }); + + const updatePayload = {}; + + if (data.name !== undefined) updatePayload.name = data.name; + + if (data.type !== undefined) updatePayload.type = data.type; + + if (data.created_date !== undefined) + updatePayload.created_date = data.created_date; + + updatePayload.updatedById = currentUser.id; + + await chatrooms.update(updatePayload, { transaction }); + + return chatrooms; + } + + static async deleteByIds(ids, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const chatrooms = await db.chatrooms.findAll({ + where: { + id: { + [Op.in]: ids, + }, + }, + transaction, + }); + + await db.sequelize.transaction(async (transaction) => { + for (const record of chatrooms) { + await record.update({ deletedBy: currentUser.id }, { transaction }); + } + for (const record of chatrooms) { + await record.destroy({ transaction }); + } + }); + + return chatrooms; + } + + static async remove(id, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const chatrooms = await db.chatrooms.findByPk(id, options); + + await chatrooms.update( + { + deletedBy: currentUser.id, + }, + { + transaction, + }, + ); + + await chatrooms.destroy({ + transaction, + }); + + return chatrooms; + } + + static async findBy(where, options) { + const transaction = (options && options.transaction) || undefined; + + const chatrooms = await db.chatrooms.findOne({ where }, { transaction }); + + if (!chatrooms) { + return chatrooms; + } + + const output = chatrooms.get({ plain: true }); + + output.chatmessages_chatroom = await chatrooms.getChatmessages_chatroom({ + transaction, + }); + + output.chatroomparticipants_chatroom = + await chatrooms.getChatroomparticipants_chatroom({ + transaction, + }); + + return output; + } + + static async findAll(filter, options) { + const limit = filter.limit || 0; + let offset = 0; + let where = {}; + const currentPage = +filter.page; + + offset = currentPage * limit; + + const orderBy = null; + + const transaction = (options && options.transaction) || undefined; + + let include = []; + + if (filter) { + if (filter.id) { + where = { + ...where, + ['id']: Utils.uuid(filter.id), + }; + } + + if (filter.name) { + where = { + ...where, + [Op.and]: Utils.ilike('chatrooms', 'name', filter.name), + }; + } + + if (filter.created_dateRange) { + const [start, end] = filter.created_dateRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + created_date: { + ...where.created_date, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + created_date: { + ...where.created_date, + [Op.lte]: end, + }, + }; + } + } + + if (filter.active !== undefined) { + where = { + ...where, + active: filter.active === true || filter.active === 'true', + }; + } + + if (filter.type) { + where = { + ...where, + type: filter.type, + }; + } + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + ['createdAt']: { + ...where.createdAt, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + ['createdAt']: { + ...where.createdAt, + [Op.lte]: end, + }, + }; + } + } + } + + const queryOptions = { + where, + include, + distinct: true, + order: + filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options?.transaction, + logging: console.log, + }; + + if (!options?.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await db.chatrooms.findAndCountAll(queryOptions); + + return { + rows: options?.countOnly ? [] : rows, + count: count, + }; + } catch (error) { + console.error('Error executing query:', error); + throw error; + } + } + + static async findAllAutocomplete(query, limit, offset) { + let where = {}; + + if (query) { + where = { + [Op.or]: [ + { ['id']: Utils.uuid(query) }, + Utils.ilike('chatrooms', 'name', query), + ], + }; + } + + const records = await db.chatrooms.findAll({ + attributes: ['id', 'name'], + where, + limit: limit ? Number(limit) : undefined, + offset: offset ? Number(offset) : undefined, + orderBy: [['name', 'ASC']], + }); + + return records.map((record) => ({ + id: record.id, + label: record.name, + })); + } +}; diff --git a/backend/src/db/api/users.js b/backend/src/db/api/users.js index 3a11e69..daac9c1 100644 --- a/backend/src/db/api/users.js +++ b/backend/src/db/api/users.js @@ -267,6 +267,16 @@ module.exports = class UsersDBApi { const output = users.get({ plain: true }); + output.chatmessages_sender = await users.getChatmessages_sender({ + transaction, + }); + + output.chatroomparticipants_user = await users.getChatroomparticipants_user( + { + transaction, + }, + ); + output.avatar = await users.getAvatar({ transaction, }); diff --git a/backend/src/db/db.config.js b/backend/src/db/db.config.js index b6a6f18..aae76fb 100644 --- a/backend/src/db/db.config.js +++ b/backend/src/db/db.config.js @@ -13,7 +13,7 @@ module.exports = { username: 'postgres', dialect: 'postgres', password: '', - database: 'db_adminpro', + database: 'db_society_community_portal', host: process.env.DB_HOST || 'localhost', logging: console.log, seederStorage: 'sequelize', diff --git a/backend/src/db/migrations/1750265371768.js b/backend/src/db/migrations/1750265371768.js new file mode 100644 index 0000000..5f83729 --- /dev/null +++ b/backend/src/db/migrations/1750265371768.js @@ -0,0 +1,72 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.createTable( + 'chatrooms', + { + id: { + type: Sequelize.DataTypes.UUID, + defaultValue: Sequelize.DataTypes.UUIDV4, + primaryKey: true, + }, + createdById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + updatedById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + importHash: { + type: Sequelize.DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.dropTable('chatrooms', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1750265419400.js b/backend/src/db/migrations/1750265419400.js new file mode 100644 index 0000000..ccdeb03 --- /dev/null +++ b/backend/src/db/migrations/1750265419400.js @@ -0,0 +1,47 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'chatrooms', + 'name', + { + type: Sequelize.DataTypes.TEXT, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('chatrooms', 'name', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1750265455323.js b/backend/src/db/migrations/1750265455323.js new file mode 100644 index 0000000..2093416 --- /dev/null +++ b/backend/src/db/migrations/1750265455323.js @@ -0,0 +1,49 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'chatrooms', + 'type', + { + type: Sequelize.DataTypes.ENUM, + + values: ['value'], + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('chatrooms', 'type', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1750265515961.js b/backend/src/db/migrations/1750265515961.js new file mode 100644 index 0000000..eb06bce --- /dev/null +++ b/backend/src/db/migrations/1750265515961.js @@ -0,0 +1,72 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.createTable( + 'chatmessages', + { + id: { + type: Sequelize.DataTypes.UUID, + defaultValue: Sequelize.DataTypes.UUIDV4, + primaryKey: true, + }, + createdById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + updatedById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + importHash: { + type: Sequelize.DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.dropTable('chatmessages', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1750265548741.js b/backend/src/db/migrations/1750265548741.js new file mode 100644 index 0000000..b78bd05 --- /dev/null +++ b/backend/src/db/migrations/1750265548741.js @@ -0,0 +1,49 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'chatrooms', + 'created_date', + { + type: Sequelize.DataTypes.DATE, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('chatrooms', 'created_date', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1750265578751.js b/backend/src/db/migrations/1750265578751.js new file mode 100644 index 0000000..144a251 --- /dev/null +++ b/backend/src/db/migrations/1750265578751.js @@ -0,0 +1,49 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'chatmessages', + 'content', + { + type: Sequelize.DataTypes.TEXT, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('chatmessages', 'content', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1750265604945.js b/backend/src/db/migrations/1750265604945.js new file mode 100644 index 0000000..037132a --- /dev/null +++ b/backend/src/db/migrations/1750265604945.js @@ -0,0 +1,49 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'chatmessages', + 'created_date', + { + type: Sequelize.DataTypes.DATE, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('chatmessages', 'created_date', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1750265634411.js b/backend/src/db/migrations/1750265634411.js new file mode 100644 index 0000000..9000cdf --- /dev/null +++ b/backend/src/db/migrations/1750265634411.js @@ -0,0 +1,72 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.createTable( + 'chatroomparticipants', + { + id: { + type: Sequelize.DataTypes.UUID, + defaultValue: Sequelize.DataTypes.UUIDV4, + primaryKey: true, + }, + createdById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + updatedById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + importHash: { + type: Sequelize.DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.dropTable('chatroomparticipants', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1750265662159.js b/backend/src/db/migrations/1750265662159.js new file mode 100644 index 0000000..ee3dbe9 --- /dev/null +++ b/backend/src/db/migrations/1750265662159.js @@ -0,0 +1,54 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'chatroomparticipants', + 'chatroomId', + { + type: Sequelize.DataTypes.UUID, + + references: { + model: 'chatrooms', + key: 'id', + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('chatroomparticipants', 'chatroomId', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1750265689235.js b/backend/src/db/migrations/1750265689235.js new file mode 100644 index 0000000..55fb170 --- /dev/null +++ b/backend/src/db/migrations/1750265689235.js @@ -0,0 +1,54 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'chatmessages', + 'chatroomId', + { + type: Sequelize.DataTypes.UUID, + + references: { + model: 'chatrooms', + key: 'id', + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('chatmessages', 'chatroomId', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1750265715124.js b/backend/src/db/migrations/1750265715124.js new file mode 100644 index 0000000..82a65c4 --- /dev/null +++ b/backend/src/db/migrations/1750265715124.js @@ -0,0 +1,54 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'chatroomparticipants', + 'userId', + { + type: Sequelize.DataTypes.UUID, + + references: { + model: 'users', + key: 'id', + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('chatroomparticipants', 'userId', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1750265741614.js b/backend/src/db/migrations/1750265741614.js new file mode 100644 index 0000000..cf6f5ff --- /dev/null +++ b/backend/src/db/migrations/1750265741614.js @@ -0,0 +1,54 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'chatmessages', + 'senderId', + { + type: Sequelize.DataTypes.UUID, + + references: { + model: 'users', + key: 'id', + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('chatmessages', 'senderId', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/models/chatmessages.js b/backend/src/db/models/chatmessages.js new file mode 100644 index 0000000..a28d989 --- /dev/null +++ b/backend/src/db/models/chatmessages.js @@ -0,0 +1,69 @@ +const config = require('../../config'); +const providers = config.providers; +const crypto = require('crypto'); +const bcrypt = require('bcrypt'); +const moment = require('moment'); + +module.exports = function (sequelize, DataTypes) { + const chatmessages = sequelize.define( + 'chatmessages', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + + content: { + type: DataTypes.TEXT, + }, + + created_date: { + type: DataTypes.DATE, + }, + + importHash: { + type: DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + chatmessages.associate = (db) => { + /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity + + //end loop + + db.chatmessages.belongsTo(db.chatrooms, { + as: 'chatroom', + foreignKey: { + name: 'chatroomId', + }, + constraints: false, + }); + + db.chatmessages.belongsTo(db.users, { + as: 'sender', + foreignKey: { + name: 'senderId', + }, + constraints: false, + }); + + db.chatmessages.belongsTo(db.users, { + as: 'createdBy', + }); + + db.chatmessages.belongsTo(db.users, { + as: 'updatedBy', + }); + }; + + return chatmessages; +}; diff --git a/backend/src/db/models/chatroomparticipants.js b/backend/src/db/models/chatroomparticipants.js new file mode 100644 index 0000000..cbdc007 --- /dev/null +++ b/backend/src/db/models/chatroomparticipants.js @@ -0,0 +1,61 @@ +const config = require('../../config'); +const providers = config.providers; +const crypto = require('crypto'); +const bcrypt = require('bcrypt'); +const moment = require('moment'); + +module.exports = function (sequelize, DataTypes) { + const chatroomparticipants = sequelize.define( + 'chatroomparticipants', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + + importHash: { + type: DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + chatroomparticipants.associate = (db) => { + /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity + + //end loop + + db.chatroomparticipants.belongsTo(db.chatrooms, { + as: 'chatroom', + foreignKey: { + name: 'chatroomId', + }, + constraints: false, + }); + + db.chatroomparticipants.belongsTo(db.users, { + as: 'user', + foreignKey: { + name: 'userId', + }, + constraints: false, + }); + + db.chatroomparticipants.belongsTo(db.users, { + as: 'createdBy', + }); + + db.chatroomparticipants.belongsTo(db.users, { + as: 'updatedBy', + }); + }; + + return chatroomparticipants; +}; diff --git a/backend/src/db/models/chatrooms.js b/backend/src/db/models/chatrooms.js new file mode 100644 index 0000000..b52d3b9 --- /dev/null +++ b/backend/src/db/models/chatrooms.js @@ -0,0 +1,75 @@ +const config = require('../../config'); +const providers = config.providers; +const crypto = require('crypto'); +const bcrypt = require('bcrypt'); +const moment = require('moment'); + +module.exports = function (sequelize, DataTypes) { + const chatrooms = sequelize.define( + 'chatrooms', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + + name: { + type: DataTypes.TEXT, + }, + + type: { + type: DataTypes.ENUM, + + values: ['value'], + }, + + created_date: { + type: DataTypes.DATE, + }, + + importHash: { + type: DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + chatrooms.associate = (db) => { + /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity + + db.chatrooms.hasMany(db.chatmessages, { + as: 'chatmessages_chatroom', + foreignKey: { + name: 'chatroomId', + }, + constraints: false, + }); + + db.chatrooms.hasMany(db.chatroomparticipants, { + as: 'chatroomparticipants_chatroom', + foreignKey: { + name: 'chatroomId', + }, + constraints: false, + }); + + //end loop + + db.chatrooms.belongsTo(db.users, { + as: 'createdBy', + }); + + db.chatrooms.belongsTo(db.users, { + as: 'updatedBy', + }); + }; + + return chatrooms; +}; diff --git a/backend/src/db/models/users.js b/backend/src/db/models/users.js index 74e549e..60179d4 100644 --- a/backend/src/db/models/users.js +++ b/backend/src/db/models/users.js @@ -102,6 +102,22 @@ module.exports = function (sequelize, DataTypes) { /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity + db.users.hasMany(db.chatmessages, { + as: 'chatmessages_sender', + foreignKey: { + name: 'senderId', + }, + constraints: false, + }); + + db.users.hasMany(db.chatroomparticipants, { + as: 'chatroomparticipants_user', + foreignKey: { + name: 'userId', + }, + constraints: false, + }); + //end loop db.users.belongsTo(db.roles, { diff --git a/backend/src/db/seeders/20200430130760-user-roles.js b/backend/src/db/seeders/20200430130760-user-roles.js index 6314b53..ace75c1 100644 --- a/backend/src/db/seeders/20200430130760-user-roles.js +++ b/backend/src/db/seeders/20200430130760-user-roles.js @@ -104,6 +104,9 @@ module.exports = { 'reports', 'roles', 'permissions', + 'chatrooms', + 'chatmessages', + 'chatroomparticipants', , ]; await queryInterface.bulkInsert( @@ -529,6 +532,81 @@ primary key ("roles_permissionsId", "permissionId") permissionId: getId('DELETE_PERMISSIONS'), }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('CREATE_CHATROOMS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('READ_CHATROOMS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('UPDATE_CHATROOMS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('DELETE_CHATROOMS'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('CREATE_CHATMESSAGES'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('READ_CHATMESSAGES'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('UPDATE_CHATMESSAGES'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('DELETE_CHATMESSAGES'), + }, + + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('CREATE_CHATROOMPARTICIPANTS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('READ_CHATROOMPARTICIPANTS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('UPDATE_CHATROOMPARTICIPANTS'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('DELETE_CHATROOMPARTICIPANTS'), + }, + { createdAt, updatedAt, diff --git a/backend/src/db/seeders/20231127130745-sample-data.js b/backend/src/db/seeders/20231127130745-sample-data.js index 53b3954..f0f7db6 100644 --- a/backend/src/db/seeders/20231127130745-sample-data.js +++ b/backend/src/db/seeders/20231127130745-sample-data.js @@ -5,6 +5,12 @@ const Departments = db.departments; const Reports = db.reports; +const Chatrooms = db.chatrooms; + +const Chatmessages = db.chatmessages; + +const Chatroomparticipants = db.chatroomparticipants; + const DepartmentsData = [ { name: 'Human Resources', @@ -29,6 +35,12 @@ const DepartmentsData = [ // type code here for "relation_many" field }, + + { + name: 'Sales', + + // type code here for "relation_many" field + }, ]; const ReportsData = [ @@ -55,21 +67,391 @@ const ReportsData = [ created_date: new Date('2023-04-05T11:15:00Z'), }, + + { + title: 'Sales Performance Review', + + created_date: new Date('2023-05-25T16:45:00Z'), + }, +]; + +const ChatroomsData = [ + { + name: 'Charles Darwin', + + type: 'value', + + created_date: new Date(Date.now()), + }, + + { + name: 'Emil Fischer', + + type: 'value', + + created_date: new Date(Date.now()), + }, + + { + name: 'Johannes Kepler', + + type: 'value', + + created_date: new Date(Date.now()), + }, + + { + name: 'Dmitri Mendeleev', + + type: 'value', + + created_date: new Date(Date.now()), + }, + + { + name: 'August Kekule', + + type: 'value', + + created_date: new Date(Date.now()), + }, +]; + +const ChatmessagesData = [ + { + content: 'Max Planck', + + created_date: new Date(Date.now()), + + // type code here for "relation_one" field + + // type code here for "relation_one" field + }, + + { + content: 'Enrico Fermi', + + created_date: new Date(Date.now()), + + // type code here for "relation_one" field + + // type code here for "relation_one" field + }, + + { + content: 'Charles Darwin', + + created_date: new Date(Date.now()), + + // type code here for "relation_one" field + + // type code here for "relation_one" field + }, + + { + content: 'Franz Boas', + + created_date: new Date(Date.now()), + + // type code here for "relation_one" field + + // type code here for "relation_one" field + }, + + { + content: 'Dmitri Mendeleev', + + created_date: new Date(Date.now()), + + // type code here for "relation_one" field + + // type code here for "relation_one" field + }, +]; + +const ChatroomparticipantsData = [ + { + // type code here for "relation_one" field + // type code here for "relation_one" field + }, + + { + // type code here for "relation_one" field + // type code here for "relation_one" field + }, + + { + // type code here for "relation_one" field + // type code here for "relation_one" field + }, + + { + // type code here for "relation_one" field + // type code here for "relation_one" field + }, + + { + // type code here for "relation_one" field + // type code here for "relation_one" field + }, ]; // Similar logic for "relation_many" // Similar logic for "relation_many" +async function associateChatmessageWithChatroom() { + const relatedChatroom0 = await Chatrooms.findOne({ + offset: Math.floor(Math.random() * (await Chatrooms.count())), + }); + const Chatmessage0 = await Chatmessages.findOne({ + order: [['id', 'ASC']], + offset: 0, + }); + if (Chatmessage0?.setChatroom) { + await Chatmessage0.setChatroom(relatedChatroom0); + } + + const relatedChatroom1 = await Chatrooms.findOne({ + offset: Math.floor(Math.random() * (await Chatrooms.count())), + }); + const Chatmessage1 = await Chatmessages.findOne({ + order: [['id', 'ASC']], + offset: 1, + }); + if (Chatmessage1?.setChatroom) { + await Chatmessage1.setChatroom(relatedChatroom1); + } + + const relatedChatroom2 = await Chatrooms.findOne({ + offset: Math.floor(Math.random() * (await Chatrooms.count())), + }); + const Chatmessage2 = await Chatmessages.findOne({ + order: [['id', 'ASC']], + offset: 2, + }); + if (Chatmessage2?.setChatroom) { + await Chatmessage2.setChatroom(relatedChatroom2); + } + + const relatedChatroom3 = await Chatrooms.findOne({ + offset: Math.floor(Math.random() * (await Chatrooms.count())), + }); + const Chatmessage3 = await Chatmessages.findOne({ + order: [['id', 'ASC']], + offset: 3, + }); + if (Chatmessage3?.setChatroom) { + await Chatmessage3.setChatroom(relatedChatroom3); + } + + const relatedChatroom4 = await Chatrooms.findOne({ + offset: Math.floor(Math.random() * (await Chatrooms.count())), + }); + const Chatmessage4 = await Chatmessages.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (Chatmessage4?.setChatroom) { + await Chatmessage4.setChatroom(relatedChatroom4); + } +} + +async function associateChatmessageWithSender() { + const relatedSender0 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Chatmessage0 = await Chatmessages.findOne({ + order: [['id', 'ASC']], + offset: 0, + }); + if (Chatmessage0?.setSender) { + await Chatmessage0.setSender(relatedSender0); + } + + const relatedSender1 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Chatmessage1 = await Chatmessages.findOne({ + order: [['id', 'ASC']], + offset: 1, + }); + if (Chatmessage1?.setSender) { + await Chatmessage1.setSender(relatedSender1); + } + + const relatedSender2 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Chatmessage2 = await Chatmessages.findOne({ + order: [['id', 'ASC']], + offset: 2, + }); + if (Chatmessage2?.setSender) { + await Chatmessage2.setSender(relatedSender2); + } + + const relatedSender3 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Chatmessage3 = await Chatmessages.findOne({ + order: [['id', 'ASC']], + offset: 3, + }); + if (Chatmessage3?.setSender) { + await Chatmessage3.setSender(relatedSender3); + } + + const relatedSender4 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Chatmessage4 = await Chatmessages.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (Chatmessage4?.setSender) { + await Chatmessage4.setSender(relatedSender4); + } +} + +async function associateChatroomparticipantWithChatroom() { + const relatedChatroom0 = await Chatrooms.findOne({ + offset: Math.floor(Math.random() * (await Chatrooms.count())), + }); + const Chatroomparticipant0 = await Chatroomparticipants.findOne({ + order: [['id', 'ASC']], + offset: 0, + }); + if (Chatroomparticipant0?.setChatroom) { + await Chatroomparticipant0.setChatroom(relatedChatroom0); + } + + const relatedChatroom1 = await Chatrooms.findOne({ + offset: Math.floor(Math.random() * (await Chatrooms.count())), + }); + const Chatroomparticipant1 = await Chatroomparticipants.findOne({ + order: [['id', 'ASC']], + offset: 1, + }); + if (Chatroomparticipant1?.setChatroom) { + await Chatroomparticipant1.setChatroom(relatedChatroom1); + } + + const relatedChatroom2 = await Chatrooms.findOne({ + offset: Math.floor(Math.random() * (await Chatrooms.count())), + }); + const Chatroomparticipant2 = await Chatroomparticipants.findOne({ + order: [['id', 'ASC']], + offset: 2, + }); + if (Chatroomparticipant2?.setChatroom) { + await Chatroomparticipant2.setChatroom(relatedChatroom2); + } + + const relatedChatroom3 = await Chatrooms.findOne({ + offset: Math.floor(Math.random() * (await Chatrooms.count())), + }); + const Chatroomparticipant3 = await Chatroomparticipants.findOne({ + order: [['id', 'ASC']], + offset: 3, + }); + if (Chatroomparticipant3?.setChatroom) { + await Chatroomparticipant3.setChatroom(relatedChatroom3); + } + + const relatedChatroom4 = await Chatrooms.findOne({ + offset: Math.floor(Math.random() * (await Chatrooms.count())), + }); + const Chatroomparticipant4 = await Chatroomparticipants.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (Chatroomparticipant4?.setChatroom) { + await Chatroomparticipant4.setChatroom(relatedChatroom4); + } +} + +async function associateChatroomparticipantWithUser() { + const relatedUser0 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Chatroomparticipant0 = await Chatroomparticipants.findOne({ + order: [['id', 'ASC']], + offset: 0, + }); + if (Chatroomparticipant0?.setUser) { + await Chatroomparticipant0.setUser(relatedUser0); + } + + const relatedUser1 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Chatroomparticipant1 = await Chatroomparticipants.findOne({ + order: [['id', 'ASC']], + offset: 1, + }); + if (Chatroomparticipant1?.setUser) { + await Chatroomparticipant1.setUser(relatedUser1); + } + + const relatedUser2 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Chatroomparticipant2 = await Chatroomparticipants.findOne({ + order: [['id', 'ASC']], + offset: 2, + }); + if (Chatroomparticipant2?.setUser) { + await Chatroomparticipant2.setUser(relatedUser2); + } + + const relatedUser3 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Chatroomparticipant3 = await Chatroomparticipants.findOne({ + order: [['id', 'ASC']], + offset: 3, + }); + if (Chatroomparticipant3?.setUser) { + await Chatroomparticipant3.setUser(relatedUser3); + } + + const relatedUser4 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Chatroomparticipant4 = await Chatroomparticipants.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (Chatroomparticipant4?.setUser) { + await Chatroomparticipant4.setUser(relatedUser4); + } +} + module.exports = { up: async (queryInterface, Sequelize) => { await Departments.bulkCreate(DepartmentsData); await Reports.bulkCreate(ReportsData); + await Chatrooms.bulkCreate(ChatroomsData); + + await Chatmessages.bulkCreate(ChatmessagesData); + + await Chatroomparticipants.bulkCreate(ChatroomparticipantsData); + await Promise.all([ // Similar logic for "relation_many" + // Similar logic for "relation_many" + + await associateChatmessageWithChatroom(), + + await associateChatmessageWithSender(), + + await associateChatroomparticipantWithChatroom(), + + await associateChatroomparticipantWithUser(), ]); }, @@ -77,5 +459,11 @@ module.exports = { await queryInterface.bulkDelete('departments', null, {}); await queryInterface.bulkDelete('reports', null, {}); + + await queryInterface.bulkDelete('chatrooms', null, {}); + + await queryInterface.bulkDelete('chatmessages', null, {}); + + await queryInterface.bulkDelete('chatroomparticipants', null, {}); }, }; diff --git a/backend/src/db/seeders/20250618164931.js b/backend/src/db/seeders/20250618164931.js new file mode 100644 index 0000000..f3d55e8 --- /dev/null +++ b/backend/src/db/seeders/20250618164931.js @@ -0,0 +1,87 @@ +const { v4: uuid } = require('uuid'); +const db = require('../models'); +const Sequelize = require('sequelize'); +const config = require('../../config'); + +module.exports = { + /** + * @param{import("sequelize").QueryInterface} queryInterface + * @return {Promise} + */ + async up(queryInterface) { + const createdAt = new Date(); + const updatedAt = new Date(); + + /** @type {Map} */ + const idMap = new Map(); + + /** + * @param {string} key + * @return {string} + */ + function getId(key) { + if (idMap.has(key)) { + return idMap.get(key); + } + const id = uuid(); + idMap.set(key, id); + return id; + } + + /** + * @param {string} name + */ + function createPermissions(name) { + return [ + { + id: getId(`CREATE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `CREATE_${name.toUpperCase()}`, + }, + { + id: getId(`READ_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `READ_${name.toUpperCase()}`, + }, + { + id: getId(`UPDATE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `UPDATE_${name.toUpperCase()}`, + }, + { + id: getId(`DELETE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `DELETE_${name.toUpperCase()}`, + }, + ]; + } + + const entities = ['chatrooms']; + + const createdPermissions = entities.flatMap(createPermissions); + + // Add permissions to database + await queryInterface.bulkInsert('permissions', createdPermissions); + // Get permissions ids + const permissionsIds = createdPermissions.map((p) => p.id); + // Get admin role + const adminRole = await db.roles.findOne({ + where: { name: config.roles.admin }, + }); + + if (adminRole) { + // Add permissions to admin role if it exists + await adminRole.addPermissions(permissionsIds); + } + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete( + 'permissions', + entities.flatMap(createPermissions), + ); + }, +}; diff --git a/backend/src/db/seeders/20250618165155.js b/backend/src/db/seeders/20250618165155.js new file mode 100644 index 0000000..44634a0 --- /dev/null +++ b/backend/src/db/seeders/20250618165155.js @@ -0,0 +1,87 @@ +const { v4: uuid } = require('uuid'); +const db = require('../models'); +const Sequelize = require('sequelize'); +const config = require('../../config'); + +module.exports = { + /** + * @param{import("sequelize").QueryInterface} queryInterface + * @return {Promise} + */ + async up(queryInterface) { + const createdAt = new Date(); + const updatedAt = new Date(); + + /** @type {Map} */ + const idMap = new Map(); + + /** + * @param {string} key + * @return {string} + */ + function getId(key) { + if (idMap.has(key)) { + return idMap.get(key); + } + const id = uuid(); + idMap.set(key, id); + return id; + } + + /** + * @param {string} name + */ + function createPermissions(name) { + return [ + { + id: getId(`CREATE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `CREATE_${name.toUpperCase()}`, + }, + { + id: getId(`READ_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `READ_${name.toUpperCase()}`, + }, + { + id: getId(`UPDATE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `UPDATE_${name.toUpperCase()}`, + }, + { + id: getId(`DELETE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `DELETE_${name.toUpperCase()}`, + }, + ]; + } + + const entities = ['chatmessages']; + + const createdPermissions = entities.flatMap(createPermissions); + + // Add permissions to database + await queryInterface.bulkInsert('permissions', createdPermissions); + // Get permissions ids + const permissionsIds = createdPermissions.map((p) => p.id); + // Get admin role + const adminRole = await db.roles.findOne({ + where: { name: config.roles.admin }, + }); + + if (adminRole) { + // Add permissions to admin role if it exists + await adminRole.addPermissions(permissionsIds); + } + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete( + 'permissions', + entities.flatMap(createPermissions), + ); + }, +}; diff --git a/backend/src/db/seeders/20250618165354.js b/backend/src/db/seeders/20250618165354.js new file mode 100644 index 0000000..01ce861 --- /dev/null +++ b/backend/src/db/seeders/20250618165354.js @@ -0,0 +1,87 @@ +const { v4: uuid } = require('uuid'); +const db = require('../models'); +const Sequelize = require('sequelize'); +const config = require('../../config'); + +module.exports = { + /** + * @param{import("sequelize").QueryInterface} queryInterface + * @return {Promise} + */ + async up(queryInterface) { + const createdAt = new Date(); + const updatedAt = new Date(); + + /** @type {Map} */ + const idMap = new Map(); + + /** + * @param {string} key + * @return {string} + */ + function getId(key) { + if (idMap.has(key)) { + return idMap.get(key); + } + const id = uuid(); + idMap.set(key, id); + return id; + } + + /** + * @param {string} name + */ + function createPermissions(name) { + return [ + { + id: getId(`CREATE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `CREATE_${name.toUpperCase()}`, + }, + { + id: getId(`READ_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `READ_${name.toUpperCase()}`, + }, + { + id: getId(`UPDATE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `UPDATE_${name.toUpperCase()}`, + }, + { + id: getId(`DELETE_${name.toUpperCase()}`), + createdAt, + updatedAt, + name: `DELETE_${name.toUpperCase()}`, + }, + ]; + } + + const entities = ['chatroomparticipants']; + + const createdPermissions = entities.flatMap(createPermissions); + + // Add permissions to database + await queryInterface.bulkInsert('permissions', createdPermissions); + // Get permissions ids + const permissionsIds = createdPermissions.map((p) => p.id); + // Get admin role + const adminRole = await db.roles.findOne({ + where: { name: config.roles.admin }, + }); + + if (adminRole) { + // Add permissions to admin role if it exists + await adminRole.addPermissions(permissionsIds); + } + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete( + 'permissions', + entities.flatMap(createPermissions), + ); + }, +}; diff --git a/backend/src/index.js b/backend/src/index.js index 91c52e2..dff70b2 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -29,6 +29,12 @@ const rolesRoutes = require('./routes/roles'); const permissionsRoutes = require('./routes/permissions'); +const chatroomsRoutes = require('./routes/chatrooms'); + +const chatmessagesRoutes = require('./routes/chatmessages'); + +const chatroomparticipantsRoutes = require('./routes/chatroomparticipants'); + const getBaseUrl = (url) => { if (!url) return ''; return url.endsWith('/api') ? url.slice(0, -4) : url; @@ -39,9 +45,9 @@ const options = { openapi: '3.0.0', info: { version: '1.0.0', - title: 'Adminpro', + title: 'society-community-portal', description: - 'Adminpro Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.', + 'society-community-portal Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.', }, servers: [ { @@ -124,6 +130,24 @@ app.use( permissionsRoutes, ); +app.use( + '/api/chatrooms', + passport.authenticate('jwt', { session: false }), + chatroomsRoutes, +); + +app.use( + '/api/chatmessages', + passport.authenticate('jwt', { session: false }), + chatmessagesRoutes, +); + +app.use( + '/api/chatroomparticipants', + passport.authenticate('jwt', { session: false }), + chatroomparticipantsRoutes, +); + app.use( '/api/openai', passport.authenticate('jwt', { session: false }), diff --git a/backend/src/routes/chatmessages.js b/backend/src/routes/chatmessages.js new file mode 100644 index 0000000..679a37d --- /dev/null +++ b/backend/src/routes/chatmessages.js @@ -0,0 +1,442 @@ +const express = require('express'); + +const ChatmessagesService = require('../services/chatmessages'); +const ChatmessagesDBApi = require('../db/api/chatmessages'); +const wrapAsync = require('../helpers').wrapAsync; + +const router = express.Router(); + +const { parse } = require('json2csv'); + +const { checkCrudPermissions } = require('../middlewares/check-permissions'); + +router.use(checkCrudPermissions('chatmessages')); + +/** + * @swagger + * components: + * schemas: + * Chatmessages: + * type: object + * properties: + + * content: + * type: string + * default: content + + */ + +/** + * @swagger + * tags: + * name: Chatmessages + * description: The Chatmessages managing API + */ + +/** + * @swagger + * /api/chatmessages: + * post: + * security: + * - bearerAuth: [] + * tags: [Chatmessages] + * summary: Add new item + * description: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * description: Data of the updated item + * type: object + * $ref: "#/components/schemas/Chatmessages" + * responses: + * 200: + * description: The item was successfully added + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatmessages" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 405: + * description: Invalid input data + * 500: + * description: Some server error + */ +router.post( + '/', + wrapAsync(async (req, res) => { + const referer = + req.headers.referer || + `${req.protocol}://${req.hostname}${req.originalUrl}`; + const link = new URL(referer); + await ChatmessagesService.create( + req.body.data, + req.currentUser, + true, + link.host, + ); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/budgets/bulk-import: + * post: + * security: + * - bearerAuth: [] + * tags: [Chatmessages] + * summary: Bulk import items + * description: Bulk import items + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * description: Data of the updated items + * type: array + * items: + * $ref: "#/components/schemas/Chatmessages" + * responses: + * 200: + * description: The items were successfully imported + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatmessages" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 405: + * description: Invalid input data + * 500: + * description: Some server error + * + */ +router.post( + '/bulk-import', + wrapAsync(async (req, res) => { + const referer = + req.headers.referer || + `${req.protocol}://${req.hostname}${req.originalUrl}`; + const link = new URL(referer); + await ChatmessagesService.bulkImport(req, res, true, link.host); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/chatmessages/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Chatmessages] + * summary: Update the data of the selected item + * description: Update the data of the selected item + * parameters: + * - in: path + * name: id + * description: Item ID to update + * required: true + * schema: + * type: string + * requestBody: + * description: Set new item data + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * description: ID of the updated item + * type: string + * data: + * description: Data of the updated item + * type: object + * $ref: "#/components/schemas/Chatmessages" + * required: + * - id + * responses: + * 200: + * description: The item data was successfully updated + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatmessages" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ +router.put( + '/:id', + wrapAsync(async (req, res) => { + await ChatmessagesService.update( + req.body.data, + req.body.id, + req.currentUser, + ); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/chatmessages/{id}: + * delete: + * security: + * - bearerAuth: [] + * tags: [Chatmessages] + * summary: Delete the selected item + * description: Delete the selected item + * parameters: + * - in: path + * name: id + * description: Item ID to delete + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatmessages" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ +router.delete( + '/:id', + wrapAsync(async (req, res) => { + await ChatmessagesService.remove(req.params.id, req.currentUser); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/chatmessages/deleteByIds: + * post: + * security: + * - bearerAuth: [] + * tags: [Chatmessages] + * summary: Delete the selected item list + * description: Delete the selected item list + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * ids: + * description: IDs of the updated items + * type: array + * responses: + * 200: + * description: The items was successfully deleted + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatmessages" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Items not found + * 500: + * description: Some server error + */ +router.post( + '/deleteByIds', + wrapAsync(async (req, res) => { + await ChatmessagesService.deleteByIds(req.body.data, req.currentUser); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/chatmessages: + * get: + * security: + * - bearerAuth: [] + * tags: [Chatmessages] + * summary: Get all chatmessages + * description: Get all chatmessages + * responses: + * 200: + * description: Chatmessages list successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Chatmessages" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get( + '/', + wrapAsync(async (req, res) => { + const filetype = req.query.filetype; + + const currentUser = req.currentUser; + const payload = await ChatmessagesDBApi.findAll(req.query, { currentUser }); + if (filetype && filetype === 'csv') { + const fields = ['id', 'content', 'created_date']; + const opts = { fields }; + try { + const csv = parse(payload.rows, opts); + res.status(200).attachment(csv); + res.send(csv); + } catch (err) { + console.error(err); + } + } else { + res.status(200).send(payload); + } + }), +); + +/** + * @swagger + * /api/chatmessages/count: + * get: + * security: + * - bearerAuth: [] + * tags: [Chatmessages] + * summary: Count all chatmessages + * description: Count all chatmessages + * responses: + * 200: + * description: Chatmessages count successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Chatmessages" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get( + '/count', + wrapAsync(async (req, res) => { + const currentUser = req.currentUser; + const payload = await ChatmessagesDBApi.findAll(req.query, null, { + countOnly: true, + currentUser, + }); + + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/chatmessages/autocomplete: + * get: + * security: + * - bearerAuth: [] + * tags: [Chatmessages] + * summary: Find all chatmessages that match search criteria + * description: Find all chatmessages that match search criteria + * responses: + * 200: + * description: Chatmessages list successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Chatmessages" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get('/autocomplete', async (req, res) => { + const payload = await ChatmessagesDBApi.findAllAutocomplete( + req.query.query, + req.query.limit, + req.query.offset, + ); + + res.status(200).send(payload); +}); + +/** + * @swagger + * /api/chatmessages/{id}: + * get: + * security: + * - bearerAuth: [] + * tags: [Chatmessages] + * summary: Get selected item + * description: Get selected item + * parameters: + * - in: path + * name: id + * description: ID of item to get + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatmessages" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ +router.get( + '/:id', + wrapAsync(async (req, res) => { + const payload = await ChatmessagesDBApi.findBy({ id: req.params.id }); + + res.status(200).send(payload); + }), +); + +router.use('/', require('../helpers').commonErrorHandler); + +module.exports = router; diff --git a/backend/src/routes/chatroomparticipants.js b/backend/src/routes/chatroomparticipants.js new file mode 100644 index 0000000..5806459 --- /dev/null +++ b/backend/src/routes/chatroomparticipants.js @@ -0,0 +1,445 @@ +const express = require('express'); + +const ChatroomparticipantsService = require('../services/chatroomparticipants'); +const ChatroomparticipantsDBApi = require('../db/api/chatroomparticipants'); +const wrapAsync = require('../helpers').wrapAsync; + +const router = express.Router(); + +const { parse } = require('json2csv'); + +const { checkCrudPermissions } = require('../middlewares/check-permissions'); + +router.use(checkCrudPermissions('chatroomparticipants')); + +/** + * @swagger + * components: + * schemas: + * Chatroomparticipants: + * type: object + * properties: + + */ + +/** + * @swagger + * tags: + * name: Chatroomparticipants + * description: The Chatroomparticipants managing API + */ + +/** + * @swagger + * /api/chatroomparticipants: + * post: + * security: + * - bearerAuth: [] + * tags: [Chatroomparticipants] + * summary: Add new item + * description: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * description: Data of the updated item + * type: object + * $ref: "#/components/schemas/Chatroomparticipants" + * responses: + * 200: + * description: The item was successfully added + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatroomparticipants" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 405: + * description: Invalid input data + * 500: + * description: Some server error + */ +router.post( + '/', + wrapAsync(async (req, res) => { + const referer = + req.headers.referer || + `${req.protocol}://${req.hostname}${req.originalUrl}`; + const link = new URL(referer); + await ChatroomparticipantsService.create( + req.body.data, + req.currentUser, + true, + link.host, + ); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/budgets/bulk-import: + * post: + * security: + * - bearerAuth: [] + * tags: [Chatroomparticipants] + * summary: Bulk import items + * description: Bulk import items + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * description: Data of the updated items + * type: array + * items: + * $ref: "#/components/schemas/Chatroomparticipants" + * responses: + * 200: + * description: The items were successfully imported + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatroomparticipants" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 405: + * description: Invalid input data + * 500: + * description: Some server error + * + */ +router.post( + '/bulk-import', + wrapAsync(async (req, res) => { + const referer = + req.headers.referer || + `${req.protocol}://${req.hostname}${req.originalUrl}`; + const link = new URL(referer); + await ChatroomparticipantsService.bulkImport(req, res, true, link.host); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/chatroomparticipants/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Chatroomparticipants] + * summary: Update the data of the selected item + * description: Update the data of the selected item + * parameters: + * - in: path + * name: id + * description: Item ID to update + * required: true + * schema: + * type: string + * requestBody: + * description: Set new item data + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * description: ID of the updated item + * type: string + * data: + * description: Data of the updated item + * type: object + * $ref: "#/components/schemas/Chatroomparticipants" + * required: + * - id + * responses: + * 200: + * description: The item data was successfully updated + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatroomparticipants" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ +router.put( + '/:id', + wrapAsync(async (req, res) => { + await ChatroomparticipantsService.update( + req.body.data, + req.body.id, + req.currentUser, + ); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/chatroomparticipants/{id}: + * delete: + * security: + * - bearerAuth: [] + * tags: [Chatroomparticipants] + * summary: Delete the selected item + * description: Delete the selected item + * parameters: + * - in: path + * name: id + * description: Item ID to delete + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatroomparticipants" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ +router.delete( + '/:id', + wrapAsync(async (req, res) => { + await ChatroomparticipantsService.remove(req.params.id, req.currentUser); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/chatroomparticipants/deleteByIds: + * post: + * security: + * - bearerAuth: [] + * tags: [Chatroomparticipants] + * summary: Delete the selected item list + * description: Delete the selected item list + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * ids: + * description: IDs of the updated items + * type: array + * responses: + * 200: + * description: The items was successfully deleted + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatroomparticipants" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Items not found + * 500: + * description: Some server error + */ +router.post( + '/deleteByIds', + wrapAsync(async (req, res) => { + await ChatroomparticipantsService.deleteByIds( + req.body.data, + req.currentUser, + ); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/chatroomparticipants: + * get: + * security: + * - bearerAuth: [] + * tags: [Chatroomparticipants] + * summary: Get all chatroomparticipants + * description: Get all chatroomparticipants + * responses: + * 200: + * description: Chatroomparticipants list successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Chatroomparticipants" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get( + '/', + wrapAsync(async (req, res) => { + const filetype = req.query.filetype; + + const currentUser = req.currentUser; + const payload = await ChatroomparticipantsDBApi.findAll(req.query, { + currentUser, + }); + if (filetype && filetype === 'csv') { + const fields = ['id']; + const opts = { fields }; + try { + const csv = parse(payload.rows, opts); + res.status(200).attachment(csv); + res.send(csv); + } catch (err) { + console.error(err); + } + } else { + res.status(200).send(payload); + } + }), +); + +/** + * @swagger + * /api/chatroomparticipants/count: + * get: + * security: + * - bearerAuth: [] + * tags: [Chatroomparticipants] + * summary: Count all chatroomparticipants + * description: Count all chatroomparticipants + * responses: + * 200: + * description: Chatroomparticipants count successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Chatroomparticipants" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get( + '/count', + wrapAsync(async (req, res) => { + const currentUser = req.currentUser; + const payload = await ChatroomparticipantsDBApi.findAll(req.query, null, { + countOnly: true, + currentUser, + }); + + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/chatroomparticipants/autocomplete: + * get: + * security: + * - bearerAuth: [] + * tags: [Chatroomparticipants] + * summary: Find all chatroomparticipants that match search criteria + * description: Find all chatroomparticipants that match search criteria + * responses: + * 200: + * description: Chatroomparticipants list successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Chatroomparticipants" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get('/autocomplete', async (req, res) => { + const payload = await ChatroomparticipantsDBApi.findAllAutocomplete( + req.query.query, + req.query.limit, + req.query.offset, + ); + + res.status(200).send(payload); +}); + +/** + * @swagger + * /api/chatroomparticipants/{id}: + * get: + * security: + * - bearerAuth: [] + * tags: [Chatroomparticipants] + * summary: Get selected item + * description: Get selected item + * parameters: + * - in: path + * name: id + * description: ID of item to get + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatroomparticipants" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ +router.get( + '/:id', + wrapAsync(async (req, res) => { + const payload = await ChatroomparticipantsDBApi.findBy({ + id: req.params.id, + }); + + res.status(200).send(payload); + }), +); + +router.use('/', require('../helpers').commonErrorHandler); + +module.exports = router; diff --git a/backend/src/routes/chatrooms.js b/backend/src/routes/chatrooms.js new file mode 100644 index 0000000..add4fa7 --- /dev/null +++ b/backend/src/routes/chatrooms.js @@ -0,0 +1,439 @@ +const express = require('express'); + +const ChatroomsService = require('../services/chatrooms'); +const ChatroomsDBApi = require('../db/api/chatrooms'); +const wrapAsync = require('../helpers').wrapAsync; + +const router = express.Router(); + +const { parse } = require('json2csv'); + +const { checkCrudPermissions } = require('../middlewares/check-permissions'); + +router.use(checkCrudPermissions('chatrooms')); + +/** + * @swagger + * components: + * schemas: + * Chatrooms: + * type: object + * properties: + + * name: + * type: string + * default: name + + * + */ + +/** + * @swagger + * tags: + * name: Chatrooms + * description: The Chatrooms managing API + */ + +/** + * @swagger + * /api/chatrooms: + * post: + * security: + * - bearerAuth: [] + * tags: [Chatrooms] + * summary: Add new item + * description: Add new item + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * description: Data of the updated item + * type: object + * $ref: "#/components/schemas/Chatrooms" + * responses: + * 200: + * description: The item was successfully added + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatrooms" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 405: + * description: Invalid input data + * 500: + * description: Some server error + */ +router.post( + '/', + wrapAsync(async (req, res) => { + const referer = + req.headers.referer || + `${req.protocol}://${req.hostname}${req.originalUrl}`; + const link = new URL(referer); + await ChatroomsService.create( + req.body.data, + req.currentUser, + true, + link.host, + ); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/budgets/bulk-import: + * post: + * security: + * - bearerAuth: [] + * tags: [Chatrooms] + * summary: Bulk import items + * description: Bulk import items + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * data: + * description: Data of the updated items + * type: array + * items: + * $ref: "#/components/schemas/Chatrooms" + * responses: + * 200: + * description: The items were successfully imported + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatrooms" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 405: + * description: Invalid input data + * 500: + * description: Some server error + * + */ +router.post( + '/bulk-import', + wrapAsync(async (req, res) => { + const referer = + req.headers.referer || + `${req.protocol}://${req.hostname}${req.originalUrl}`; + const link = new URL(referer); + await ChatroomsService.bulkImport(req, res, true, link.host); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/chatrooms/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Chatrooms] + * summary: Update the data of the selected item + * description: Update the data of the selected item + * parameters: + * - in: path + * name: id + * description: Item ID to update + * required: true + * schema: + * type: string + * requestBody: + * description: Set new item data + * required: true + * content: + * application/json: + * schema: + * properties: + * id: + * description: ID of the updated item + * type: string + * data: + * description: Data of the updated item + * type: object + * $ref: "#/components/schemas/Chatrooms" + * required: + * - id + * responses: + * 200: + * description: The item data was successfully updated + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatrooms" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ +router.put( + '/:id', + wrapAsync(async (req, res) => { + await ChatroomsService.update(req.body.data, req.body.id, req.currentUser); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/chatrooms/{id}: + * delete: + * security: + * - bearerAuth: [] + * tags: [Chatrooms] + * summary: Delete the selected item + * description: Delete the selected item + * parameters: + * - in: path + * name: id + * description: Item ID to delete + * required: true + * schema: + * type: string + * responses: + * 200: + * description: The item was successfully deleted + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatrooms" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ +router.delete( + '/:id', + wrapAsync(async (req, res) => { + await ChatroomsService.remove(req.params.id, req.currentUser); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/chatrooms/deleteByIds: + * post: + * security: + * - bearerAuth: [] + * tags: [Chatrooms] + * summary: Delete the selected item list + * description: Delete the selected item list + * requestBody: + * required: true + * content: + * application/json: + * schema: + * properties: + * ids: + * description: IDs of the updated items + * type: array + * responses: + * 200: + * description: The items was successfully deleted + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatrooms" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Items not found + * 500: + * description: Some server error + */ +router.post( + '/deleteByIds', + wrapAsync(async (req, res) => { + await ChatroomsService.deleteByIds(req.body.data, req.currentUser); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/chatrooms: + * get: + * security: + * - bearerAuth: [] + * tags: [Chatrooms] + * summary: Get all chatrooms + * description: Get all chatrooms + * responses: + * 200: + * description: Chatrooms list successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Chatrooms" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get( + '/', + wrapAsync(async (req, res) => { + const filetype = req.query.filetype; + + const currentUser = req.currentUser; + const payload = await ChatroomsDBApi.findAll(req.query, { currentUser }); + if (filetype && filetype === 'csv') { + const fields = ['id', 'name', 'created_date']; + const opts = { fields }; + try { + const csv = parse(payload.rows, opts); + res.status(200).attachment(csv); + res.send(csv); + } catch (err) { + console.error(err); + } + } else { + res.status(200).send(payload); + } + }), +); + +/** + * @swagger + * /api/chatrooms/count: + * get: + * security: + * - bearerAuth: [] + * tags: [Chatrooms] + * summary: Count all chatrooms + * description: Count all chatrooms + * responses: + * 200: + * description: Chatrooms count successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Chatrooms" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get( + '/count', + wrapAsync(async (req, res) => { + const currentUser = req.currentUser; + const payload = await ChatroomsDBApi.findAll(req.query, null, { + countOnly: true, + currentUser, + }); + + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/chatrooms/autocomplete: + * get: + * security: + * - bearerAuth: [] + * tags: [Chatrooms] + * summary: Find all chatrooms that match search criteria + * description: Find all chatrooms that match search criteria + * responses: + * 200: + * description: Chatrooms list successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Chatrooms" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get('/autocomplete', async (req, res) => { + const payload = await ChatroomsDBApi.findAllAutocomplete( + req.query.query, + req.query.limit, + req.query.offset, + ); + + res.status(200).send(payload); +}); + +/** + * @swagger + * /api/chatrooms/{id}: + * get: + * security: + * - bearerAuth: [] + * tags: [Chatrooms] + * summary: Get selected item + * description: Get selected item + * parameters: + * - in: path + * name: id + * description: ID of item to get + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Selected item successfully received + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Chatrooms" + * 400: + * description: Invalid ID supplied + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Item not found + * 500: + * description: Some server error + */ +router.get( + '/:id', + wrapAsync(async (req, res) => { + const payload = await ChatroomsDBApi.findBy({ id: req.params.id }); + + res.status(200).send(payload); + }), +); + +router.use('/', require('../helpers').commonErrorHandler); + +module.exports = router; diff --git a/backend/src/services/chatmessages.js b/backend/src/services/chatmessages.js new file mode 100644 index 0000000..0cd14ac --- /dev/null +++ b/backend/src/services/chatmessages.js @@ -0,0 +1,117 @@ +const db = require('../db/models'); +const ChatmessagesDBApi = require('../db/api/chatmessages'); +const processFile = require('../middlewares/upload'); +const ValidationError = require('./notifications/errors/validation'); +const csv = require('csv-parser'); +const axios = require('axios'); +const config = require('../config'); +const stream = require('stream'); + +module.exports = class ChatmessagesService { + static async create(data, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + await ChatmessagesDBApi.create(data, { + currentUser, + transaction, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async bulkImport(req, res, sendInvitationEmails = true, host) { + const transaction = await db.sequelize.transaction(); + + try { + await processFile(req, res); + const bufferStream = new stream.PassThrough(); + const results = []; + + await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); // convert Buffer to Stream + + await new Promise((resolve, reject) => { + bufferStream + .pipe(csv()) + .on('data', (data) => results.push(data)) + .on('end', async () => { + console.log('CSV results', results); + resolve(); + }) + .on('error', (error) => reject(error)); + }); + + await ChatmessagesDBApi.bulkImport(results, { + transaction, + ignoreDuplicates: true, + validate: true, + currentUser: req.currentUser, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async update(data, id, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + let chatmessages = await ChatmessagesDBApi.findBy( + { id }, + { transaction }, + ); + + if (!chatmessages) { + throw new ValidationError('chatmessagesNotFound'); + } + + const updatedChatmessages = await ChatmessagesDBApi.update(id, data, { + currentUser, + transaction, + }); + + await transaction.commit(); + return updatedChatmessages; + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async deleteByIds(ids, currentUser) { + const transaction = await db.sequelize.transaction(); + + try { + await ChatmessagesDBApi.deleteByIds(ids, { + currentUser, + transaction, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async remove(id, currentUser) { + const transaction = await db.sequelize.transaction(); + + try { + await ChatmessagesDBApi.remove(id, { + currentUser, + transaction, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } +}; diff --git a/backend/src/services/chatroomparticipants.js b/backend/src/services/chatroomparticipants.js new file mode 100644 index 0000000..675b57f --- /dev/null +++ b/backend/src/services/chatroomparticipants.js @@ -0,0 +1,118 @@ +const db = require('../db/models'); +const ChatroomparticipantsDBApi = require('../db/api/chatroomparticipants'); +const processFile = require('../middlewares/upload'); +const ValidationError = require('./notifications/errors/validation'); +const csv = require('csv-parser'); +const axios = require('axios'); +const config = require('../config'); +const stream = require('stream'); + +module.exports = class ChatroomparticipantsService { + static async create(data, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + await ChatroomparticipantsDBApi.create(data, { + currentUser, + transaction, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async bulkImport(req, res, sendInvitationEmails = true, host) { + const transaction = await db.sequelize.transaction(); + + try { + await processFile(req, res); + const bufferStream = new stream.PassThrough(); + const results = []; + + await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); // convert Buffer to Stream + + await new Promise((resolve, reject) => { + bufferStream + .pipe(csv()) + .on('data', (data) => results.push(data)) + .on('end', async () => { + console.log('CSV results', results); + resolve(); + }) + .on('error', (error) => reject(error)); + }); + + await ChatroomparticipantsDBApi.bulkImport(results, { + transaction, + ignoreDuplicates: true, + validate: true, + currentUser: req.currentUser, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async update(data, id, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + let chatroomparticipants = await ChatroomparticipantsDBApi.findBy( + { id }, + { transaction }, + ); + + if (!chatroomparticipants) { + throw new ValidationError('chatroomparticipantsNotFound'); + } + + const updatedChatroomparticipants = + await ChatroomparticipantsDBApi.update(id, data, { + currentUser, + transaction, + }); + + await transaction.commit(); + return updatedChatroomparticipants; + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async deleteByIds(ids, currentUser) { + const transaction = await db.sequelize.transaction(); + + try { + await ChatroomparticipantsDBApi.deleteByIds(ids, { + currentUser, + transaction, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async remove(id, currentUser) { + const transaction = await db.sequelize.transaction(); + + try { + await ChatroomparticipantsDBApi.remove(id, { + currentUser, + transaction, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } +}; diff --git a/backend/src/services/chatrooms.js b/backend/src/services/chatrooms.js new file mode 100644 index 0000000..428e211 --- /dev/null +++ b/backend/src/services/chatrooms.js @@ -0,0 +1,114 @@ +const db = require('../db/models'); +const ChatroomsDBApi = require('../db/api/chatrooms'); +const processFile = require('../middlewares/upload'); +const ValidationError = require('./notifications/errors/validation'); +const csv = require('csv-parser'); +const axios = require('axios'); +const config = require('../config'); +const stream = require('stream'); + +module.exports = class ChatroomsService { + static async create(data, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + await ChatroomsDBApi.create(data, { + currentUser, + transaction, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async bulkImport(req, res, sendInvitationEmails = true, host) { + const transaction = await db.sequelize.transaction(); + + try { + await processFile(req, res); + const bufferStream = new stream.PassThrough(); + const results = []; + + await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); // convert Buffer to Stream + + await new Promise((resolve, reject) => { + bufferStream + .pipe(csv()) + .on('data', (data) => results.push(data)) + .on('end', async () => { + console.log('CSV results', results); + resolve(); + }) + .on('error', (error) => reject(error)); + }); + + await ChatroomsDBApi.bulkImport(results, { + transaction, + ignoreDuplicates: true, + validate: true, + currentUser: req.currentUser, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async update(data, id, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + let chatrooms = await ChatroomsDBApi.findBy({ id }, { transaction }); + + if (!chatrooms) { + throw new ValidationError('chatroomsNotFound'); + } + + const updatedChatrooms = await ChatroomsDBApi.update(id, data, { + currentUser, + transaction, + }); + + await transaction.commit(); + return updatedChatrooms; + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async deleteByIds(ids, currentUser) { + const transaction = await db.sequelize.transaction(); + + try { + await ChatroomsDBApi.deleteByIds(ids, { + currentUser, + transaction, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async remove(id, currentUser) { + const transaction = await db.sequelize.transaction(); + + try { + await ChatroomsDBApi.remove(id, { + currentUser, + transaction, + }); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } +}; diff --git a/backend/src/services/notifications/list.js b/backend/src/services/notifications/list.js index 9a363a7..77a5101 100644 --- a/backend/src/services/notifications/list.js +++ b/backend/src/services/notifications/list.js @@ -1,6 +1,6 @@ const errors = { app: { - title: 'Adminpro', + title: 'society-community-portal', }, auth: { diff --git a/backend/src/services/search.js b/backend/src/services/search.js index 05fd805..18ba9cd 100644 --- a/backend/src/services/search.js +++ b/backend/src/services/search.js @@ -46,6 +46,10 @@ module.exports = class SearchService { departments: ['name'], reports: ['title'], + + chatrooms: ['name'], + + chatmessages: ['content'], }; const columnsInt = {}; diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 6c9b041..6215971 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -25,7 +25,7 @@ services: - ./data/db:/var/lib/postgresql/data environment: - POSTGRES_HOST_AUTH_METHOD=trust - - POSTGRES_DB=db_adminpro + - POSTGRES_DB=db_society_community_portal ports: - "5432:5432" logging: diff --git a/frontend/README.md b/frontend/README.md index c36bd6b..180586d 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,4 +1,4 @@ -# Adminpro +# society-community-portal ## This project was generated by Flatlogic Platform. diff --git a/frontend/src/components/AsideMenuLayer.tsx b/frontend/src/components/AsideMenuLayer.tsx index 4008b92..6e6213c 100644 --- a/frontend/src/components/AsideMenuLayer.tsx +++ b/frontend/src/components/AsideMenuLayer.tsx @@ -45,7 +45,7 @@ export default function AsideMenuLayer({ >
- Adminpro + society-community-portal
)} + + {hasPermission(currentUser, 'READ_CHATROOMS') && ( + +
+
+
+
+ Chatrooms +
+
+ {chatrooms} +
+
+
+ +
+
+
+ + )} + + {hasPermission(currentUser, 'READ_CHATMESSAGES') && ( + +
+
+
+
+ Chatmessages +
+
+ {chatmessages} +
+
+
+ +
+
+
+ + )} + + {hasPermission(currentUser, 'READ_CHATROOMPARTICIPANTS') && ( + +
+
+
+
+ Chatroomparticipants +
+
+ {chatroomparticipants} +
+
+
+ +
+
+
+ + )} diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index a488d32..8877556 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -27,7 +27,7 @@ import FaqSection from '../components/WebPageComponents/FaqComponent'; export default function WebSite() { const cardsStyle = useAppSelector((state) => state.style.cardsStyle); const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const projectName = 'Adminpro'; + const projectName = 'society-community-portal'; useEffect(() => { const darkElement = document.querySelector('body .dark'); @@ -125,10 +125,10 @@ export default function WebSite() { content={`Discover our comprehensive admin portal designed for management teams, offering robust features, seamless communication, and efficient data management.`} /> - +
- + ); } diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx index ff37b2d..3c185a1 100644 --- a/frontend/src/pages/login.tsx +++ b/frontend/src/pages/login.tsx @@ -52,7 +52,7 @@ export default function Login() { remember: true, }); - const title = 'Adminpro'; + const title = 'society-community-portal'; // Fetch Pexels image/video useEffect(() => { diff --git a/frontend/src/pages/privacy-policy.tsx b/frontend/src/pages/privacy-policy.tsx index 473508f..09744bc 100644 --- a/frontend/src/pages/privacy-policy.tsx +++ b/frontend/src/pages/privacy-policy.tsx @@ -5,7 +5,7 @@ import LayoutGuest from '../layouts/Guest'; import { getPageTitle } from '../config'; export default function PrivacyPolicy() { - const title = 'Adminpro'; + const title = 'society-community-portal'; const [projectUrl, setProjectUrl] = useState(''); useEffect(() => { diff --git a/frontend/src/pages/terms-of-use.tsx b/frontend/src/pages/terms-of-use.tsx index 41a1fd8..ebb6c97 100644 --- a/frontend/src/pages/terms-of-use.tsx +++ b/frontend/src/pages/terms-of-use.tsx @@ -5,7 +5,7 @@ import LayoutGuest from '../layouts/Guest'; import { getPageTitle } from '../config'; export default function PrivacyPolicy() { - const title = 'Adminpro'; + const title = 'society-community-portal'; const [projectUrl, setProjectUrl] = useState(''); useEffect(() => { diff --git a/frontend/src/pages/users/users-view.tsx b/frontend/src/pages/users/users-view.tsx index ce0851d..1c5f887 100644 --- a/frontend/src/pages/users/users-view.tsx +++ b/frontend/src/pages/users/users-view.tsx @@ -138,6 +138,82 @@ const UsersView = () => { + <> +

Chatmessages Sender

+ +
+ + + + + + + + + + {users.chatmessages_sender && + Array.isArray(users.chatmessages_sender) && + users.chatmessages_sender.map((item: any) => ( + + router.push( + `/chatmessages/chatmessages-view/?id=${item.id}`, + ) + } + > + + + + + ))} + +
ContentCreated date
{item.content} + {dataFormatter.dateTimeFormatter(item.created_date)} +
+
+ {!users?.chatmessages_sender?.length && ( +
No data
+ )} +
+ + + <> +

Chatroomparticipants User

+ +
+ + + + + + {users.chatroomparticipants_user && + Array.isArray(users.chatroomparticipants_user) && + users.chatroomparticipants_user.map((item: any) => ( + + router.push( + `/chatroomparticipants/chatroomparticipants-view/?id=${item.id}`, + ) + } + > + ))} + +
+
+ {!users?.chatroomparticipants_user?.length && ( +
No data
+ )} +
+ + state.style.cardsStyle); const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const projectName = 'Adminpro'; + const projectName = 'society-community-portal'; useEffect(() => { const darkElement = document.querySelector('body .dark'); @@ -96,10 +96,10 @@ export default function WebSite() { content={`Learn more about ${projectName}, our mission, values, and the innovative features that empower management teams to excel.`} /> - +
- + ); } diff --git a/frontend/src/pages/web_pages/contact.tsx b/frontend/src/pages/web_pages/contact.tsx index ada456a..8b030ce 100644 --- a/frontend/src/pages/web_pages/contact.tsx +++ b/frontend/src/pages/web_pages/contact.tsx @@ -21,7 +21,7 @@ import FaqSection from '../../components/WebPageComponents/FaqComponent'; export default function WebSite() { const cardsStyle = useAppSelector((state) => state.style.cardsStyle); const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const projectName = 'Adminpro'; + const projectName = 'society-community-portal'; useEffect(() => { const darkElement = document.querySelector('body .dark'); @@ -67,10 +67,10 @@ export default function WebSite() { content={`Get in touch with the ${projectName} team for inquiries, support, or feedback. We're here to assist you with any questions you may have.`} /> - +
- + ); } diff --git a/frontend/src/pages/web_pages/faq.tsx b/frontend/src/pages/web_pages/faq.tsx index 1a8c960..9dde8d3 100644 --- a/frontend/src/pages/web_pages/faq.tsx +++ b/frontend/src/pages/web_pages/faq.tsx @@ -18,7 +18,7 @@ import FaqSection from '../../components/WebPageComponents/FaqComponent'; export default function WebSite() { const cardsStyle = useAppSelector((state) => state.style.cardsStyle); const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const projectName = 'Adminpro'; + const projectName = 'society-community-portal'; useEffect(() => { const darkElement = document.querySelector('body .dark'); @@ -69,10 +69,10 @@ export default function WebSite() { content={`Find answers to common questions about ${projectName}. Learn more about our features, pricing, and support options.`} /> - +
- + ); } diff --git a/frontend/src/pages/web_pages/home.tsx b/frontend/src/pages/web_pages/home.tsx index 162eeb5..1b01664 100644 --- a/frontend/src/pages/web_pages/home.tsx +++ b/frontend/src/pages/web_pages/home.tsx @@ -27,7 +27,7 @@ import FaqSection from '../../components/WebPageComponents/FaqComponent'; export default function WebSite() { const cardsStyle = useAppSelector((state) => state.style.cardsStyle); const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const projectName = 'Adminpro'; + const projectName = 'society-community-portal'; useEffect(() => { const darkElement = document.querySelector('body .dark'); @@ -99,10 +99,10 @@ export default function WebSite() { content={`Discover our comprehensive admin portal designed for management teams, offering robust features, seamless communication, and efficient data management.`} /> - +
- + ); } diff --git a/frontend/src/pages/web_pages/services.tsx b/frontend/src/pages/web_pages/services.tsx index e7f956c..55b3b61 100644 --- a/frontend/src/pages/web_pages/services.tsx +++ b/frontend/src/pages/web_pages/services.tsx @@ -21,7 +21,7 @@ import FaqSection from '../../components/WebPageComponents/FaqComponent'; export default function WebSite() { const cardsStyle = useAppSelector((state) => state.style.cardsStyle); const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const projectName = 'Adminpro'; + const projectName = 'society-community-portal'; useEffect(() => { const darkElement = document.querySelector('body .dark'); @@ -88,10 +88,10 @@ export default function WebSite() { content={`Explore the range of services offered by ${projectName}, designed to enhance management efficiency and drive team success.`} /> - +
- + ); } diff --git a/frontend/src/stores/chatmessages/chatmessagesSlice.ts b/frontend/src/stores/chatmessages/chatmessagesSlice.ts new file mode 100644 index 0000000..a9806d4 --- /dev/null +++ b/frontend/src/stores/chatmessages/chatmessagesSlice.ts @@ -0,0 +1,241 @@ +import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import axios from 'axios'; +import { + fulfilledNotify, + rejectNotify, + resetNotify, +} from '../../helpers/notifyStateHandler'; + +interface MainState { + chatmessages: any; + loading: boolean; + count: number; + refetch: boolean; + rolesWidgets: any[]; + notify: { + showNotification: boolean; + textNotification: string; + typeNotification: string; + }; +} + +const initialState: MainState = { + chatmessages: [], + loading: false, + count: 0, + refetch: false, + rolesWidgets: [], + notify: { + showNotification: false, + textNotification: '', + typeNotification: 'warn', + }, +}; + +export const fetch = createAsyncThunk( + 'chatmessages/fetch', + async (data: any) => { + const { id, query } = data; + const result = await axios.get( + `chatmessages${query || (id ? `/${id}` : '')}`, + ); + return id + ? result.data + : { rows: result.data.rows, count: result.data.count }; + }, +); + +export const deleteItemsByIds = createAsyncThunk( + 'chatmessages/deleteByIds', + async (data: any, { rejectWithValue }) => { + try { + await axios.post('chatmessages/deleteByIds', { data }); + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const deleteItem = createAsyncThunk( + 'chatmessages/deleteChatmessages', + async (id: string, { rejectWithValue }) => { + try { + await axios.delete(`chatmessages/${id}`); + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const create = createAsyncThunk( + 'chatmessages/createChatmessages', + async (data: any, { rejectWithValue }) => { + try { + const result = await axios.post('chatmessages', { data }); + return result.data; + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const uploadCsv = createAsyncThunk( + 'chatmessages/uploadCsv', + async (file: File, { rejectWithValue }) => { + try { + const data = new FormData(); + data.append('file', file); + data.append('filename', file.name); + + const result = await axios.post('chatmessages/bulk-import', data, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + return result.data; + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const update = createAsyncThunk( + 'chatmessages/updateChatmessages', + async (payload: any, { rejectWithValue }) => { + try { + const result = await axios.put(`chatmessages/${payload.id}`, { + id: payload.id, + data: payload.data, + }); + return result.data; + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const chatmessagesSlice = createSlice({ + name: 'chatmessages', + initialState, + reducers: { + setRefetch: (state, action: PayloadAction) => { + state.refetch = action.payload; + }, + }, + extraReducers: (builder) => { + builder.addCase(fetch.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(fetch.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(fetch.fulfilled, (state, action) => { + if (action.payload.rows && action.payload.count >= 0) { + state.chatmessages = action.payload.rows; + state.count = action.payload.count; + } else { + state.chatmessages = action.payload; + } + state.loading = false; + }); + + builder.addCase(deleteItemsByIds.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + + builder.addCase(deleteItemsByIds.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, 'Chatmessages has been deleted'); + }); + + builder.addCase(deleteItemsByIds.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(deleteItem.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + + builder.addCase(deleteItem.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, `${'Chatmessages'.slice(0, -1)} has been deleted`); + }); + + builder.addCase(deleteItem.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(create.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(create.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(create.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, `${'Chatmessages'.slice(0, -1)} has been created`); + }); + + builder.addCase(update.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(update.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, `${'Chatmessages'.slice(0, -1)} has been updated`); + }); + builder.addCase(update.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(uploadCsv.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(uploadCsv.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, 'Chatmessages has been uploaded'); + }); + builder.addCase(uploadCsv.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + }, +}); + +// Action creators are generated for each case reducer function +export const { setRefetch } = chatmessagesSlice.actions; + +export default chatmessagesSlice.reducer; diff --git a/frontend/src/stores/chatroomparticipants/chatroomparticipantsSlice.ts b/frontend/src/stores/chatroomparticipants/chatroomparticipantsSlice.ts new file mode 100644 index 0000000..c7cfdb9 --- /dev/null +++ b/frontend/src/stores/chatroomparticipants/chatroomparticipantsSlice.ts @@ -0,0 +1,254 @@ +import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import axios from 'axios'; +import { + fulfilledNotify, + rejectNotify, + resetNotify, +} from '../../helpers/notifyStateHandler'; + +interface MainState { + chatroomparticipants: any; + loading: boolean; + count: number; + refetch: boolean; + rolesWidgets: any[]; + notify: { + showNotification: boolean; + textNotification: string; + typeNotification: string; + }; +} + +const initialState: MainState = { + chatroomparticipants: [], + loading: false, + count: 0, + refetch: false, + rolesWidgets: [], + notify: { + showNotification: false, + textNotification: '', + typeNotification: 'warn', + }, +}; + +export const fetch = createAsyncThunk( + 'chatroomparticipants/fetch', + async (data: any) => { + const { id, query } = data; + const result = await axios.get( + `chatroomparticipants${query || (id ? `/${id}` : '')}`, + ); + return id + ? result.data + : { rows: result.data.rows, count: result.data.count }; + }, +); + +export const deleteItemsByIds = createAsyncThunk( + 'chatroomparticipants/deleteByIds', + async (data: any, { rejectWithValue }) => { + try { + await axios.post('chatroomparticipants/deleteByIds', { data }); + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const deleteItem = createAsyncThunk( + 'chatroomparticipants/deleteChatroomparticipants', + async (id: string, { rejectWithValue }) => { + try { + await axios.delete(`chatroomparticipants/${id}`); + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const create = createAsyncThunk( + 'chatroomparticipants/createChatroomparticipants', + async (data: any, { rejectWithValue }) => { + try { + const result = await axios.post('chatroomparticipants', { data }); + return result.data; + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const uploadCsv = createAsyncThunk( + 'chatroomparticipants/uploadCsv', + async (file: File, { rejectWithValue }) => { + try { + const data = new FormData(); + data.append('file', file); + data.append('filename', file.name); + + const result = await axios.post( + 'chatroomparticipants/bulk-import', + data, + { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }, + ); + + return result.data; + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const update = createAsyncThunk( + 'chatroomparticipants/updateChatroomparticipants', + async (payload: any, { rejectWithValue }) => { + try { + const result = await axios.put(`chatroomparticipants/${payload.id}`, { + id: payload.id, + data: payload.data, + }); + return result.data; + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const chatroomparticipantsSlice = createSlice({ + name: 'chatroomparticipants', + initialState, + reducers: { + setRefetch: (state, action: PayloadAction) => { + state.refetch = action.payload; + }, + }, + extraReducers: (builder) => { + builder.addCase(fetch.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(fetch.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(fetch.fulfilled, (state, action) => { + if (action.payload.rows && action.payload.count >= 0) { + state.chatroomparticipants = action.payload.rows; + state.count = action.payload.count; + } else { + state.chatroomparticipants = action.payload; + } + state.loading = false; + }); + + builder.addCase(deleteItemsByIds.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + + builder.addCase(deleteItemsByIds.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, 'Chatroomparticipants has been deleted'); + }); + + builder.addCase(deleteItemsByIds.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(deleteItem.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + + builder.addCase(deleteItem.fulfilled, (state) => { + state.loading = false; + fulfilledNotify( + state, + `${'Chatroomparticipants'.slice(0, -1)} has been deleted`, + ); + }); + + builder.addCase(deleteItem.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(create.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(create.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(create.fulfilled, (state) => { + state.loading = false; + fulfilledNotify( + state, + `${'Chatroomparticipants'.slice(0, -1)} has been created`, + ); + }); + + builder.addCase(update.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(update.fulfilled, (state) => { + state.loading = false; + fulfilledNotify( + state, + `${'Chatroomparticipants'.slice(0, -1)} has been updated`, + ); + }); + builder.addCase(update.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(uploadCsv.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(uploadCsv.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, 'Chatroomparticipants has been uploaded'); + }); + builder.addCase(uploadCsv.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + }, +}); + +// Action creators are generated for each case reducer function +export const { setRefetch } = chatroomparticipantsSlice.actions; + +export default chatroomparticipantsSlice.reducer; diff --git a/frontend/src/stores/chatrooms/chatroomsSlice.ts b/frontend/src/stores/chatrooms/chatroomsSlice.ts new file mode 100644 index 0000000..655325b --- /dev/null +++ b/frontend/src/stores/chatrooms/chatroomsSlice.ts @@ -0,0 +1,236 @@ +import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import axios from 'axios'; +import { + fulfilledNotify, + rejectNotify, + resetNotify, +} from '../../helpers/notifyStateHandler'; + +interface MainState { + chatrooms: any; + loading: boolean; + count: number; + refetch: boolean; + rolesWidgets: any[]; + notify: { + showNotification: boolean; + textNotification: string; + typeNotification: string; + }; +} + +const initialState: MainState = { + chatrooms: [], + loading: false, + count: 0, + refetch: false, + rolesWidgets: [], + notify: { + showNotification: false, + textNotification: '', + typeNotification: 'warn', + }, +}; + +export const fetch = createAsyncThunk('chatrooms/fetch', async (data: any) => { + const { id, query } = data; + const result = await axios.get(`chatrooms${query || (id ? `/${id}` : '')}`); + return id + ? result.data + : { rows: result.data.rows, count: result.data.count }; +}); + +export const deleteItemsByIds = createAsyncThunk( + 'chatrooms/deleteByIds', + async (data: any, { rejectWithValue }) => { + try { + await axios.post('chatrooms/deleteByIds', { data }); + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const deleteItem = createAsyncThunk( + 'chatrooms/deleteChatrooms', + async (id: string, { rejectWithValue }) => { + try { + await axios.delete(`chatrooms/${id}`); + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const create = createAsyncThunk( + 'chatrooms/createChatrooms', + async (data: any, { rejectWithValue }) => { + try { + const result = await axios.post('chatrooms', { data }); + return result.data; + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const uploadCsv = createAsyncThunk( + 'chatrooms/uploadCsv', + async (file: File, { rejectWithValue }) => { + try { + const data = new FormData(); + data.append('file', file); + data.append('filename', file.name); + + const result = await axios.post('chatrooms/bulk-import', data, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + return result.data; + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const update = createAsyncThunk( + 'chatrooms/updateChatrooms', + async (payload: any, { rejectWithValue }) => { + try { + const result = await axios.put(`chatrooms/${payload.id}`, { + id: payload.id, + data: payload.data, + }); + return result.data; + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const chatroomsSlice = createSlice({ + name: 'chatrooms', + initialState, + reducers: { + setRefetch: (state, action: PayloadAction) => { + state.refetch = action.payload; + }, + }, + extraReducers: (builder) => { + builder.addCase(fetch.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(fetch.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(fetch.fulfilled, (state, action) => { + if (action.payload.rows && action.payload.count >= 0) { + state.chatrooms = action.payload.rows; + state.count = action.payload.count; + } else { + state.chatrooms = action.payload; + } + state.loading = false; + }); + + builder.addCase(deleteItemsByIds.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + + builder.addCase(deleteItemsByIds.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, 'Chatrooms has been deleted'); + }); + + builder.addCase(deleteItemsByIds.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(deleteItem.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + + builder.addCase(deleteItem.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, `${'Chatrooms'.slice(0, -1)} has been deleted`); + }); + + builder.addCase(deleteItem.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(create.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(create.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(create.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, `${'Chatrooms'.slice(0, -1)} has been created`); + }); + + builder.addCase(update.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(update.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, `${'Chatrooms'.slice(0, -1)} has been updated`); + }); + builder.addCase(update.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + + builder.addCase(uploadCsv.pending, (state) => { + state.loading = true; + resetNotify(state); + }); + builder.addCase(uploadCsv.fulfilled, (state) => { + state.loading = false; + fulfilledNotify(state, 'Chatrooms has been uploaded'); + }); + builder.addCase(uploadCsv.rejected, (state, action) => { + state.loading = false; + rejectNotify(state, action); + }); + }, +}); + +// Action creators are generated for each case reducer function +export const { setRefetch } = chatroomsSlice.actions; + +export default chatroomsSlice.reducer; diff --git a/frontend/src/stores/store.ts b/frontend/src/stores/store.ts index a20788f..d84befd 100644 --- a/frontend/src/stores/store.ts +++ b/frontend/src/stores/store.ts @@ -9,6 +9,9 @@ import departmentsSlice from './departments/departmentsSlice'; import reportsSlice from './reports/reportsSlice'; import rolesSlice from './roles/rolesSlice'; import permissionsSlice from './permissions/permissionsSlice'; +import chatroomsSlice from './chatrooms/chatroomsSlice'; +import chatmessagesSlice from './chatmessages/chatmessagesSlice'; +import chatroomparticipantsSlice from './chatroomparticipants/chatroomparticipantsSlice'; export const store = configureStore({ reducer: { @@ -22,6 +25,9 @@ export const store = configureStore({ reports: reportsSlice, roles: rolesSlice, permissions: permissionsSlice, + chatrooms: chatroomsSlice, + chatmessages: chatmessagesSlice, + chatroomparticipants: chatroomparticipantsSlice, }, });