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.
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..8117a63 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\":\"Xsbn6jNbREZRRL91\",\"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') && (
+