diff --git a/api-sample/database/database-schema.sql b/api-sample/database/database-schema.sql
index 0e1766bc..6549df5d 100644
--- a/api-sample/database/database-schema.sql
+++ b/api-sample/database/database-schema.sql
@@ -3,48 +3,121 @@ USE `{{PROJECT_NAME_SNAKECASE}}_db`;
-- DONT MODIFY THIS MIGRATION
-- it is used by Xest local development pipeline
DROP TABLE IF EXISTS `migrations`;
-CREATE TABLE `migrations` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `name` varchar(255) NOT NULL,
- `run_on` datetime NOT NULL,
- PRIMARY KEY (`id`)
-) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET = utf8mb4 COLLATE utf8mb4_unicode_ci;
-
-INSERT INTO `migrations` (
- name,
- run_on
-) VALUES (
- "/20211107064324-database-schema",
- "20211107064324"
-);
+
+CREATE TABLE
+ `migrations` (
+ `id` int (11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(255) NOT NULL,
+ `run_on` datetime NOT NULL,
+ PRIMARY KEY (`id`)
+ ) ENGINE = InnoDB AUTO_INCREMENT = 0 DEFAULT CHARSET = utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+INSERT INTO
+ `migrations` (name, run_on)
+VALUES
+ (
+ "/20211107064324-database-schema",
+ "20211107064324"
+ );
-- YOU CAN MODIFY BELOW THIS LINE
-DROP TABLE IF EXISTS user_types;
-CREATE TABLE user_types(
- user_type_id int AUTO_INCREMENT PRIMARY KEY,
- user_type VARCHAR(50) NOT NULL
+
+-- USER_ORGANIZATION_ROLES TABLE START
+DROP TABLE IF EXISTS user_organization_roles;
+CREATE TABLE user_organization_roles(
+ user_organization_role_id INT AUTO_INCREMENT PRIMARY KEY NOT NULL,
+ user_organization_role VARCHAR(100) NOT NULL
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE utf8mb4_unicode_ci;
+-- USER_ROLES TABLE END
+-- USERS TABLE START
DROP TABLE IF EXISTS users;
-CREATE TABLE users(
- user_id int AUTO_INCREMENT PRIMARY KEY,
- first_name VARCHAR(50) NOT NULL,
- last_name VARCHAR(50) NOT NULL,
- email VARCHAR(50) NOT NULL UNIQUE,
- password VARCHAR(500) NOT NULL,
- user_type_id int NOT NULL,
+CREATE TABLE
+ users (
+ user_id int AUTO_INCREMENT PRIMARY KEY,
+ first_name VARCHAR(50) NOT NULL,
+ last_name VARCHAR(50) NOT NULL,
+ email VARCHAR(50) NOT NULL UNIQUE,
+ password VARCHAR(500) NOT NULL,
+ is_super_admin tinyint NOT NULL,
+ updated_at DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE utf8mb4_unicode_ci;
+-- USERS TABLE END
+
+-- ORGANIZATIONS TABLE START
+DROP TABLE IF EXISTS organizations;
+CREATE TABLE
+ organizations (
+ organization_id INT AUTO_INCREMENT PRIMARY KEY NOT NULL,
+ organization_name VARCHAR(100) NOT NULL,
+ updated_at DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+ ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE utf8mb4_unicode_ci;
+-- ORGANIZATIONS TABLE END
+
+-- USER_ORGANIZATIONS TABLE START
+DROP TABLE IF EXISTS user_organizations;
+CREATE TABLE user_organizations(
+ user_organization_id INT AUTO_INCREMENT PRIMARY KEY NOT NULL,
+ user_id INT NOT NULL,
+ user_organization_role_id INT NOT NULL,
+ organization_id INT NOT NULL,
+ added_by INT NOT NULL,
+ updated_at DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_type_id) REFERENCES user_types(user_type_id)
+ FOREIGN KEY (user_id) REFERENCES users(user_id),
+ FOREIGN KEY (user_organization_role_id) REFERENCES user_organization_roles(user_organization_role_id),
+ FOREIGN KEY (organization_id) REFERENCES organizations(organization_id)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE utf8mb4_unicode_ci;
+-- USER_ORGANIZATIONS TABLE END
+-- USER_ORGANIZATION_INVITATIONS TABLE START
+DROP TABLE IF EXISTS user_organization_invitations;
+CREATE TABLE user_organization_invitations(
+ user_organization_invitation_id INT AUTO_INCREMENT PRIMARY KEY NOT NULL,
+ invitation_shortcode VARCHAR(36) NOT NULL,
+ organization_id INT NOT NULL,
+ email VARCHAR (200) NOT NULL,
+ invited_by INT NOT NULL,
+ user_organization_role_id INT NOT NULL,
+ comment VARCHAR(500),
+ sent_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ accepted_at DATETIME,
+ declined_at DATETIME,
+ updated_at DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_organization_role_id) REFERENCES user_organization_roles(user_organization_role_id),
+ FOREIGN KEY (organization_id) REFERENCES organizations(organization_id),
+ FOREIGN KEY (invited_by) REFERENCES user_organizations(user_id)
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE utf8mb4_unicode_ci;
+-- USER_ORGANIZATION_INVITATIONS TABLE END
+-- USER_ORGANIZATION_REQUESTS TABLE START
DROP TABLE IF EXISTS password_recovery_requests;
CREATE TABLE password_recovery_requests(
password_recovery_request_id int AUTO_INCREMENT PRIMARY KEY,
requested_email VARCHAR(150) NOT NULL,
- shortcode VARCHAR(40) NOT NULL UNIQUE,
+ shortcode VARCHAR(36) NOT NULL UNIQUE,
recovered_at DATETIME,
expiry_date DATETIME NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (requested_email) REFERENCES users(email)
-)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
\ No newline at end of file
+)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci;
+-- USER_ORGANIZATION_REQUESTS TABLE END
+
+-- USER_ORGANIZATION_REQUESTS TABLE START
+DROP TABLE IF EXISTS registration_requests;
+CREATE TABLE registration_requests(
+ registration_request_id int AUTO_INCREMENT PRIMARY KEY,
+ first_name VARCHAR(50) NOT NULL,
+ last_name VARCHAR(50) NOT NULL,
+ email VARCHAR(50) NOT NULL UNIQUE,
+ password VARCHAR(500) NOT NULL,
+ is_super_admin int NOT NULL,
+ organization_name VARCHAR(50) NOT NULL,
+ registration_shortcode VARCHAR(36) NOT NULL UNIQUE,
+ status VARCHAR(20) DEFAULT 'pending',
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+ updated_at DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE utf8mb4_unicode_ci;
+-- USER_ORGANIZATION_REQUESTS TABLE END
\ No newline at end of file
diff --git a/api-sample/database/seed-data.sql b/api-sample/database/seed-data.sql
index 0e5ae202..613bc0c2 100644
--- a/api-sample/database/seed-data.sql
+++ b/api-sample/database/seed-data.sql
@@ -3,26 +3,180 @@ USE `{{PROJECT_NAME_SNAKECASE}}_db`;
-- DONT MODIFY THIS MIGRATION
-- it is used by Xest local development pipeline
-INSERT INTO `migrations` (
- name,
- run_on
+INSERT INTO
+ `migrations` (name, run_on)
+VALUES
+ ("/20211107064324-seed-data", "20211107064324");
+
+-- YOU CAN MODIFY BELOW THIS LINE
+INSERT INTO
+ user_organization_roles (user_organization_role_id, user_organization_role)
+VALUES
+ (1, "OrganizationAdmin"),
+ (2, "Member");
+
+INSERT INTO
+ users (
+ user_id,
+ first_name,
+ last_name,
+ email,
+ password,
+ is_super_admin,
+ created_at
+ )
+VALUES
+ (
+ 1,
+ "Ahmet",
+ "Akinsql",
+ "ahmet@akinsql.com",
+ SHA2 (CONCAT ("12345678", "SECRET_SALT"), 224),
+ 1,
+ "2020-11-20 12:00:00"
+ ),
+ (
+ 2,
+ "Joe",
+ "Bloggs",
+ "joebloggs@gmail.com",
+ SHA2 (CONCAT ("12345678", "SECRET_SALT"), 224),
+ 0,
+ "2020-11-20 12:00:00"
+ ),
+ (
+ 3,
+ "Jim",
+ "Bloggs",
+ "jimbloggs@yahoo.com",
+ SHA2 (CONCAT ("12345678", "SECRET_SALT"), 224),
+ 1,
+ "2020-11-20 12:00:00"
+ ),
+ (
+ 4,
+ "Jane",
+ "Doe",
+ "janedoe@example.com",
+ SHA2 (CONCAT ("12345678", "SECRET_SALT"), 224),
+ 0,
+ "2020-11-20 12:00:00"
+ );
+
+INSERT INTO
+ organizations (organization_name)
+VALUES
+ ('Organization 1'),
+ ('Organization 2'),
+ ('Organization 3'),
+ ('Organization 4'),
+ ('Organization 5');
+
+INSERT INTO
+ user_organizations (
+ user_id,
+ user_organization_role_id,
+ organization_id,
+ added_by
+ )
+VALUES
+ (1, 1, 1, 1),
+ (2, 2, 2, 2),
+ (3, 1, 1, 3),
+ (4, 2, 2, 4);
+
+INSERT INTO
+ user_organization_invitations (
+ invitation_shortcode,
+ organization_id,
+ email,
+ invited_by,
+ user_organization_role_id,
+ comment,
+ sent_at,
+ accepted_at
+ )
+VALUES
+ (
+ 'shortcode1',
+ 1,
+ 'email1@example.com',
+ 1,
+ 1,
+ 'Comment 1',
+ '2022-01-01 00:00:00',
+ '2022-01-02 00:00:00'
+ ),
+ (
+ 'shortcode2',
+ 1,
+ 'email2@example.com',
+ 1,
+ 2,
+ 'Comment 2',
+ '2022-01-02 00:00:00',
+ '2022-01-03 00:00:00'
+ ),
+ (
+ 'shortcode3',
+ 2,
+ 'email3@example.com',
+ 2,
+ 1,
+ 'Comment 3',
+ '2022-01-03 00:00:00',
+ '2022-01-04 00:00:00'
+ ),
+ (
+ 'shortcode4',
+ 2,
+ 'email4@example.com',
+ 2,
+ 2,
+ 'Comment 4',
+ '2022-01-04 00:00:00',
+ '2022-01-05 00:00:00'
+ );
+
+-- INSERT INTO password_recovery_requests(shortcode,requested_email,expiry_date,created_at)
+-- VALUES ("321","ahmet@akinsql.com","2020-09-20 12:30:00","2022-01-03 12:30:00");
+
+INSERT INTO password_recovery_requests(
+ requested_email,
+ shortcode,
+ expiry_date
) VALUES (
- "/20211107064324-seed-data",
- "20211107064324"
+ 'ahmet@akinsql.com',
+ 'shortcode1',
+ DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 1 DAY)
+), (
+ 'joebloggs@gmail.com',
+ 'shortcode2',
+ DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 1 DAY)
);
--- YOU CAN MODIFY BELOW THIS LINE
-INSERT INTO user_types (user_type_id, user_type)
-VALUES (1, "admin");
-INSERT INTO user_types (user_type_id, user_type)
-VALUES (2, "user");
-
-INSERT INTO users (user_id, first_name, last_name, email, password, user_type_id, created_at)
-VALUES (1, "Ahmet", "Akinsql", "ahmet@akinsql.com", SHA2(CONCAT("password","SECRET_SALT"), 224), 1, "2020-11-20 12:00:00");
-INSERT INTO users (user_id, first_name, last_name, email, password, user_type_id, created_at)
-VALUES (2, "Joe", "Bloggs","joebloggs@gmail.com", SHA2(CONCAT("password","SECRET_SALT"), 224), 2, "2020-11-20 12:00:00");
-INSERT INTO users (user_id, first_name, last_name, email, password, user_type_id, created_at)
-VALUES (3, "Jim", "Bloggs" , "jimbloggs@yahoo.com", SHA2(CONCAT("password","SECRET_SALT"), 224), 2, "2020-11-20 12:00:00");
-
-INSERT INTO password_recovery_requests(shortcode,requested_email,expiry_date,created_at)
-VALUES ("321","ahmet@akinsql.com","2020-09-20 12:30:00","2022-01-03 12:30:00");
\ No newline at end of file
+INSERT INTO registration_requests(
+ first_name,
+ last_name,
+ email,
+ password,
+ is_super_admin,
+ organization_name,
+ registration_shortcode
+) VALUES (
+ 'John',
+ 'Doe',
+ 'john.doe@example.com',
+ SHA2 (CONCAT ("12345678", "SECRET_SALT"), 224),
+ 1,
+ 'Example Organization',
+ 'shortcode1'
+), (
+ 'Jane',
+ 'Doe',
+ 'joebloggs@gmail.com',
+ SHA2 (CONCAT ("12345678", "SECRET_SALT"), 224),
+ 0,
+ 'Example Organization 2',
+ 'shortcode2'
+);
\ No newline at end of file
diff --git a/api-sample/package.json b/api-sample/package.json
index 9e6184d6..391cb4d7 100644
--- a/api-sample/package.json
+++ b/api-sample/package.json
@@ -126,6 +126,7 @@
"jsonwebtoken": "^8.5.1",
"jwks-rsa": "^1.5.0",
"lodash": "^4.17.19",
+ "mailgun.js": "^10.1.0",
"minio": "^7.0.19",
"mjml": "^4.5.1",
"module-alias": "^2.2.0",
@@ -147,6 +148,6 @@
"winston-transport": "^4.3.0",
"yamlify-object": "^0.5.1",
"yamlify-object-colors": "^1.0.3",
- "yup": "^0.27.0"
+ "yup": "^1.3.2"
}
}
diff --git a/api-sample/src/actions/invitations/createOrganizationInvitation/index.js b/api-sample/src/actions/invitations/createOrganizationInvitation/index.js
new file mode 100644
index 00000000..cd67e20d
--- /dev/null
+++ b/api-sample/src/actions/invitations/createOrganizationInvitation/index.js
@@ -0,0 +1,23 @@
+const insertOrganizationInvitation = require("./queries/insertOrganizationInvitation");
+
+const createOrganizationInvitation = async ({
+ userId,
+ orgId,
+ email,
+ userOrganizationRoleId,
+ comment,
+ invitationShortcode
+}) => {
+ const newInvitationId = await insertOrganizationInvitation({
+ userId,
+ orgId,
+ email,
+ userOrganizationRoleId,
+ comment,
+ invitationShortcode
+ });
+
+ return { newInvitationId };
+};
+
+module.exports = createOrganizationInvitation;
diff --git a/api-sample/src/actions/invitations/createOrganizationInvitation/queries/insertOrganizationInvitation.js b/api-sample/src/actions/invitations/createOrganizationInvitation/queries/insertOrganizationInvitation.js
new file mode 100644
index 00000000..6f4a2947
--- /dev/null
+++ b/api-sample/src/actions/invitations/createOrganizationInvitation/queries/insertOrganizationInvitation.js
@@ -0,0 +1,35 @@
+const {
+ submitQuery,
+ getInsertId,
+ sqlValueOrNull
+} = require("~root/lib/database");
+
+const insertOrganizationInvitation = ({
+ userId,
+ orgId,
+ email,
+ userOrganizationRoleId,
+ comment,
+ invitationShortcode
+}) => submitQuery`
+ INSERT INTO
+ user_organization_invitations(
+ email,
+ comment,
+ invitation_shortcode,
+ invited_by,
+ organization_id,
+ user_organization_role_id
+ )
+VALUES
+ (
+ ${email},
+ ${sqlValueOrNull(comment)},
+ ${invitationShortcode},
+ ${userId},
+ ${orgId},
+ ${userOrganizationRoleId}
+ );
+`;
+
+module.exports = getInsertId(insertOrganizationInvitation);
diff --git a/api-sample/src/actions/invitations/fetchInvitationById/index.js b/api-sample/src/actions/invitations/fetchInvitationById/index.js
new file mode 100644
index 00000000..473da273
--- /dev/null
+++ b/api-sample/src/actions/invitations/fetchInvitationById/index.js
@@ -0,0 +1,9 @@
+const selectInvitationById = require("./queries/selectInvitationById");
+
+const fetchInvitationById = async ({ invitationId, shortCode }) => {
+ const invitation = await selectInvitationById({ invitationId, shortCode });
+
+ return { invitation };
+};
+
+module.exports = fetchInvitationById;
diff --git a/api-sample/src/actions/invitations/fetchInvitationById/queries/selectInvitationById.js b/api-sample/src/actions/invitations/fetchInvitationById/queries/selectInvitationById.js
new file mode 100644
index 00000000..d04382f5
--- /dev/null
+++ b/api-sample/src/actions/invitations/fetchInvitationById/queries/selectInvitationById.js
@@ -0,0 +1,15 @@
+const { submitQuery, camelKeys, getFirst } = require("~root/lib/database");
+
+const selectInvitationById = ({ invitationId, shortCode }) => submitQuery`
+ SELECT
+ user_organization_invitation_id,
+ organization_id,
+ invited_by,
+ user_role_id
+ FROM user_organization_invitations
+ WHERE user_organization_invitation_id = ${invitationId}
+ AND invitation_shortcode = ${shortCode}
+ AND accepted_at IS NULL
+`;
+
+module.exports = getFirst(camelKeys(selectInvitationById));
diff --git a/api-sample/src/actions/invitations/fetchInvitationByShortCode/index.js b/api-sample/src/actions/invitations/fetchInvitationByShortCode/index.js
new file mode 100644
index 00000000..db574c83
--- /dev/null
+++ b/api-sample/src/actions/invitations/fetchInvitationByShortCode/index.js
@@ -0,0 +1,9 @@
+const selectInvitationById = require("./queries/selectInvitationByShortCode");
+
+const fetchInvitationByShortCode = async ({ shortCode }) => {
+ const invitation = await selectInvitationById({ shortCode });
+
+ return { invitation };
+};
+
+module.exports = fetchInvitationByShortCode;
diff --git a/api-sample/src/actions/invitations/fetchInvitationByShortCode/queries/selectInvitationByShortCode.js b/api-sample/src/actions/invitations/fetchInvitationByShortCode/queries/selectInvitationByShortCode.js
new file mode 100644
index 00000000..d80ede34
--- /dev/null
+++ b/api-sample/src/actions/invitations/fetchInvitationByShortCode/queries/selectInvitationByShortCode.js
@@ -0,0 +1,15 @@
+const { submitQuery, camelKeys, getFirst } = require("~root/lib/database");
+
+const selectInvitationByShortCode = ({ shortCode }) => submitQuery`
+ SELECT
+ user_organization_invitation_id,
+ organization_id,
+ invited_by,
+ user_organization_role_id,
+ email
+ FROM user_organization_invitations
+ WHERE invitation_shortcode = ${shortCode}
+ AND accepted_at IS NULL
+`;
+
+module.exports = getFirst(camelKeys(selectInvitationByShortCode));
diff --git a/api-sample/src/actions/invitations/fetchUserInvitations/index.js b/api-sample/src/actions/invitations/fetchUserInvitations/index.js
new file mode 100644
index 00000000..c846ba81
--- /dev/null
+++ b/api-sample/src/actions/invitations/fetchUserInvitations/index.js
@@ -0,0 +1,9 @@
+const selectUserInvitations = require("./queries/selectUserInvitations");
+
+const fetchUserInvitations = async ({ email }) => {
+ const userInvitations = await selectUserInvitations({ email });
+
+ return { userInvitations };
+};
+
+module.exports = fetchUserInvitations;
diff --git a/api-sample/src/actions/invitations/fetchUserInvitations/queries/selectUserInvitations.js b/api-sample/src/actions/invitations/fetchUserInvitations/queries/selectUserInvitations.js
new file mode 100644
index 00000000..8abb41d4
--- /dev/null
+++ b/api-sample/src/actions/invitations/fetchUserInvitations/queries/selectUserInvitations.js
@@ -0,0 +1,28 @@
+const { submitQuery, camelKeys } = require("~root/lib/database");
+
+const selectUserInvitations = ({ email }) => submitQuery`
+ SELECT
+ user_organization_invitations.user_organization_invitation_id,
+ user_organization_invitations.organization_id,
+ user_organization_invitations.email,
+ user_organization_invitations.invited_by,
+ user_organization_invitations.accepted_at,
+ user_organization_invitations.user_type_id,
+ user_organization_invitations.sent_at,
+ user_organization_invitations.invitation_shortcode,
+ users.first_name AS invited_by_first_name,
+ users.last_name AS invited_by_last_name,
+ u2.first_name AS user_first_name,
+ u2.last_name AS user_last_name,
+ user_types.user_type,
+ organizations.organization_name
+ FROM user_organization_invitations
+ LEFT JOIN users ON users.user_id = user_organization_invitations.invited_by
+ LEFT JOIN users u2 ON u2.email = user_organization_invitations.email
+ LEFT JOIN user_types ON user_types.user_type_id = user_organization_invitations.user_type_id
+ LEFT JOIN organizations ON organizations.organization_id = user_organization_invitations.organization_id
+ WHERE user_organization_invitations.email = ${email}
+ ORDER BY user_organization_invitations.user_organization_invitation_id DESC
+`;
+
+module.exports = camelKeys(selectUserInvitations);
\ No newline at end of file
diff --git a/api-sample/src/actions/invitations/modifyOrganizationInvitation/index.js b/api-sample/src/actions/invitations/modifyOrganizationInvitation/index.js
new file mode 100644
index 00000000..5eb5c3a8
--- /dev/null
+++ b/api-sample/src/actions/invitations/modifyOrganizationInvitation/index.js
@@ -0,0 +1,17 @@
+const patchOrganizationInvitation = require("./queries/updateOrganizationInvitation");
+
+const modifyOrganizationInvitation = async ({
+ email,
+ invitationShortcode,
+ orgId
+}) => {
+ const orgInvitation = await patchOrganizationInvitation({
+ email,
+ invitationShortcode,
+ orgId
+ });
+
+ return { orgInvitation };
+};
+
+module.exports = modifyOrganizationInvitation;
diff --git a/api-sample/src/actions/invitations/modifyOrganizationInvitation/queries/updateOrganizationInvitation.js b/api-sample/src/actions/invitations/modifyOrganizationInvitation/queries/updateOrganizationInvitation.js
new file mode 100644
index 00000000..edee038c
--- /dev/null
+++ b/api-sample/src/actions/invitations/modifyOrganizationInvitation/queries/updateOrganizationInvitation.js
@@ -0,0 +1,26 @@
+const {
+ submitQuery,
+ sql,
+ sqlReduce,
+ getInsertId
+} = require("~root/lib/database");
+
+const updateOrganizationInvitation = ({
+ email = null,
+ invitationShortcode,
+ orgId
+}) => {
+ const updates = [];
+ if (email !== null) {
+ updates.push(sql`email = ${email}`);
+ }
+
+ if (updates.length !== 0) {
+ return submitQuery`UPDATE user_organization_invitations SET ${updates.reduce(
+ sqlReduce
+ )} WHERE invitation_shortcode = ${invitationShortcode} && organization_id = ${orgId};`;
+ }
+ return Promise.resolve();
+};
+
+module.exports = getInsertId(updateOrganizationInvitation);
diff --git a/api-sample/src/actions/invitations/patchInvitationAsAccepted/index.js b/api-sample/src/actions/invitations/patchInvitationAsAccepted/index.js
new file mode 100644
index 00000000..897ee0c5
--- /dev/null
+++ b/api-sample/src/actions/invitations/patchInvitationAsAccepted/index.js
@@ -0,0 +1,9 @@
+const acceptInvitationQuery = require("./queries/acceptInvitationQuery");
+
+const patchInvitationAsAccepted = async ({ invitationId, shortCode }) => {
+ const invitation = await acceptInvitationQuery({ invitationId, shortCode });
+
+ return { invitation };
+};
+
+module.exports = patchInvitationAsAccepted;
diff --git a/api-sample/src/actions/invitations/patchInvitationAsAccepted/queries/acceptInvitationQuery.js b/api-sample/src/actions/invitations/patchInvitationAsAccepted/queries/acceptInvitationQuery.js
new file mode 100644
index 00000000..0ee2f66d
--- /dev/null
+++ b/api-sample/src/actions/invitations/patchInvitationAsAccepted/queries/acceptInvitationQuery.js
@@ -0,0 +1,19 @@
+const { submitQuery, sql, sqlReduce } = require("~root/lib/database");
+
+const acceptInvitationQuery = ({ invitationId, shortCode }) => {
+ const updates = [];
+
+ if (invitationId) {
+ updates.push(sql`accepted_at = CURRENT_TIMESTAMP`);
+ }
+
+ if (updates.length !== 0) {
+ return submitQuery`
+ UPDATE
+ user_organization_invitations SET ${updates.reduce(
+ sqlReduce
+ )} WHERE invitation_shortcode = ${shortCode} && user_organization_invitation_id = ${invitationId}`;
+ }
+ return Promise.resolve();
+};
+module.exports = acceptInvitationQuery;
diff --git a/api-sample/src/actions/invitations/removeInvitation/index.js b/api-sample/src/actions/invitations/removeInvitation/index.js
new file mode 100644
index 00000000..62d89272
--- /dev/null
+++ b/api-sample/src/actions/invitations/removeInvitation/index.js
@@ -0,0 +1,10 @@
+const deleteInvitation = require("./queries/deleteInvitation");
+
+const removeInvitation = async ({ invitationId }) => {
+ const invitation = await deleteInvitation({
+ invitationId
+ });
+ return { invitation };
+};
+
+module.exports = removeInvitation;
diff --git a/api-sample/src/actions/invitations/removeInvitation/queries/deleteInvitation.js b/api-sample/src/actions/invitations/removeInvitation/queries/deleteInvitation.js
new file mode 100644
index 00000000..86d7d5cf
--- /dev/null
+++ b/api-sample/src/actions/invitations/removeInvitation/queries/deleteInvitation.js
@@ -0,0 +1,8 @@
+const { submitQuery, getInsertId } = require("~root/lib/database");
+
+const deleteInvitation = ({ invitationId }) => submitQuery`
+ DELETE
+ FROM user_organization_invitations
+ WHERE user_organization_invitation_id = ${invitationId}
+`;
+module.exports = getInsertId(deleteInvitation);
diff --git a/api-sample/src/actions/organizations/createNewOrganization/index.js b/api-sample/src/actions/organizations/createNewOrganization/index.js
new file mode 100644
index 00000000..54bd396e
--- /dev/null
+++ b/api-sample/src/actions/organizations/createNewOrganization/index.js
@@ -0,0 +1,8 @@
+const insertOrganization = require("./queries/insertOrganization");
+
+const createOrganization = async ({ organizationName }) => {
+ const organizationId = await insertOrganization({ organizationName });
+ return { organizationId };
+};
+
+module.exports = createOrganization;
diff --git a/api-sample/src/actions/organizations/createNewOrganization/queries/insertOrganization.js b/api-sample/src/actions/organizations/createNewOrganization/queries/insertOrganization.js
new file mode 100644
index 00000000..6ece38ce
--- /dev/null
+++ b/api-sample/src/actions/organizations/createNewOrganization/queries/insertOrganization.js
@@ -0,0 +1,8 @@
+const { submitQuery, getInsertId } = require("~root/lib/database");
+
+const insertOrganization = ({ organizationName }) => submitQuery`
+ INSERT INTO organizations(organization_name)
+ VALUES(${organizationName});
+`;
+
+module.exports = getInsertId(insertOrganization);
diff --git a/api-sample/src/actions/organizations/createOrganization/index.js b/api-sample/src/actions/organizations/createOrganization/index.js
new file mode 100644
index 00000000..cd4cafa3
--- /dev/null
+++ b/api-sample/src/actions/organizations/createOrganization/index.js
@@ -0,0 +1,9 @@
+const insertOrganization = require("./queries/insertOrganization");
+
+const createOrganization = async ({ organizationName }) => {
+ const organizationId = await insertOrganization({ organizationName });
+
+ return { organizationId };
+};
+
+module.exports = createOrganization;
diff --git a/api-sample/src/actions/organizations/createOrganization/queries/insertOrganization.js b/api-sample/src/actions/organizations/createOrganization/queries/insertOrganization.js
new file mode 100644
index 00000000..dce4a1be
--- /dev/null
+++ b/api-sample/src/actions/organizations/createOrganization/queries/insertOrganization.js
@@ -0,0 +1,15 @@
+const { submitQuery, getInsertId } = require("~root/lib/database");
+
+const insertOrganization = ({ organizationName }) => submitQuery`
+ INSERT INTO
+ organizations
+ (
+ organization_name
+ )
+ VALUES
+ (
+ ${organizationName}
+ )
+`;
+
+module.exports = getInsertId(insertOrganization);
diff --git a/api-sample/src/actions/organizations/createOrganizationUser/index.js b/api-sample/src/actions/organizations/createOrganizationUser/index.js
new file mode 100644
index 00000000..e63bbbd0
--- /dev/null
+++ b/api-sample/src/actions/organizations/createOrganizationUser/index.js
@@ -0,0 +1,19 @@
+const insertOrganizationUser = require("./queries/insertOrganizationUser");
+
+const createOrganizationUser = async ({
+ userId,
+ userOrganizationRoleId,
+ organizationId,
+ addedBy
+}) => {
+ const organizationUserId = await insertOrganizationUser({
+ userId,
+ userOrganizationRoleId,
+ organizationId,
+ addedBy
+ });
+
+ return { organizationUserId };
+};
+
+module.exports = createOrganizationUser;
diff --git a/api-sample/src/actions/organizations/createOrganizationUser/queries/insertOrganizationUser.js b/api-sample/src/actions/organizations/createOrganizationUser/queries/insertOrganizationUser.js
new file mode 100644
index 00000000..ce42b264
--- /dev/null
+++ b/api-sample/src/actions/organizations/createOrganizationUser/queries/insertOrganizationUser.js
@@ -0,0 +1,25 @@
+const { submitQuery, getInsertId } = require("~root/lib/database");
+
+const insertOrganizationUser = ({
+ userId,
+ userOrganizationRoleId,
+ organizationId,
+ addedBy
+}) => submitQuery`
+ INSERT INTO user_organizations
+ (
+ user_id,
+ user_organization_role_id,
+ organization_id,
+ added_by
+ )
+ VALUES
+ (
+ ${userId},
+ ${userOrganizationRoleId},
+ ${organizationId},
+ ${addedBy}
+ )
+`;
+
+module.exports = getInsertId(insertOrganizationUser);
diff --git a/api-sample/src/actions/organizations/fetchOrganizationInvitations/index.js b/api-sample/src/actions/organizations/fetchOrganizationInvitations/index.js
new file mode 100644
index 00000000..f99c611a
--- /dev/null
+++ b/api-sample/src/actions/organizations/fetchOrganizationInvitations/index.js
@@ -0,0 +1,9 @@
+const selectOrganizationInvitations = require("./queries/selectOrganizationInvitations");
+
+const fetchOrganizationInvitations = async ({ orgId }) => {
+ const invitations = await selectOrganizationInvitations({ orgId });
+
+ return { invitations };
+};
+
+module.exports = fetchOrganizationInvitations;
diff --git a/api-sample/src/actions/organizations/fetchOrganizationInvitations/queries/selectOrganizationInvitations.js b/api-sample/src/actions/organizations/fetchOrganizationInvitations/queries/selectOrganizationInvitations.js
new file mode 100644
index 00000000..74048255
--- /dev/null
+++ b/api-sample/src/actions/organizations/fetchOrganizationInvitations/queries/selectOrganizationInvitations.js
@@ -0,0 +1,24 @@
+const { submitQuery, camelKeys } = require("~root/lib/database");
+
+const selectOrganizationInvitations = ({ orgId }) => submitQuery`
+ SELECT
+ user_organization_invitations.user_organization_invitation_id,
+ user_organization_invitations.invitation_shortcode,
+ user_organization_invitations.organization_id,
+ user_organization_invitations.email,
+ user_organization_invitations.invited_by,
+ user_organization_invitations.user_organization_role_id,
+ user_organization_invitations.comment,
+ user_organization_invitations.sent_at,
+ user_organization_invitations.accepted_at,
+ user_organization_invitations.declined_at,
+ user_organization_invitations.updated_at,
+ users.first_name as invitedByFirstName,
+ users.last_name as invitedByLastName
+ FROM user_organization_invitations
+ LEFT JOIN users ON users.user_id = user_organization_invitations.invited_by
+ WHERE organization_id = ${orgId}
+ ORDER BY sent_at DESC
+`;
+
+module.exports = camelKeys(selectOrganizationInvitations);
diff --git a/api-sample/src/actions/organizations/fetchOrganizationNameById/index.js b/api-sample/src/actions/organizations/fetchOrganizationNameById/index.js
new file mode 100644
index 00000000..ff5a5073
--- /dev/null
+++ b/api-sample/src/actions/organizations/fetchOrganizationNameById/index.js
@@ -0,0 +1,9 @@
+const selectOrganizationNameById = require("./queries/selectOrganizationNameById");
+
+const fetchOrganizationNameById = async ({ organizationId }) => {
+ const organizationName = await selectOrganizationNameById({ organizationId });
+
+ return { organizationName };
+};
+
+module.exports = fetchOrganizationNameById;
diff --git a/api-sample/src/actions/organizations/fetchOrganizationNameById/queries/selectOrganizationNameById.js b/api-sample/src/actions/organizations/fetchOrganizationNameById/queries/selectOrganizationNameById.js
new file mode 100644
index 00000000..213519ed
--- /dev/null
+++ b/api-sample/src/actions/organizations/fetchOrganizationNameById/queries/selectOrganizationNameById.js
@@ -0,0 +1,10 @@
+const { submitQuery, camelKeys } = require("~root/lib/database");
+
+const selectOrganizationNameById = ({ organizationId }) => submitQuery`
+ SELECT
+ organization_name
+ FROM organizations
+ WHERE organization_id = ${organizationId}
+`;
+
+module.exports = camelKeys(selectOrganizationNameById);
diff --git a/api-sample/src/actions/organizations/fetchOrganizationUsers/index.js b/api-sample/src/actions/organizations/fetchOrganizationUsers/index.js
new file mode 100644
index 00000000..d36a4090
--- /dev/null
+++ b/api-sample/src/actions/organizations/fetchOrganizationUsers/index.js
@@ -0,0 +1,9 @@
+const selectOrganizationUsers = require("./queries/selectOrganizationUsers");
+
+const fetchOrganizationUsers = async ({ orgId }) => {
+ const organizationUsers = await selectOrganizationUsers({ orgId });
+
+ return { organizationUsers };
+};
+
+module.exports = fetchOrganizationUsers;
diff --git a/api-sample/src/actions/organizations/fetchOrganizationUsers/queries/selectOrganizationUsers.js b/api-sample/src/actions/organizations/fetchOrganizationUsers/queries/selectOrganizationUsers.js
new file mode 100644
index 00000000..a08de287
--- /dev/null
+++ b/api-sample/src/actions/organizations/fetchOrganizationUsers/queries/selectOrganizationUsers.js
@@ -0,0 +1,21 @@
+const { submitQuery, camelKeys } = require("~root/lib/database");
+
+const selectOrganizationUsers = ({ orgId }) => submitQuery`
+ SELECT
+ user_organizations.user_organization_id,
+ user_organizations.user_id,
+ user_organizations.user_organization_role_id,
+ user_organizations.organization_id,
+ users.first_name,
+ users.last_name,
+ users.email,
+ user_organization_roles.user_organization_role,
+ user_organizations.updated_at,
+ user_organizations.created_at
+ FROM user_organizations
+ LEFT JOIN users ON users.user_id = user_organizations.user_id
+ LEFT JOIN user_organization_roles ON user_organizations.user_organization_role_id = user_organizations.user_organization_role_id
+ WHERE organization_id = ${orgId}
+`;
+
+module.exports = camelKeys(selectOrganizationUsers);
diff --git a/api-sample/src/actions/organizations/fetchOrganizationsByEmail/index.js b/api-sample/src/actions/organizations/fetchOrganizationsByEmail/index.js
new file mode 100644
index 00000000..a9c63237
--- /dev/null
+++ b/api-sample/src/actions/organizations/fetchOrganizationsByEmail/index.js
@@ -0,0 +1,9 @@
+const selectOrgOverview = require("./queries/selectOrganizationByEmail");
+
+const fetchOrganizationsByEmail = async ({ email }) => {
+ const organizations = await selectOrgOverview({ email });
+
+ return { organizations };
+};
+
+module.exports = fetchOrganizationsByEmail;
diff --git a/api-sample/src/actions/organizations/fetchOrganizationsByEmail/queries/selectOrganizationByEmail.js b/api-sample/src/actions/organizations/fetchOrganizationsByEmail/queries/selectOrganizationByEmail.js
new file mode 100644
index 00000000..b11feccf
--- /dev/null
+++ b/api-sample/src/actions/organizations/fetchOrganizationsByEmail/queries/selectOrganizationByEmail.js
@@ -0,0 +1,16 @@
+const { submitQuery, camelKeys } = require("~root/lib/database");
+
+const selectOrganizationByEmail = ({ email }) => submitQuery`
+ SELECT
+ organizations.organization_id,
+ organizations.organization_name,
+ user_organization_roles.user_organization_role,
+ user_organization_roles.user_organization_role_id
+ FROM user_organizations
+ LEFT JOIN users ON user_organizations.user_id = users.user_id
+ LEFT JOIN organizations ON user_organizations.organization_id = organizations.organization_id
+ LEFT JOIN user_organization_roles ON user_organizations.user_organization_role_id = user_organization_roles.user_organization_role_id
+ WHERE users.email = ${email}
+`;
+
+module.exports = camelKeys(selectOrganizationByEmail);
diff --git a/api-sample/src/actions/organizations/fetchOrganizationsByUserId/index.js b/api-sample/src/actions/organizations/fetchOrganizationsByUserId/index.js
new file mode 100644
index 00000000..d2a7e2f3
--- /dev/null
+++ b/api-sample/src/actions/organizations/fetchOrganizationsByUserId/index.js
@@ -0,0 +1,9 @@
+const selectOrganizationsByUserId = require("./queries/selectOrganizationsByUserId");
+
+const fetchOrganizationsByUserId = async ({ userId }) => {
+ const organizations = await selectOrganizationsByUserId({ userId });
+
+ return { organizations };
+};
+
+module.exports = fetchOrganizationsByUserId;
diff --git a/api-sample/src/actions/organizations/fetchOrganizationsByUserId/queries/selectOrganizationsByUserId.js b/api-sample/src/actions/organizations/fetchOrganizationsByUserId/queries/selectOrganizationsByUserId.js
new file mode 100644
index 00000000..c81478ad
--- /dev/null
+++ b/api-sample/src/actions/organizations/fetchOrganizationsByUserId/queries/selectOrganizationsByUserId.js
@@ -0,0 +1,16 @@
+const { submitQuery, camelKeys } = require("~root/lib/database");
+
+const selectOrganizationsByUserId = ({ userId }) => submitQuery`
+ SELECT
+ organizations.organization_id,
+ organizations.organization_name,
+ user_organization_roles.user_organization_role,
+ user_organization_roles.user_organization_role_id
+ FROM user_organizations
+ LEFT JOIN users ON user_organizations.user_id = users.user_id
+ LEFT JOIN organizations ON user_organizations.organization_id = organizations.organization_id
+ LEFT JOIN user_organization_roles ON user_organizations.user_organization_role_id = user_organization_roles.user_organization_role_id
+ WHERE users.user_id = ${userId}
+`;
+
+module.exports = camelKeys(selectOrganizationsByUserId);
diff --git a/api-sample/src/actions/organizations/modifyOrganizationUser/index.js b/api-sample/src/actions/organizations/modifyOrganizationUser/index.js
new file mode 100644
index 00000000..927f448b
--- /dev/null
+++ b/api-sample/src/actions/organizations/modifyOrganizationUser/index.js
@@ -0,0 +1,21 @@
+const updateOrganizationUser = require("./queries/updateOrganizationUser");
+
+const modifyOrganizationUser = async ({
+ userId,
+ orgId,
+ orgUserId,
+ userRoleId,
+ enabled
+}) => {
+ const updatedorgUserId = await updateOrganizationUser({
+ userId,
+ orgId,
+ orgUserId,
+ userRoleId,
+ enabled
+ });
+
+ return { updatedorgUserId };
+};
+
+module.exports = modifyOrganizationUser;
diff --git a/api-sample/src/actions/organizations/modifyOrganizationUser/queries/updateOrganizationUser.js b/api-sample/src/actions/organizations/modifyOrganizationUser/queries/updateOrganizationUser.js
new file mode 100644
index 00000000..c1ce506f
--- /dev/null
+++ b/api-sample/src/actions/organizations/modifyOrganizationUser/queries/updateOrganizationUser.js
@@ -0,0 +1,41 @@
+const { submitQuery, sql, sqlReduce } = require("~root/lib/database");
+
+const updateOrganizationUser = ({
+ userId,
+ orgId,
+ orgUserId,
+ userRoleId = null,
+ enabled = null
+}) => {
+ const updates = [];
+
+ if (userRoleId !== null) {
+ updates.push(sql`user_role_id = ${userRoleId}`);
+ }
+
+ if (enabled !== null) {
+ if (enabled === true) {
+ updates.push(sql`disabled_by = null`);
+ updates.push(sql`disabled_at = null`);
+ }
+ if (enabled === false) {
+ updates.push(sql`disabled_by = ${userId}`);
+ updates.push(sql`disabled_at = CURRENT_TIMESTAMP`);
+ }
+ }
+
+ if (updates.length !== 0) {
+ updates.push(sql`updated_at = CURRENT_TIMESTAMP`);
+ return submitQuery`
+ UPDATE
+ user_organizations
+ SET
+ ${updates.reduce(sqlReduce)}
+ WHERE
+ user_id = ${orgUserId} && organization_id = ${orgId}
+ `;
+ }
+ return Promise.resolve();
+};
+
+module.exports = updateOrganizationUser;
diff --git a/api-sample/src/actions/organizations/removeOrganizationUser/index.js b/api-sample/src/actions/organizations/removeOrganizationUser/index.js
new file mode 100644
index 00000000..e5dd4f4a
--- /dev/null
+++ b/api-sample/src/actions/organizations/removeOrganizationUser/index.js
@@ -0,0 +1,11 @@
+const deleteOrganizationUser = require("./queries/deleteOrganizationUser");
+
+const removeOrganizationUser = async ({ orgUserId, orgId }) => {
+ const deletedOrgUser = await deleteOrganizationUser({
+ orgUserId,
+ orgId
+ });
+ return { deletedOrgUser };
+};
+
+module.exports = removeOrganizationUser;
diff --git a/api-sample/src/actions/organizations/removeOrganizationUser/queries/deleteOrganizationUser.js b/api-sample/src/actions/organizations/removeOrganizationUser/queries/deleteOrganizationUser.js
new file mode 100644
index 00000000..d31dea92
--- /dev/null
+++ b/api-sample/src/actions/organizations/removeOrganizationUser/queries/deleteOrganizationUser.js
@@ -0,0 +1,9 @@
+const { submitQuery } = require("~root/lib/database");
+
+const deleteOrganizationUser = ({ orgUserId, orgId }) =>
+ submitQuery`
+ DELETE FROM user_organizations
+ WHERE user_id = ${orgUserId} && organization_id = ${orgId};
+ `;
+
+module.exports = deleteOrganizationUser;
diff --git a/api-sample/src/actions/password-recovery/createRecoveryRequest/queries/insertRecoveryRequest.js b/api-sample/src/actions/password-recovery/createRecoveryRequest/queries/insertRecoveryRequest.js
index 24067627..0215de9c 100644
--- a/api-sample/src/actions/password-recovery/createRecoveryRequest/queries/insertRecoveryRequest.js
+++ b/api-sample/src/actions/password-recovery/createRecoveryRequest/queries/insertRecoveryRequest.js
@@ -1,8 +1,8 @@
const { submitQuery, getInsertId } = require("~root/lib/database");
const insertRecoveryRequest = ({ requestedEmail, URLshortcode }) => submitQuery`
- INSERT INTO
- password_recovery_requests (
+ INSERT INTO
+ password_recovery_requests (
requested_email,
shortcode,
expiry_date
diff --git a/api-sample/src/actions/password-recovery/modifyPassword/queries/updatePassword.js b/api-sample/src/actions/password-recovery/modifyPassword/queries/updatePassword.js
index 64bf600e..317b1e54 100644
--- a/api-sample/src/actions/password-recovery/modifyPassword/queries/updatePassword.js
+++ b/api-sample/src/actions/password-recovery/modifyPassword/queries/updatePassword.js
@@ -1,9 +1,9 @@
const { submitQuery, getInsertId } = require("~root/lib/database");
const updatePassword = ({ email, password }) => submitQuery`
-
-UPDATE users
-SET password = SHA2(CONCAT(${password},${process.env.PASSWORD_SALT}), 224) WHERE email= ${email};
+ UPDATE users
+ SET password = SHA2(CONCAT(${password},${process.env.PASSWORD_SALT}), 224)
+ WHERE email= ${email};
`;
module.exports = getInsertId(updatePassword);
diff --git a/api-sample/src/actions/password-recovery/modifyRecoveryDate/queries/updateRecoveryDate.js b/api-sample/src/actions/password-recovery/modifyRecoveryDate/queries/updateRecoveryDate.js
index 83268bf1..1cb280d7 100644
--- a/api-sample/src/actions/password-recovery/modifyRecoveryDate/queries/updateRecoveryDate.js
+++ b/api-sample/src/actions/password-recovery/modifyRecoveryDate/queries/updateRecoveryDate.js
@@ -1,11 +1,9 @@
const { submitQuery, getInsertId } = require("~root/lib/database");
const updateRecoveryDate = ({ email }) => submitQuery`
-
UPDATE password_recovery_requests
SET recovered_at = CURRENT_TIMESTAMP
WHERE requested_email = ${email};
-
- `;
+`;
module.exports = getInsertId(updateRecoveryDate);
diff --git a/api-sample/src/actions/schemaHelpers/queries/selectEventUser/selectEventUser.js b/api-sample/src/actions/schemaHelpers/queries/selectEventUser/selectEventUser.js
new file mode 100644
index 00000000..18814c4b
--- /dev/null
+++ b/api-sample/src/actions/schemaHelpers/queries/selectEventUser/selectEventUser.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectEventUser = ({ orgId, userId }) => submitQuery`
+ SELECT
+ user_organization_id
+ FROM user_organizations
+ WHERE user_id = ${userId} && organization_id = ${orgId} && (user_role_id = 3 || user_role_id = 4 )
+`;
+
+module.exports = getFirst(selectEventUser, "user_organization_id");
diff --git a/api-sample/src/actions/schemaHelpers/queries/selectOrganizationUser/selectOrganizationUser.js b/api-sample/src/actions/schemaHelpers/queries/selectOrganizationUser/selectOrganizationUser.js
new file mode 100644
index 00000000..0f37a0f8
--- /dev/null
+++ b/api-sample/src/actions/schemaHelpers/queries/selectOrganizationUser/selectOrganizationUser.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectOrganizationUser = ({ orgId, userId }) => submitQuery`
+ SELECT
+ user_organization_id
+ FROM user_organizations
+ WHERE user_id = ${userId} && organization_id = ${orgId}
+`;
+
+module.exports = getFirst(selectOrganizationUser, "user_organization_id");
diff --git a/api-sample/src/actions/schemaHelpers/queries/selectSuperAdmin/selectSuperAdmin.js b/api-sample/src/actions/schemaHelpers/queries/selectSuperAdmin/selectSuperAdmin.js
new file mode 100644
index 00000000..2bdd688a
--- /dev/null
+++ b/api-sample/src/actions/schemaHelpers/queries/selectSuperAdmin/selectSuperAdmin.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectSuperAdmin = ({ userId }) => submitQuery`
+ SELECT
+ user_id
+ FROM users
+ WHERE user_id = ${userId} && is_super_admin = 1;
+`;
+
+module.exports = getFirst(selectSuperAdmin, "user_id");
diff --git a/api-sample/src/actions/schemaHelpers/queries/selectUserById/selectUserById.js b/api-sample/src/actions/schemaHelpers/queries/selectUserById/selectUserById.js
new file mode 100644
index 00000000..e155c461
--- /dev/null
+++ b/api-sample/src/actions/schemaHelpers/queries/selectUserById/selectUserById.js
@@ -0,0 +1,11 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUserById = ({ userId }) => submitQuery`
+ SELECT
+ first_name,
+ email
+ FROM users
+ WHERE user_id = ${userId}
+`;
+
+module.exports = getFirst(selectUserById);
diff --git a/api-sample/src/actions/schemaHelpers/queries/selectUserById/selectUserByOrgId.js b/api-sample/src/actions/schemaHelpers/queries/selectUserById/selectUserByOrgId.js
new file mode 100644
index 00000000..bcedfc21
--- /dev/null
+++ b/api-sample/src/actions/schemaHelpers/queries/selectUserById/selectUserByOrgId.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUserByOrgId = ({ userId, orgId }) => submitQuery`
+ SELECT
+ user_organization_id
+ FROM user_organizations
+ WHERE user_id = ${userId} && organization_id = ${orgId}
+`;
+
+module.exports = getFirst(selectUserByOrgId);
diff --git a/api-sample/src/actions/users/createCompleteRegistration/index.js b/api-sample/src/actions/users/createCompleteRegistration/index.js
new file mode 100644
index 00000000..ffc24e52
--- /dev/null
+++ b/api-sample/src/actions/users/createCompleteRegistration/index.js
@@ -0,0 +1,21 @@
+const insertCompleteRegistration = require("./queries/insertCompleteRegistration");
+
+const createCompleteRegistration = async ({
+ firstName,
+ lastName,
+ email,
+ password,
+ companyName
+}) => {
+ const userId = await insertCompleteRegistration({
+ firstName,
+ lastName,
+ email,
+ password,
+ companyName
+ });
+
+ return { userId };
+};
+
+module.exports = createCompleteRegistration;
diff --git a/api-sample/src/actions/users/createCompleteRegistration/queries/insertCompleteRegistration.js b/api-sample/src/actions/users/createCompleteRegistration/queries/insertCompleteRegistration.js
new file mode 100644
index 00000000..5cda41cc
--- /dev/null
+++ b/api-sample/src/actions/users/createCompleteRegistration/queries/insertCompleteRegistration.js
@@ -0,0 +1,21 @@
+const { submitQuery, getInsertId } = require("~root/lib/database");
+
+const insertUser = ({ firstName, lastName, email, password }) => submitQuery`
+ INSERT INTO users (
+ first_name,
+ last_name,
+ email,
+ password,
+ is_super_admin
+ )
+ VALUES
+ (
+ ${firstName},
+ ${lastName},
+ ${email},
+ ${password},
+ 0
+ );
+`;
+
+module.exports = getInsertId(insertUser);
diff --git a/api-sample/src/actions/users/createOrganizationInvitation/index.js b/api-sample/src/actions/users/createOrganizationInvitation/index.js
new file mode 100644
index 00000000..50cf6023
--- /dev/null
+++ b/api-sample/src/actions/users/createOrganizationInvitation/index.js
@@ -0,0 +1,23 @@
+const insertOrganizationInvitation = require("./queries/insertInvitation");
+
+const createOrganizationInvitation = async ({
+ userId,
+ orgId,
+ email,
+ userOrganizationRoleId,
+ comment,
+ invitationShortcode
+}) => {
+ const newInvitationId = await insertOrganizationInvitation({
+ userId,
+ orgId,
+ email,
+ userOrganizationRoleId,
+ comment,
+ invitationShortcode
+ });
+
+ return { newInvitationId };
+};
+
+module.exports = createOrganizationInvitation;
diff --git a/api-sample/src/actions/users/createOrganizationInvitation/queries/insertInvitation.js b/api-sample/src/actions/users/createOrganizationInvitation/queries/insertInvitation.js
new file mode 100644
index 00000000..a24b1783
--- /dev/null
+++ b/api-sample/src/actions/users/createOrganizationInvitation/queries/insertInvitation.js
@@ -0,0 +1,35 @@
+const {
+ submitQuery,
+ getInsertId,
+ sqlValueOrNull
+} = require("~root/lib/database");
+
+const insertOrganizationInvitation = ({
+ userId,
+ orgId,
+ email,
+ userOrganizationRoleId,
+ comment,
+ invitationShortcode
+}) => submitQuery`
+ INSERT INTO
+ user_organization_invitations(
+ email,
+ comment,
+ invitation_shortcode,
+ invited_by,
+ organization_id,
+ user_organization_role_id
+ )
+VALUES
+ (
+ ${email},
+ ${sqlValueOrNull(comment)},
+ ${invitationShortcode},
+ ${userId},
+ ${orgId},
+ ${userOrganizationRoleId}
+ );
+`;
+
+module.exports = getInsertId(insertOrganizationInvitation);
diff --git a/api-sample/src/actions/users/createRegistrationRequest/index.js b/api-sample/src/actions/users/createRegistrationRequest/index.js
new file mode 100644
index 00000000..04d59328
--- /dev/null
+++ b/api-sample/src/actions/users/createRegistrationRequest/index.js
@@ -0,0 +1,23 @@
+const insertRegistrationRequest = require("./queries/insertRegistrationRequest");
+
+const createRegistrationRequest = async ({
+ firstName,
+ lastName,
+ email,
+ password,
+ organizationName,
+ registrationShortcode
+}) => {
+ const createdRegistrationRequest = await insertRegistrationRequest({
+ firstName,
+ lastName,
+ email,
+ password,
+ organizationName,
+ registrationShortcode
+ });
+
+ return { createdRegistrationRequest };
+};
+
+module.exports = createRegistrationRequest;
diff --git a/api-sample/src/actions/users/createRegistrationRequest/queries/insertRegistrationRequest.js b/api-sample/src/actions/users/createRegistrationRequest/queries/insertRegistrationRequest.js
new file mode 100644
index 00000000..4fde7845
--- /dev/null
+++ b/api-sample/src/actions/users/createRegistrationRequest/queries/insertRegistrationRequest.js
@@ -0,0 +1,34 @@
+const { submitQuery, getInsertId } = require("~root/lib/database");
+
+const insertUser = ({
+ firstName,
+ lastName,
+ email,
+ password,
+ organizationName,
+ registrationShortcode
+}) => submitQuery`
+ INSERT INTO registration_requests (
+ first_name,
+ last_name,
+ email,
+ password,
+ is_super_admin,
+ organization_name,
+ registration_shortcode,
+ status
+ )
+ VALUES
+ (
+ ${firstName},
+ ${lastName},
+ ${email},
+ SHA2(CONCAT(${password},${process.env.PASSWORD_SALT}), 224),
+ 0,
+ ${organizationName},
+ ${registrationShortcode},
+ "pending"
+ );
+`;
+
+module.exports = getInsertId(insertUser);
diff --git a/api-sample/src/actions/users/createUser/index.js b/api-sample/src/actions/users/createUser/index.js
index a06f14ed..367bafba 100644
--- a/api-sample/src/actions/users/createUser/index.js
+++ b/api-sample/src/actions/users/createUser/index.js
@@ -5,14 +5,14 @@ const createUser = async ({
lastName,
email,
password,
- userTypeId
+ isSuperAdmin
}) => {
const user = await insertUser({
firstName,
lastName,
email,
password,
- userTypeId
+ isSuperAdmin
});
return { user };
diff --git a/api-sample/src/actions/users/createUser/queries/insertUser.js b/api-sample/src/actions/users/createUser/queries/insertUser.js
index 6087b56d..6ce354d6 100644
--- a/api-sample/src/actions/users/createUser/queries/insertUser.js
+++ b/api-sample/src/actions/users/createUser/queries/insertUser.js
@@ -5,7 +5,7 @@ const insertUser = ({
lastName,
email,
password,
- userTypeId
+ isSuperAdmin
}) => submitQuery`
INSERT INTO users
(
@@ -13,7 +13,7 @@ const insertUser = ({
last_name,
email,
password,
- user_type_id
+ is_super_admin
)
VALUES
(
@@ -21,7 +21,7 @@ const insertUser = ({
${lastName},
${email},
SHA2(CONCAT(${password},${process.env.PASSWORD_SALT}), 224),
- ${userTypeId}
+ ${isSuperAdmin}
)
`;
diff --git a/api-sample/src/actions/users/fetchRegistrationRequestByShortcode/index.js b/api-sample/src/actions/users/fetchRegistrationRequestByShortcode/index.js
new file mode 100644
index 00000000..c74ad96b
--- /dev/null
+++ b/api-sample/src/actions/users/fetchRegistrationRequestByShortcode/index.js
@@ -0,0 +1,13 @@
+const selectRegistrationRequestByShortcode = require("./queries/selectRegistrationRequestByShortcode");
+
+const fetchRegistrationRequestByShortcode = async ({
+ registrationShortcode
+}) => {
+ const potentialNewUserData = await selectRegistrationRequestByShortcode({
+ registrationShortcode
+ });
+
+ return { potentialNewUserData };
+};
+
+module.exports = fetchRegistrationRequestByShortcode;
diff --git a/api-sample/src/actions/users/fetchRegistrationRequestByShortcode/queries/selectRegistrationRequestByShortcode.js b/api-sample/src/actions/users/fetchRegistrationRequestByShortcode/queries/selectRegistrationRequestByShortcode.js
new file mode 100644
index 00000000..1a3265d6
--- /dev/null
+++ b/api-sample/src/actions/users/fetchRegistrationRequestByShortcode/queries/selectRegistrationRequestByShortcode.js
@@ -0,0 +1,18 @@
+const { submitQuery, camelKeys, getFirst } = require("~root/lib/database");
+
+const selectRegistrationRequestByShortcode = ({
+ registrationShortcode
+}) => submitQuery`
+ SELECT
+ first_name,
+ last_name,
+ email,
+ password,
+ organization_name
+ FROM registration_requests
+ WHERE registration_shortcode = ${registrationShortcode}
+ ORDER BY created_at DESC
+ LIMIT 1
+`;
+
+module.exports = getFirst(camelKeys(selectRegistrationRequestByShortcode));
diff --git a/api-sample/src/actions/users/fetchRequestByShortcode/index.js b/api-sample/src/actions/users/fetchRequestByShortcode/index.js
new file mode 100644
index 00000000..d0eedc53
--- /dev/null
+++ b/api-sample/src/actions/users/fetchRequestByShortcode/index.js
@@ -0,0 +1,9 @@
+const selectRequestByShortcode = require("./queries/selectRequestByShortcode");
+
+const fetchRequestByShortcode = async ({ shortcode }) => {
+ const request = await selectRequestByShortcode({ shortcode });
+
+ return { request };
+};
+
+module.exports = fetchRequestByShortcode;
diff --git a/api-sample/src/actions/users/fetchRequestByShortcode/queries/selectRequestByShortcode.js b/api-sample/src/actions/users/fetchRequestByShortcode/queries/selectRequestByShortcode.js
new file mode 100644
index 00000000..93b0a742
--- /dev/null
+++ b/api-sample/src/actions/users/fetchRequestByShortcode/queries/selectRequestByShortcode.js
@@ -0,0 +1,15 @@
+const { submitQuery, camelKeys, getFirst } = require("~root/lib/database");
+
+const selectRequestByShortcode = ({ shortcode }) => submitQuery`
+ SELECT
+ password_recovery_request_id,
+ email,
+ shortcode,
+ recovered_at,
+ expiry_date,
+ created_at
+ FROM password_recovery_requests
+ WHERE shortcode = ${shortcode}
+`;
+
+module.exports = getFirst(camelKeys(selectRequestByShortcode));
diff --git a/api-sample/src/actions/users/fetchUser/queries/selectUser.js b/api-sample/src/actions/users/fetchUser/queries/selectUser.js
index d5b5e4a6..6c7a3deb 100644
--- a/api-sample/src/actions/users/fetchUser/queries/selectUser.js
+++ b/api-sample/src/actions/users/fetchUser/queries/selectUser.js
@@ -7,10 +7,10 @@ const selectUser = ({ email, password }) => submitQuery`
last_name,
password,
email,
- user_types.user_type_id,
- user_types.user_type
+ user_roles.user_role_id,
+ user_roles.user_role
FROM users
- LEFT JOIN user_types ON users.user_type_id = user_types.user_type_id
+ LEFT JOIN user_roles ON users.user_role_id = user_roles.user_role_id
WHERE email = ${email}
AND password = SHA2(CONCAT(${password}, ${process.env.PASSWORD_SALT}), 224);
`;
diff --git a/api-sample/src/actions/users/fetchUserByEmail/index.js b/api-sample/src/actions/users/fetchUserByEmail/index.js
new file mode 100644
index 00000000..a5363f55
--- /dev/null
+++ b/api-sample/src/actions/users/fetchUserByEmail/index.js
@@ -0,0 +1,11 @@
+const selectUserIdByEmail = require("./queries/selectUserIdByEmail");
+
+const fetchUserIdByEmail = async ({ email }) => {
+ const newOrgUserUserId = await selectUserIdByEmail({
+ email
+ });
+
+ return { newOrgUserUserId };
+};
+
+module.exports = fetchUserIdByEmail;
diff --git a/api-sample/src/actions/users/fetchUserByEmail/queries/selectUserIdByEmail.js b/api-sample/src/actions/users/fetchUserByEmail/queries/selectUserIdByEmail.js
new file mode 100644
index 00000000..116de09d
--- /dev/null
+++ b/api-sample/src/actions/users/fetchUserByEmail/queries/selectUserIdByEmail.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUserIdByEmail = ({ email }) => submitQuery`
+ SELECT
+ user_id
+ FROM users
+ WHERE email = ${email}
+`;
+
+module.exports = getFirst(selectUserIdByEmail, "user_id");
diff --git a/api-sample/src/actions/users/fetchUserById/queries/selectUserById.js b/api-sample/src/actions/users/fetchUserById/queries/selectUserById.js
index 58e4e154..4bede5a8 100644
--- a/api-sample/src/actions/users/fetchUserById/queries/selectUserById.js
+++ b/api-sample/src/actions/users/fetchUserById/queries/selectUserById.js
@@ -1,14 +1,12 @@
const { submitQuery, camelKeys, getFirst } = require("~root/lib/database");
const selectUserById = ({ userId }) => submitQuery`
- SELECT
- user_id,
+ SELECT
first_name,
last_name,
- user_types.user_type_id,
- user_types.user_type
+ email,
+ is_super_admin,
FROM users
- LEFT JOIN user_types ON users.user_type_id = user_types.user_type_id
WHERE user_id = ${userId}
`;
diff --git a/api-sample/src/actions/users/fetchUserInvitation/index.js b/api-sample/src/actions/users/fetchUserInvitation/index.js
new file mode 100644
index 00000000..8e21601b
--- /dev/null
+++ b/api-sample/src/actions/users/fetchUserInvitation/index.js
@@ -0,0 +1,13 @@
+const selectUserInvitation = require("./queries/selectUserInvitation");
+
+const fetchUserInvitation = async ({ email, invitationId, shortcode }) => {
+ const invitation = await selectUserInvitation({
+ email,
+ invitationId,
+ shortcode
+ });
+
+ return { invitation };
+};
+
+module.exports = fetchUserInvitation;
diff --git a/api-sample/src/actions/users/fetchUserInvitation/queries/selectUserInvitation.js b/api-sample/src/actions/users/fetchUserInvitation/queries/selectUserInvitation.js
new file mode 100644
index 00000000..1ba779cf
--- /dev/null
+++ b/api-sample/src/actions/users/fetchUserInvitation/queries/selectUserInvitation.js
@@ -0,0 +1,32 @@
+const { submitQuery, camelKeys, getFirst } = require("~root/lib/database");
+
+const selectUserInvitation = ({
+ email,
+ invitationId,
+ shortcode
+}) => submitQuery`
+ SELECT
+ user_organization_invitations.user_organization_invitation_id,
+ user_organization_invitations.invitation_shortcode,
+ user_organization_invitations.organization_id,
+ user_organization_invitations.email,
+ user_organization_invitations.invited_by,
+ user_organization_invitations.user_organization_role_id,
+ user_organization_invitations.comment,
+ user_organization_invitations.sent_at,
+ user_organization_invitations.accepted_at,
+ user_organization_invitations.declined_at,
+ user_organization_invitations.updated_at,
+ users.first_name as invitedByFirstName,
+ users.last_name as invitedByLastName,
+ user_organization_roles.user_organization_role,
+ organizations.organization_name
+ FROM user_organization_invitations
+ LEFT JOIN users ON users.user_id = user_organization_invitations.invited_by
+ LEFT JOIN user_organization_roles ON user_organization_roles.user_organization_role_id = user_organization_invitations.user_organization_role_id
+ LEFT JOIN organizations ON organizations.organization_id = user_organization_invitations.organization_id
+ WHERE user_organization_invitations.email = ${email} && user_organization_invitations.user_organization_invitation_id = ${invitationId} && user_organization_invitations.invitation_shortcode = ${shortcode}
+ ORDER BY sent_at DESC
+`;
+
+module.exports = getFirst(camelKeys(selectUserInvitation));
diff --git a/api-sample/src/actions/users/fetchUserInvitations/index.js b/api-sample/src/actions/users/fetchUserInvitations/index.js
new file mode 100644
index 00000000..7a836e11
--- /dev/null
+++ b/api-sample/src/actions/users/fetchUserInvitations/index.js
@@ -0,0 +1,9 @@
+const selectUserInvitations = require("./queries/selectUserInvitations");
+
+const fetchUserInvitations = async ({ email }) => {
+ const invitations = await selectUserInvitations({ email });
+
+ return { invitations };
+};
+
+module.exports = fetchUserInvitations;
diff --git a/api-sample/src/actions/users/fetchUserInvitations/queries/selectUserInvitations.js b/api-sample/src/actions/users/fetchUserInvitations/queries/selectUserInvitations.js
new file mode 100644
index 00000000..1e4b8c82
--- /dev/null
+++ b/api-sample/src/actions/users/fetchUserInvitations/queries/selectUserInvitations.js
@@ -0,0 +1,28 @@
+const { submitQuery, camelKeys } = require("~root/lib/database");
+
+const selectUserInvitations = ({ email }) => submitQuery`
+ SELECT
+ user_organization_invitations.user_organization_invitation_id,
+ user_organization_invitations.invitation_shortcode,
+ user_organization_invitations.organization_id,
+ user_organization_invitations.email,
+ user_organization_invitations.invited_by,
+ user_organization_invitations.user_organization_role_id,
+ user_organization_invitations.comment,
+ user_organization_invitations.sent_at,
+ user_organization_invitations.accepted_at,
+ user_organization_invitations.declined_at,
+ user_organization_invitations.updated_at,
+ users.first_name as invitedByFirstName,
+ users.last_name as invitedByLastName,
+ user_organization_roles.user_organization_role,
+ organizations.organization_name
+ FROM user_organization_invitations
+ LEFT JOIN users ON users.user_id = user_organization_invitations.invited_by
+ LEFT JOIN user_organization_roles ON user_organization_roles.user_organization_role_id = user_organization_invitations.user_organization_role_id
+ LEFT JOIN organizations ON organizations.organization_id = user_organization_invitations.organization_id
+ WHERE user_organization_invitations.email = ${email}
+ ORDER BY sent_at DESC
+`;
+
+module.exports = camelKeys(selectUserInvitations);
diff --git a/api-sample/src/actions/users/fetchUserOrganizations/index.js b/api-sample/src/actions/users/fetchUserOrganizations/index.js
new file mode 100644
index 00000000..a3eb2554
--- /dev/null
+++ b/api-sample/src/actions/users/fetchUserOrganizations/index.js
@@ -0,0 +1,9 @@
+const selectUserOrganizations = require("./queries/selectUserOrganizations");
+
+const fetchUserOrganizations = async ({ userId }) => {
+ const organizations = await selectUserOrganizations({ userId });
+
+ return { organizations };
+};
+
+module.exports = fetchUserOrganizations;
diff --git a/api-sample/src/actions/users/fetchUserOrganizations/queries/selectUserOrganizations.js b/api-sample/src/actions/users/fetchUserOrganizations/queries/selectUserOrganizations.js
new file mode 100644
index 00000000..05f21ee2
--- /dev/null
+++ b/api-sample/src/actions/users/fetchUserOrganizations/queries/selectUserOrganizations.js
@@ -0,0 +1,16 @@
+const { submitQuery, camelKeys } = require("~root/lib/database");
+
+const selectUserOrganizations = ({ userId }) => submitQuery`
+ SELECT
+ user_organizations.user_id,
+ organizations.organization_id,
+ user_organizations.user_organization_role_id,
+ user_organization_roles.user_organization_role,
+ organizations.organization_name
+ FROM user_organizations
+ LEFT JOIN organizations ON organizations.organization_id = user_organizations.organization_id
+ LEFT JOIN user_organization_roles ON user_organization_roles.user_organization_role_id = user_organizations.user_organization_role_id
+ WHERE user_id = ${userId}
+`;
+
+module.exports = camelKeys(selectUserOrganizations);
diff --git a/api-sample/src/actions/users/fetchUserTypes/index.js b/api-sample/src/actions/users/fetchUserTypes/index.js
deleted file mode 100644
index e812aa83..00000000
--- a/api-sample/src/actions/users/fetchUserTypes/index.js
+++ /dev/null
@@ -1,9 +0,0 @@
-const selectUserTypes = require("./queries/selectUserTypes");
-
-const fetchUserTypes = async () => {
- const userTypes = await selectUserTypes();
-
- return { userTypes };
-};
-
-module.exports = fetchUserTypes;
diff --git a/api-sample/src/actions/users/fetchUserTypes/queries/selectUserTypes.js b/api-sample/src/actions/users/fetchUserTypes/queries/selectUserTypes.js
deleted file mode 100644
index c25429f9..00000000
--- a/api-sample/src/actions/users/fetchUserTypes/queries/selectUserTypes.js
+++ /dev/null
@@ -1,9 +0,0 @@
-const { submitQuery, camelKeys } = require("~root/lib/database");
-
-const selectUserTypes = () => submitQuery`
- SELECT
- user_type
- FROM user_types
-`;
-
-module.exports = camelKeys(selectUserTypes);
diff --git a/api-sample/src/actions/users/modifyRegistrationRequestStatus/index.js b/api-sample/src/actions/users/modifyRegistrationRequestStatus/index.js
new file mode 100644
index 00000000..87c5d523
--- /dev/null
+++ b/api-sample/src/actions/users/modifyRegistrationRequestStatus/index.js
@@ -0,0 +1,10 @@
+const updateRegistrationRequestStatus = require("./queries/updateRegistrationRequestStatus");
+
+const modifyRegistrationRequestStatus = async ({ registrationShortcode }) => {
+ const registrationRequestStatus = await updateRegistrationRequestStatus({
+ registrationShortcode
+ });
+ return { registrationRequestStatus };
+};
+
+module.exports = modifyRegistrationRequestStatus;
diff --git a/api-sample/src/actions/users/modifyRegistrationRequestStatus/queries/updateRegistrationRequestStatus.js b/api-sample/src/actions/users/modifyRegistrationRequestStatus/queries/updateRegistrationRequestStatus.js
new file mode 100644
index 00000000..a566d589
--- /dev/null
+++ b/api-sample/src/actions/users/modifyRegistrationRequestStatus/queries/updateRegistrationRequestStatus.js
@@ -0,0 +1,12 @@
+const { submitQuery } = require("~root/lib/database");
+
+const updateRegistrationRequestStatus = ({
+ registrationShortcode
+}) => submitQuery`
+ UPDATE registration_requests
+ SET status = "approved",
+ updated_at = CURRENT_TIMESTAMP
+ WHERE registration_shortcode= ${registrationShortcode};
+`;
+
+module.exports = updateRegistrationRequestStatus;
diff --git a/api-sample/src/actions/users/modifyUser/queries/updateUserDetails.js b/api-sample/src/actions/users/modifyUser/queries/updateUserDetails.js
index fdb033ef..6368ed95 100644
--- a/api-sample/src/actions/users/modifyUser/queries/updateUserDetails.js
+++ b/api-sample/src/actions/users/modifyUser/queries/updateUserDetails.js
@@ -7,10 +7,10 @@ const updateUserDetails = ({
password
}) => submitQuery`
UPDATE users
- SET
+ SET
first_name = ${firstName},
last_name = ${lastName},
- password= SHA2(CONCAT(${password}, ${process.env.PASSWORD_SALT}), 224)
+ password = SHA2(CONCAT(${password}, ${process.env.PASSWORD_SALT}), 224)
WHERE user_id = ${userId};
`;
diff --git a/api-sample/src/actions/users/modifyUserInvitation/index.js b/api-sample/src/actions/users/modifyUserInvitation/index.js
new file mode 100644
index 00000000..881f9f22
--- /dev/null
+++ b/api-sample/src/actions/users/modifyUserInvitation/index.js
@@ -0,0 +1,12 @@
+const updateUserInvitation = require("./queries/updateUserInvitation");
+
+const modifyUserInvitation = async ({ invitationId, shortcode }) => {
+ const patchedInvitationId = await updateUserInvitation({
+ invitationId,
+ shortcode
+ });
+
+ return { patchedInvitationId };
+};
+
+module.exports = modifyUserInvitation;
diff --git a/api-sample/src/actions/users/modifyUserInvitation/queries/updateUserInvitation.js b/api-sample/src/actions/users/modifyUserInvitation/queries/updateUserInvitation.js
new file mode 100644
index 00000000..ca59b576
--- /dev/null
+++ b/api-sample/src/actions/users/modifyUserInvitation/queries/updateUserInvitation.js
@@ -0,0 +1,28 @@
+const {
+ submitQuery,
+ sql,
+ sqlReduce,
+ getInsertId
+} = require("~root/lib/database");
+
+const updateUserInvitation = ({ invitationId, shortcode }) => {
+ const updates = [];
+
+ if (invitationId && shortcode) {
+ updates.push(sql`accepted_at = CURRENT_TIMESTAMP`);
+ }
+
+ if (updates.length !== 0) {
+ return submitQuery`
+ UPDATE
+ user_organization_invitations
+ SET
+ ${updates.reduce(sqlReduce)}
+ WHERE
+ user_organization_invitation_id = ${invitationId} && invitation_shortcode = ${shortcode}
+ `;
+ }
+ return Promise.resolve();
+};
+
+module.exports = getInsertId(updateUserInvitation);
diff --git a/api-sample/src/actions/users/removeInvitation/index.js b/api-sample/src/actions/users/removeInvitation/index.js
new file mode 100644
index 00000000..62d89272
--- /dev/null
+++ b/api-sample/src/actions/users/removeInvitation/index.js
@@ -0,0 +1,10 @@
+const deleteInvitation = require("./queries/deleteInvitation");
+
+const removeInvitation = async ({ invitationId }) => {
+ const invitation = await deleteInvitation({
+ invitationId
+ });
+ return { invitation };
+};
+
+module.exports = removeInvitation;
diff --git a/api-sample/src/actions/users/removeInvitation/queries/deleteInvitation.js b/api-sample/src/actions/users/removeInvitation/queries/deleteInvitation.js
new file mode 100644
index 00000000..86d7d5cf
--- /dev/null
+++ b/api-sample/src/actions/users/removeInvitation/queries/deleteInvitation.js
@@ -0,0 +1,8 @@
+const { submitQuery, getInsertId } = require("~root/lib/database");
+
+const deleteInvitation = ({ invitationId }) => submitQuery`
+ DELETE
+ FROM user_organization_invitations
+ WHERE user_organization_invitation_id = ${invitationId}
+`;
+module.exports = getInsertId(deleteInvitation);
diff --git a/api-sample/src/app/controllers/invitations/acceptInvitation/index.js b/api-sample/src/app/controllers/invitations/acceptInvitation/index.js
new file mode 100644
index 00000000..6d96b809
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/acceptInvitation/index.js
@@ -0,0 +1,48 @@
+const handleAPIError = require("~root/utils/handleAPIError");
+const fetchInvitationById = require("~root/actions/invitations/fetchInvitationById");
+const createOrganizationUser = require("~root/actions/organizations/createOrganizationUser");
+const patchInvitationAsAccepted = require("~root/actions/invitations/patchInvitationAsAccepted");
+const acceptInvitationSchema = require("./schemas/acceptInvitationSchema");
+
+const acceptInvitation = async (req, res) => {
+ const { userId, email } = req.user;
+ const { shortCode, invitationId } = req.body;
+
+ try {
+ await acceptInvitationSchema.validate(
+ { userId, invitationId, shortCode, email },
+ {
+ abortEarly: false
+ }
+ );
+
+ const { invitation } = await fetchInvitationById({
+ invitationId,
+ shortCode
+ });
+
+ const {
+ organizationId: orgId,
+ invitedBy,
+ userOrganizationRoleId
+ } = invitation;
+
+ await patchInvitationAsAccepted({
+ invitationId,
+ shortCode
+ });
+
+ const { organizationUserId } = await createOrganizationUser({
+ newOrgUserUserId: userId,
+ orgId,
+ userId: invitedBy,
+ userOrganizationRoleId
+ });
+
+ res.status(201).send({ organizationUserId });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = acceptInvitation;
diff --git a/api-sample/src/app/controllers/invitations/acceptInvitation/schemas/acceptInvitationSchema.js b/api-sample/src/app/controllers/invitations/acceptInvitation/schemas/acceptInvitationSchema.js
new file mode 100644
index 00000000..6138c70f
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/acceptInvitation/schemas/acceptInvitationSchema.js
@@ -0,0 +1,49 @@
+const yup = require("yup");
+const selectInvitationByShortCode = require("./queries/selectInvitationByShortCode");
+
+const acceptInvitationSchema = yup.object().shape({
+ userId: yup
+ .number()
+ .positive()
+ .min(1, "This field can not be empty!")
+ .label("User Id")
+ .typeError("The user id must be a valid number"),
+
+ email: yup
+ .string()
+ .email()
+ .label("email")
+ .typeError("The email must be a valid email"),
+
+ invitationId: yup
+ .number()
+ .positive()
+ .label("Invitation Id")
+ .typeError("The invitation Id must be a valid number"),
+
+ shortCode: yup
+ .string()
+ .min(1)
+ .label("Invitation ShortCode")
+ .typeError("The Invitation shortCode must be a valid string")
+ .test(
+ "ShortCodeMustExist",
+ "The Invitation ShortCode must belong to an invitation with the same Invitation Id & the Invitation must belong to the user",
+ async function test(shortCode) {
+ const { invitationId, email } = this.parent;
+
+ const invitation = await selectInvitationByShortCode({
+ shortCode,
+ invitationId,
+ email
+ });
+ if (invitation) {
+ return true;
+ }
+
+ return false;
+ }
+ )
+});
+
+module.exports = acceptInvitationSchema;
diff --git a/api-sample/src/app/controllers/invitations/acceptInvitation/schemas/queries/selectInvitationByShortCode.js b/api-sample/src/app/controllers/invitations/acceptInvitation/schemas/queries/selectInvitationByShortCode.js
new file mode 100644
index 00000000..b4c046f3
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/acceptInvitation/schemas/queries/selectInvitationByShortCode.js
@@ -0,0 +1,15 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectInvitationByShortCode = ({
+ shortCode,
+ invitationId,
+ email
+}) => submitQuery`
+ SELECT
+ invited_by,
+ email
+ FROM user_organization_invitations
+ WHERE user_organization_invitation_id = ${invitationId} && invitation_shortcode = ${shortCode} && email = ${email}
+`;
+
+module.exports = getFirst(selectInvitationByShortCode);
diff --git a/api-sample/src/app/controllers/invitations/deleteInvitation/index.js b/api-sample/src/app/controllers/invitations/deleteInvitation/index.js
new file mode 100644
index 00000000..f7f3c192
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/deleteInvitation/index.js
@@ -0,0 +1,32 @@
+const handleAPIError = require("~root/utils/handleAPIError");
+const removeInvitation = require("~root/actions/users/removeInvitation");
+const deleteInvitationSchema = require("./schemas/deleteInvitationSchema");
+
+const deleteInvitation = async (req, res) => {
+ const { userId, email } = req.user;
+ const { shortcode, invitationId } = req.body;
+
+ try {
+ await deleteInvitationSchema.validate(
+ {
+ userId,
+ invitationId,
+ shortcode,
+ email
+ },
+ {
+ abortEarly: false
+ }
+ );
+ const { invitation } = await removeInvitation({
+ invitationId
+ });
+ res.status(204).send({
+ invitation
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = deleteInvitation;
diff --git a/api-sample/src/app/controllers/invitations/deleteInvitation/schemas/deleteInvitationSchema.js b/api-sample/src/app/controllers/invitations/deleteInvitation/schemas/deleteInvitationSchema.js
new file mode 100644
index 00000000..de91e76d
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/deleteInvitation/schemas/deleteInvitationSchema.js
@@ -0,0 +1,48 @@
+const yup = require("yup");
+const selectInvitationByShortCode = require("./queries/selectInvitationByShortCode");
+
+const deleteInvitationSchema = yup.object().shape({
+ userId: yup
+ .number()
+ .positive()
+ .min(1, "This field can not be empty!")
+ .label("User Id")
+ .typeError("The user id must be a valid number"),
+
+ invitationId: yup
+ .string()
+ .label("invitaionId")
+ .typeError("The invitation id must be a valid id"),
+
+ email: yup
+ .string()
+ .email()
+ .label("email")
+ .typeError("The email must be a valid email"),
+
+ shortcode: yup
+ .string()
+ .min(1)
+ .label("Invitation ShortCode")
+ .typeError("The Invitation shortCode must be a valid string")
+ .test(
+ "ShortCodeMustExist",
+ "The Invitation ShortCode must belong to an invitation with the same Invitation Id & the Invitation must belong to the user",
+ async function test(shortCode) {
+ const { invitationId, email } = this.parent;
+
+ const invitation = await selectInvitationByShortCode({
+ shortCode,
+ invitationId,
+ email
+ });
+ if (invitation) {
+ return true;
+ }
+
+ return false;
+ }
+ )
+});
+
+module.exports = deleteInvitationSchema;
diff --git a/api-sample/src/app/controllers/invitations/deleteInvitation/schemas/queries/selectInvitationByShortCode.js b/api-sample/src/app/controllers/invitations/deleteInvitation/schemas/queries/selectInvitationByShortCode.js
new file mode 100644
index 00000000..a3e65f38
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/deleteInvitation/schemas/queries/selectInvitationByShortCode.js
@@ -0,0 +1,11 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUser = ({ shortCode, invitationId, email }) => submitQuery`
+ SELECT
+ invited_by,
+ email
+ FROM user_organization_invitations
+ WHERE user_organization_invitation_id = ${invitationId} && invitation_shortcode = ${shortCode} && email = ${email}
+`;
+
+module.exports = getFirst(selectUser);
diff --git a/api-sample/src/app/controllers/invitations/deleteInvitationByOrgUser/index.js b/api-sample/src/app/controllers/invitations/deleteInvitationByOrgUser/index.js
new file mode 100644
index 00000000..36482be8
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/deleteInvitationByOrgUser/index.js
@@ -0,0 +1,31 @@
+const removeInvitation = require("~root/actions/invitations/removeInvitation");
+const handleAPIError = require("~root/utils/handleAPIError");
+const deleteInvitationSchema = require("./schemas/deleteInvitationSchema");
+
+const deleteInvitationByOrgUser = async (req, res) => {
+ const { userId } = req.user;
+ const { invitationId, orgId } = req.params;
+
+ try {
+ await deleteInvitationSchema.validate(
+ {
+ orgId,
+ userId,
+ invitationId
+ },
+ {
+ abortEarly: false
+ }
+ );
+ const { invitation } = await removeInvitation({
+ invitationId
+ });
+ res.status(204).send({
+ invitation
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = deleteInvitationByOrgUser;
diff --git a/api-sample/src/app/controllers/invitations/deleteInvitationByOrgUser/schemas/deleteInvitationSchema.js b/api-sample/src/app/controllers/invitations/deleteInvitationByOrgUser/schemas/deleteInvitationSchema.js
new file mode 100644
index 00000000..efbc2b31
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/deleteInvitationByOrgUser/schemas/deleteInvitationSchema.js
@@ -0,0 +1,64 @@
+const yup = require("yup");
+// eslint-disable-next-line import/order
+const selectInvitation = require("./queries/selectInvitation");
+const selectSuperAdmin = require("~root/actions/schemaHelpers/queries/selectSuperAdmin/selectSuperAdmin");
+const selectOrganizationUser = require("~root/actions/schemaHelpers/queries/selectOrganizationUser/selectOrganizationUser");
+
+const deleteInvitationSchema = yup.object().shape({
+ userId: yup
+ .number()
+ .positive()
+ .min(1, "This field can not be empty!")
+ .label("User Id")
+ .typeError("The user id must be a valid number")
+ .test(
+ "doesUserBelongToOrganization",
+ "The user must belong to the Organization and have the appropriate access (organization admin or event manager) or be a superAdmin",
+ async function test(userId) {
+ const { orgId } = this.parent;
+
+ const isUserSuperAdmin = await selectSuperAdmin({
+ userId
+ });
+ if (isUserSuperAdmin) {
+ return true;
+ }
+
+ const user = await selectOrganizationUser({
+ orgId,
+ userId
+ });
+ if (user) {
+ return true;
+ }
+
+ return false;
+ }
+ ),
+
+ invitationId: yup
+ .number()
+ .positive()
+ .label("invitaionId")
+ .typeError("The invitation id must be a valid id")
+ .test(
+ "ShortCodeMustExist",
+ "The Invitation ShortCode must belong to an invitation with the same Invitation Id & the Invitation must belong to the user",
+ async function test(shortCode) {
+ const { invitationId, email } = this.parent;
+
+ const invitation = await selectInvitation({
+ shortCode,
+ invitationId,
+ email
+ });
+ if (invitation) {
+ return true;
+ }
+
+ return false;
+ }
+ )
+});
+
+module.exports = deleteInvitationSchema;
diff --git a/api-sample/src/app/controllers/invitations/deleteInvitationByOrgUser/schemas/queries/selectInvitation.js b/api-sample/src/app/controllers/invitations/deleteInvitationByOrgUser/schemas/queries/selectInvitation.js
new file mode 100644
index 00000000..a4517584
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/deleteInvitationByOrgUser/schemas/queries/selectInvitation.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectInvitation = ({ invitationId }) => submitQuery`
+ SELECT
+ user_organization_invitation_id
+ FROM user_organization_invitations
+ WHERE user_organization_invitation_id = ${invitationId}
+`;
+
+module.exports = getFirst(selectInvitation);
diff --git a/api-sample/src/app/controllers/invitations/getUserInvitations/index.js b/api-sample/src/app/controllers/invitations/getUserInvitations/index.js
new file mode 100644
index 00000000..dabf9c64
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/getUserInvitations/index.js
@@ -0,0 +1,26 @@
+const fetchUserInvitations = require("~root/actions/users/fetchUserInvitations");
+const handleAPIError = require("~root/utils/handleAPIError");
+const getUserInvitationSchema = require("./schemas/getUserInvitationsSchema");
+
+const getUserInvitations = async (req, res) => {
+ const { userId, email } = req.user;
+ try {
+ await getUserInvitationSchema.validate(
+ {
+ userId,
+ email
+ },
+ {
+ abortEarly: false
+ }
+ );
+ const { invitations } = await fetchUserInvitations({ email });
+ res.status(201).send({
+ invitations
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = getUserInvitations;
diff --git a/api-sample/src/app/controllers/invitations/getUserInvitations/schemas/getUserInvitationsSchema.js b/api-sample/src/app/controllers/invitations/getUserInvitations/schemas/getUserInvitationsSchema.js
new file mode 100644
index 00000000..48dab141
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/getUserInvitations/schemas/getUserInvitationsSchema.js
@@ -0,0 +1,33 @@
+const yup = require("yup");
+const selectUser = require("./queries/selectUser");
+
+const getUserInvitationsSchema = yup.object().shape({
+ email: yup
+ .string()
+ .email()
+ .required()
+ .label("email")
+ .typeError("The email is invalid"),
+
+ userId: yup
+ .number()
+ .positive()
+ .min(1, "This field can not be empty!")
+ .label("User Id")
+ .typeError("The user id must be a valid number")
+ .test("doesUserExist", "The user must exist", async function test(userId) {
+ const { email } = this.parent;
+
+ const user = await selectUser({
+ userId,
+ email
+ });
+ if (user) {
+ return true;
+ }
+
+ return false;
+ })
+});
+
+module.exports = getUserInvitationsSchema;
diff --git a/api-sample/src/app/controllers/invitations/getUserInvitations/schemas/queries/selectUser.js b/api-sample/src/app/controllers/invitations/getUserInvitations/schemas/queries/selectUser.js
new file mode 100644
index 00000000..152d3d7a
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/getUserInvitations/schemas/queries/selectUser.js
@@ -0,0 +1,13 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUser = ({ userId, email }) => submitQuery`
+ SELECT
+ user_id,
+ first_name,
+ last_name,
+ email
+ FROM users
+ WHERE user_id = ${userId} && email = ${email}
+`;
+
+module.exports = getFirst(selectUser);
diff --git a/api-sample/src/app/controllers/invitations/patchOrganizationInvitation/index.js b/api-sample/src/app/controllers/invitations/patchOrganizationInvitation/index.js
new file mode 100644
index 00000000..0f86a342
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/patchOrganizationInvitation/index.js
@@ -0,0 +1,34 @@
+const handleAPIError = require("~root/utils/handleAPIError");
+const modifyOrganizationInvitation = require("~root/actions/invitations/modifyOrganizationInvitation");
+const patchOrganizationInvitationSchema = require("./schemas/patchOrganizationInvitationSchema");
+
+const patchOrganizationInvitation = async (req, res) => {
+ const { userId } = req.user;
+ const { email, orgId, invitationShortcode } = req.body;
+
+ try {
+ await patchOrganizationInvitationSchema.validate(
+ {
+ userId,
+ email,
+ orgId,
+ invitationShortcode
+ },
+ {
+ abortEarly: false
+ }
+ );
+
+ const { orgInvitation } = await modifyOrganizationInvitation({
+ email,
+ invitationShortcode,
+ orgId
+ });
+
+ res.status(201).send({ orgInvitation });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = patchOrganizationInvitation;
diff --git a/api-sample/src/app/controllers/invitations/patchOrganizationInvitation/schemas/patchOrganizationInvitationSchema.js b/api-sample/src/app/controllers/invitations/patchOrganizationInvitation/schemas/patchOrganizationInvitationSchema.js
new file mode 100644
index 00000000..0df99f1a
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/patchOrganizationInvitation/schemas/patchOrganizationInvitationSchema.js
@@ -0,0 +1,87 @@
+const yup = require("yup");
+const selectSuperAdmin = require("~root/actions/schemaHelpers/queries/selectSuperAdmin/selectSuperAdmin");
+const selectOrganizationUser = require("~root/actions/schemaHelpers/queries/selectOrganizationUser/selectOrganizationUser");
+const selectUserOrganizationByEmail = require("./queries/selectUserOrganizationByEmail");
+const selectInvitation = require("./queries/selectInvitation");
+
+const patchOrganizationInvitationSchema = yup.object().shape({
+ email: yup
+ .string()
+ .email()
+ .required()
+ .label("Email")
+ .typeError("Email is invalid.")
+ .test("doesEmailExist", "User account already exists.", async function test(
+ email
+ ) {
+ const { orgId } = this.parent;
+
+ const userOrganization = await selectUserOrganizationByEmail({
+ email,
+ orgId
+ });
+
+ if (userOrganization) {
+ return false;
+ }
+
+ return true;
+ }),
+
+ invitationShortcode: yup
+ .string()
+ .required()
+ .label("invitation shortcode")
+ .typeError("the invitation shortcode is not a valid string")
+ .test(
+ "doesInvitationShortcodeExist",
+ "Invitation shortcode does not exists.",
+ async function test(invitationShortcode) {
+ const invitation = await selectInvitation({ invitationShortcode });
+
+ if (invitation) {
+ return true;
+ }
+ return false;
+ }
+ ),
+
+ orgId: yup
+ .number()
+ .required()
+ .label("Organization ID")
+ .typeError("Organization Id must be a number"),
+
+ userId: yup
+ .number()
+ .positive()
+ .min(1, "This field can not be empty!")
+ .label("User Id")
+ .typeError("The user id must be a valid number")
+ .test(
+ "doesUserBelongToOrganization",
+ "The user must belong to the Organization and have the appropriate access (organization admin or event manager) or be a superAdmin",
+ async function test(userId) {
+ const { orgId } = this.parent;
+
+ const isUserSuperAdmin = await selectSuperAdmin({
+ userId
+ });
+ if (isUserSuperAdmin) {
+ return true;
+ }
+
+ const user = await selectOrganizationUser({
+ orgId,
+ userId
+ });
+ if (user) {
+ return true;
+ }
+
+ return false;
+ }
+ )
+});
+
+module.exports = patchOrganizationInvitationSchema;
diff --git a/api-sample/src/app/controllers/invitations/patchOrganizationInvitation/schemas/queries/selectInvitation.js b/api-sample/src/app/controllers/invitations/patchOrganizationInvitation/schemas/queries/selectInvitation.js
new file mode 100644
index 00000000..d97ac968
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/patchOrganizationInvitation/schemas/queries/selectInvitation.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectInvitation = ({ invitationShortcode }) => submitQuery`
+ SELECT
+ user_organization_invitation_id
+ FROM user_organization_invitations
+ WHERE invitation_shortcode = ${invitationShortcode};
+`;
+
+module.exports = getFirst(selectInvitation, "user_organization_invitation_id");
diff --git a/api-sample/src/app/controllers/invitations/patchOrganizationInvitation/schemas/queries/selectUserOrganizationByEmail.js b/api-sample/src/app/controllers/invitations/patchOrganizationInvitation/schemas/queries/selectUserOrganizationByEmail.js
new file mode 100644
index 00000000..cd6fe333
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/patchOrganizationInvitation/schemas/queries/selectUserOrganizationByEmail.js
@@ -0,0 +1,14 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUserOrganizationByEmail = ({ email, orgId }) => submitQuery`
+ SELECT
+ user_organizations.user_organization_id
+ FROM user_organizations
+ LEFT JOIN users ON user_organizations.user_id = users.user_id
+ WHERE users.email = ${email} && user_organizations.organization_id = ${orgId}
+`;
+
+module.exports = getFirst(
+ selectUserOrganizationByEmail,
+ "user_organization_id"
+);
diff --git a/api-sample/src/app/controllers/invitations/postOrganizationInvitation/index.js b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/index.js
new file mode 100644
index 00000000..64132f96
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/index.js
@@ -0,0 +1,94 @@
+const { v4: uuidv4 } = require("uuid");
+const handleAPIError = require("~root/utils/handleAPIError");
+const createOrganizationInvitation = require("~root/actions/users/createOrganizationInvitation");
+const sendEmail = require("~root/services/emails/sendEmail");
+const postOrganizationInvitationSchema = require("./schemas/postOrganizationInvitationSchema");
+const selectUserFullNameById = require("./queries/selectUserFullNameById");
+const selectOrganizationNameById = require("./queries/selectOrganizationNameById");
+const selectUserOrganizationRoleNameById = require("./queries/selectUserOrganizationRoleNameById");
+const selectUserByEmail = require("./queries/selectUserByEmail");
+
+const postOrganizationInvitation = async (req, res) => {
+ const { userId } = req.user;
+ const { email, userOrganizationRoleId, comment, orgId } = req.body;
+ const invitationShortcode = uuidv4();
+
+ try {
+ await postOrganizationInvitationSchema.validate(
+ {
+ userId,
+ orgId,
+ email,
+ userOrganizationRoleId,
+ comment
+ },
+ {
+ abortEarly: false
+ }
+ );
+
+ const { firstName, lastName } = await selectUserFullNameById({ userId });
+ const { organizationName } = await selectOrganizationNameById({ orgId });
+ const { userOrganizationRole } = await selectUserOrganizationRoleNameById({
+ userOrganizationRoleId
+ });
+ const { user } = await selectUserByEmail({ email });
+
+ let newRole;
+ if (userOrganizationRole === "OrganizationAdmin") {
+ newRole = "Organization Admin";
+ }
+ if (userOrganizationRole === "Member") {
+ newRole = "Member";
+ }
+
+ const { newInvitationId } = await createOrganizationInvitation({
+ userId,
+ orgId,
+ email,
+ userOrganizationRoleId,
+ comment,
+ invitationShortcode
+ });
+
+ if (user) {
+ const emailPayload = {
+ to: email,
+ template: "user-organization-invitation",
+ version: "0.0.1",
+ metadata: {
+ firstName,
+ lastName,
+ userOrganizationRole: newRole,
+ organizationName,
+ senderMessage: comment,
+ notificationsPageUrl: `${process.env.APP_BASE_URL}/login`
+ }
+ };
+ await sendEmail(emailPayload);
+ } else {
+ const emailPayload = {
+ to: email,
+ template: "new-user-organization-invitation",
+ version: "0.0.1",
+ metadata: {
+ firstName,
+ lastName,
+ userOrganizationRole: newRole,
+ organizationName,
+ senderMessage: comment,
+ notificationsPageUrl: `${process.env.APP_BASE_URL}/register/${invitationShortcode}`
+ }
+ };
+ await sendEmail(emailPayload);
+ }
+
+ res.send({
+ newInvitationId
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = postOrganizationInvitation;
diff --git a/api-sample/src/app/controllers/invitations/postOrganizationInvitation/queries/selectOrganizationNameById.js b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/queries/selectOrganizationNameById.js
new file mode 100644
index 00000000..b7a1eeeb
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/queries/selectOrganizationNameById.js
@@ -0,0 +1,12 @@
+const { submitQuery, getFirst, camelKeys } = require("~root/lib/database");
+
+const selectUserOrganizationByEmail = ({ orgId }) => submitQuery`
+ SELECT
+ organization_name
+ FROM organizations
+ WHERE organization_id = ${orgId}
+`;
+
+module.exports = getFirst(
+ camelKeys(selectUserOrganizationByEmail, "organization_name")
+);
diff --git a/api-sample/src/app/controllers/invitations/postOrganizationInvitation/queries/selectUserByEmail.js b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/queries/selectUserByEmail.js
new file mode 100644
index 00000000..50c9579a
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/queries/selectUserByEmail.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUserByEmail = ({ email }) => submitQuery`
+ SELECT
+ email
+ FROM users
+ WHERE email = ${email};
+`;
+
+module.exports = getFirst(selectUserByEmail, "email");
diff --git a/api-sample/src/app/controllers/invitations/postOrganizationInvitation/queries/selectUserFullNameById.js b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/queries/selectUserFullNameById.js
new file mode 100644
index 00000000..b9a3106a
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/queries/selectUserFullNameById.js
@@ -0,0 +1,11 @@
+const { submitQuery, getFirst, camelKeys } = require("~root/lib/database");
+
+const selectUserFullNameById = ({ userId }) => submitQuery`
+ SELECT
+ first_name,
+ last_name
+ FROM users
+ WHERE user_id = ${userId}
+`;
+
+module.exports = getFirst(camelKeys(selectUserFullNameById));
diff --git a/api-sample/src/app/controllers/invitations/postOrganizationInvitation/queries/selectUserOrganizationRoleNameById.js b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/queries/selectUserOrganizationRoleNameById.js
new file mode 100644
index 00000000..76e2b85c
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/queries/selectUserOrganizationRoleNameById.js
@@ -0,0 +1,12 @@
+const { submitQuery, getFirst, camelKeys } = require("~root/lib/database");
+
+const selectUserOrganizationRoleNameById = ({
+ userOrganizationRoleId
+}) => submitQuery`
+ SELECT
+ user_organization_role
+ FROM user_organization_roles
+ WHERE user_organization_role_id = ${userOrganizationRoleId}
+`;
+
+module.exports = getFirst(camelKeys(selectUserOrganizationRoleNameById));
diff --git a/api-sample/src/app/controllers/invitations/postOrganizationInvitation/schemas/postOrganizationInvitationSchema.js b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/schemas/postOrganizationInvitationSchema.js
new file mode 100644
index 00000000..6fcd6383
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/schemas/postOrganizationInvitationSchema.js
@@ -0,0 +1,101 @@
+const yup = require("yup");
+const selectSuperAdmin = require("~root/actions/schemaHelpers/queries/selectSuperAdmin/selectSuperAdmin");
+const selectOrganizationUser = require("~root/actions/schemaHelpers/queries/selectOrganizationUser/selectOrganizationUser");
+const selectUserOrganizationByEmail = require("./queries/selectUserOrganizationByEmail");
+const selectUserRole = require("./queries/selectUserRole");
+const selectInvitation = require("./queries/selectInvitation");
+
+const postOrganizationInvitationSchema = yup.object().shape({
+ comment: yup
+ .string()
+ .label("comment")
+ .typeError("Invalid comment")
+ .max(500),
+
+ email: yup
+ .string()
+ .email()
+ .required()
+ .label("Email")
+ .typeError("Email is invalid.")
+ .test(
+ "doesEmailExist",
+ "User account already exists or Invitation already exist",
+ async function test(email) {
+ const { orgId, userOrganizationRoleId } = this.parent;
+
+ const userOrganization = await selectUserOrganizationByEmail({
+ email,
+ orgId,
+ userOrganizationRoleId
+ });
+ if (userOrganization) {
+ return false;
+ }
+
+ const invitation = await selectInvitation({
+ email,
+ orgId,
+ userOrganizationRoleId
+ });
+
+ if (invitation) {
+ return false;
+ }
+ return true;
+ }
+ ),
+
+ userOrganizationRoleId: yup
+ .number()
+ .positive()
+ .required()
+ .label("user role id")
+ .typeError("the user role id must be a valid number")
+ .test(
+ "doesUserRoleIdExist",
+ "User role Id does not exists.",
+ async function test(userOrganizationRoleId) {
+ const userRole = await selectUserRole({ userOrganizationRoleId });
+
+ if (userRole) {
+ return true;
+ }
+ return false;
+ }
+ ),
+ orgId: yup
+ .number()
+ .required()
+ .label("Organization ID")
+ .typeError("Organization Id must be a number"),
+ userId: yup
+ .number()
+ .positive()
+ .min(1, "This field can not be empty!")
+ .label("User Id")
+ .typeError("The user id must be a valid number")
+ .test(
+ "doesUserBelongToOrganization",
+ "The user must belong to the Organization and have the appropriate access (organization admin or event manager) or be a superAdmin",
+ async function test(userId) {
+ const { orgId } = this.parent;
+ const isUserSuperAdmin = await selectSuperAdmin({
+ userId
+ });
+ if (isUserSuperAdmin) {
+ return true;
+ }
+ const user = await selectOrganizationUser({
+ orgId,
+ userId
+ });
+ if (user) {
+ return true;
+ }
+ return false;
+ }
+ )
+});
+
+module.exports = postOrganizationInvitationSchema;
diff --git a/api-sample/src/app/controllers/invitations/postOrganizationInvitation/schemas/queries/selectInvitation.js b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/schemas/queries/selectInvitation.js
new file mode 100644
index 00000000..119872ee
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/schemas/queries/selectInvitation.js
@@ -0,0 +1,14 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectInvitation = ({
+ email,
+ orgId,
+ userOrganizationRoleId
+}) => submitQuery`
+ SELECT
+ user_organization_invitation_id
+ FROM user_organization_invitations
+ WHERE email = ${email} && organization_id = ${orgId} && user_organization_role_id = ${userOrganizationRoleId}
+`;
+
+module.exports = getFirst(selectInvitation, "user_organization_invitation_id");
diff --git a/api-sample/src/app/controllers/invitations/postOrganizationInvitation/schemas/queries/selectUserOrganizationByEmail.js b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/schemas/queries/selectUserOrganizationByEmail.js
new file mode 100644
index 00000000..f04463d9
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/schemas/queries/selectUserOrganizationByEmail.js
@@ -0,0 +1,18 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUserOrganizationByEmail = ({
+ email,
+ orgId,
+ userOrganizationRoleId
+}) => submitQuery`
+ SELECT
+ user_organizations.user_organization_id
+ FROM user_organizations
+ LEFT JOIN users ON users.user_id = user_organizations.user_id
+ WHERE users.email = ${email} && user_organizations.organization_id = ${orgId} && user_organizations.user_organization_role_id = ${userOrganizationRoleId}
+`;
+
+module.exports = getFirst(
+ selectUserOrganizationByEmail,
+ "user_organization_id"
+);
diff --git a/api-sample/src/app/controllers/invitations/postOrganizationInvitation/schemas/queries/selectUserRole.js b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/schemas/queries/selectUserRole.js
new file mode 100644
index 00000000..cc9439e1
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/postOrganizationInvitation/schemas/queries/selectUserRole.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUserRole = ({ userOrganizationRoleId }) => submitQuery`
+ SELECT
+ user_organization_role
+ FROM user_organization_roles
+ WHERE user_organization_role_id = ${userOrganizationRoleId}
+`;
+
+module.exports = getFirst(selectUserRole, "user_organization_role");
diff --git a/api-sample/src/app/controllers/invitations/postOrganizationInvitations/index.js b/api-sample/src/app/controllers/invitations/postOrganizationInvitations/index.js
new file mode 100644
index 00000000..6bb2e8b5
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/postOrganizationInvitations/index.js
@@ -0,0 +1,48 @@
+const { v4: uuidv4 } = require("uuid");
+const handleAPIError = require("~root/utils/handleAPIError");
+const createOrganizationInvitation = require("~root/actions/invitations/createOrganizationInvitation");
+const asyncParallel = require("~root/utils/asyncParallel");
+const postOrganizationInvitationsSchema = require("./schemas/postOrganizationInvitationsSchema");
+
+const postOrganizationInvitations = async (req, res) => {
+ const { userId } = req.user;
+ const { orgId } = req.params;
+ const { allInvitedUsers } = req.body;
+ const invitationShortcode = uuidv4();
+
+ try {
+ const invitedUsers = [];
+ await asyncParallel(allInvitedUsers, async newUser => {
+ const { email, userOrganizationRoleId } = newUser;
+
+ await postOrganizationInvitationsSchema.validate(
+ {
+ userId,
+ orgId,
+ email,
+ userOrganizationRoleId
+ },
+ {
+ abortEarly: false
+ }
+ );
+
+ const { newInvitationId } = await createOrganizationInvitation({
+ userId,
+ orgId,
+ email,
+ userOrganizationRoleId,
+ invitationShortcode
+ });
+ invitedUsers.push(newInvitationId);
+ });
+
+ res.send({
+ invitedUsers
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = postOrganizationInvitations;
diff --git a/api-sample/src/app/controllers/invitations/postOrganizationInvitations/schemas/postOrganizationInvitationsSchema.js b/api-sample/src/app/controllers/invitations/postOrganizationInvitations/schemas/postOrganizationInvitationsSchema.js
new file mode 100644
index 00000000..3ba2de7e
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/postOrganizationInvitations/schemas/postOrganizationInvitationsSchema.js
@@ -0,0 +1,87 @@
+const yup = require("yup");
+const selectSuperAdmin = require("~root/actions/schemaHelpers/queries/selectSuperAdmin/selectSuperAdmin");
+const selectOrganizationUser = require("~root/actions/schemaHelpers/queries/selectOrganizationUser/selectOrganizationUser");
+const selectUserOrganizationByEmail = require("./queries/selectUserOrganizationByEmail");
+const selectUserRole = require("./queries/selectUserRole");
+
+const postOrganizationInvitationsSchema = yup.object().shape({
+ email: yup
+ .string()
+ .email()
+ .required()
+ .label("Email")
+ .typeError("Email is invalid.")
+ .test("doesEmailExist", "User account already exists.", async function test(
+ email
+ ) {
+ const { orgId } = this.parent;
+
+ const userOrganization = await selectUserOrganizationByEmail({
+ email,
+ orgId
+ });
+
+ if (userOrganization) {
+ return false;
+ }
+
+ return true;
+ }),
+
+ userOrganizationRoleId: yup
+ .number()
+ .positive()
+ .required()
+ .label("user role id")
+ .typeError("the user role id must be a valid number")
+ .test(
+ "doesUserRoleIdExist",
+ "User role Id does not exists.",
+ async function test(userOrganizationRoleId) {
+ const userRole = await selectUserRole({ userOrganizationRoleId });
+ if (userRole) {
+ return true;
+ }
+ return false;
+ }
+ ),
+
+ orgId: yup
+ .number()
+ .required()
+ .label("Organization ID")
+ .typeError("Organization Id must be a number"),
+
+ userId: yup
+ .number()
+ .positive()
+ .min(1, "This field can not be empty!")
+ .label("User Id")
+ .typeError("The user id must be a valid number")
+ .test(
+ "doesUserBelongToOrganization",
+ "The user must belong to the Organization and have the appropriate access (organization admin or event manager) or be a superAdmin",
+ async function test(userId) {
+ const { orgId } = this.parent;
+
+ const isUserSuperAdmin = await selectSuperAdmin({
+ userId
+ });
+ if (isUserSuperAdmin) {
+ return true;
+ }
+
+ const user = await selectOrganizationUser({
+ orgId,
+ userId
+ });
+ if (user) {
+ return true;
+ }
+
+ return false;
+ }
+ )
+});
+
+module.exports = postOrganizationInvitationsSchema;
diff --git a/api-sample/src/app/controllers/invitations/postOrganizationInvitations/schemas/queries/selectUserOrganizationByEmail.js b/api-sample/src/app/controllers/invitations/postOrganizationInvitations/schemas/queries/selectUserOrganizationByEmail.js
new file mode 100644
index 00000000..2fe76985
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/postOrganizationInvitations/schemas/queries/selectUserOrganizationByEmail.js
@@ -0,0 +1,14 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUserOrganizationByEmail = ({ email, orgId }) => submitQuery`
+ SELECT
+ user_organizations.user_organization_id
+ FROM user_organizations
+ LEFT JOIN users ON users.user_id = user_organizations.user_id
+ WHERE users.email = ${email} && user_organizations.organization_id = ${orgId}
+`;
+
+module.exports = getFirst(
+ selectUserOrganizationByEmail,
+ "user_organization_id"
+);
diff --git a/api-sample/src/app/controllers/invitations/postOrganizationInvitations/schemas/queries/selectUserRole.js b/api-sample/src/app/controllers/invitations/postOrganizationInvitations/schemas/queries/selectUserRole.js
new file mode 100644
index 00000000..cc9439e1
--- /dev/null
+++ b/api-sample/src/app/controllers/invitations/postOrganizationInvitations/schemas/queries/selectUserRole.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUserRole = ({ userOrganizationRoleId }) => submitQuery`
+ SELECT
+ user_organization_role
+ FROM user_organization_roles
+ WHERE user_organization_role_id = ${userOrganizationRoleId}
+`;
+
+module.exports = getFirst(selectUserRole, "user_organization_role");
diff --git a/api-sample/src/app/controllers/organizations/deleteOrganizationUser/index.js b/api-sample/src/app/controllers/organizations/deleteOrganizationUser/index.js
new file mode 100644
index 00000000..d24c72f8
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/deleteOrganizationUser/index.js
@@ -0,0 +1,32 @@
+const handleAPIError = require("~root/utils/handleAPIError");
+const removeOrganizationUser = require("~root/actions/organizations/removeOrganizationUser");
+const deleteOrganizationUserSchema = require("./schemas/deleteOrganizationUserSchema");
+
+const deleteOrganizationUser = async (req, res) => {
+ const { userId } = req.user;
+ const { orgId, orgUserId } = req.body;
+
+ try {
+ await deleteOrganizationUserSchema.validate(
+ {
+ orgId,
+ userId,
+ orgUserId
+ },
+ {
+ abortEarly: false
+ }
+ );
+
+ const { deletedOrgUser } = await removeOrganizationUser({
+ orgUserId,
+ orgId
+ });
+
+ res.status(204).send({ deletedOrgUser });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = deleteOrganizationUser;
diff --git a/api-sample/src/app/controllers/organizations/deleteOrganizationUser/schemas/deleteOrganizationUserSchema.js b/api-sample/src/app/controllers/organizations/deleteOrganizationUser/schemas/deleteOrganizationUserSchema.js
new file mode 100644
index 00000000..aac9b784
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/deleteOrganizationUser/schemas/deleteOrganizationUserSchema.js
@@ -0,0 +1,65 @@
+const yup = require("yup");
+const selectOrganizationUser = require("~root/actions/schemaHelpers/queries/selectOrganizationUser/selectOrganizationUser");
+const selectSuperAdmin = require("~root/actions/schemaHelpers/queries/selectSuperAdmin/selectSuperAdmin");
+const selectOrgUserById = require("./queries/selectOrgUserById");
+
+const deleteOrganizationUserSchema = yup.object().shape({
+ orgId: yup
+ .number()
+ .required()
+ .label("Organization ID")
+ .typeError("Organization Id must be a number"),
+
+ userId: yup
+ .number()
+ .positive()
+ .min(1, "This field can not be empty!")
+ .label("User Id")
+ .typeError("The user id must be a valid number")
+ .test(
+ "doesUserBelongToOrganization",
+ "The user must belong to the Organization and have the appropriate access (organization admin or event manager) or be a superAdmin",
+ async function test(userId) {
+ const { orgId } = this.parent;
+
+ const isUserSuperAdmin = await selectSuperAdmin({
+ userId
+ });
+ if (isUserSuperAdmin) {
+ return true;
+ }
+
+ const user = await selectOrganizationUser({
+ orgId,
+ userId
+ });
+ if (user) {
+ return true;
+ }
+
+ return false;
+ }
+ ),
+
+ orgUserId: yup
+ .number()
+ .positive()
+ .required()
+ .label("OrgUser to be deleted")
+ .typeError("the organization User Id must be a valid number")
+ .test(
+ "doesTheUserBelongToTheOrganization",
+ "check if the organization user exist and belong to the organization",
+ async function test(orgUserId) {
+ const { orgId } = this.parent;
+ const orgUser = await selectOrgUserById({ orgId, orgUserId });
+
+ if (orgUser) {
+ return true;
+ }
+ return false;
+ }
+ )
+});
+
+module.exports = deleteOrganizationUserSchema;
diff --git a/api-sample/src/app/controllers/organizations/deleteOrganizationUser/schemas/queries/selectOrgUserById.js b/api-sample/src/app/controllers/organizations/deleteOrganizationUser/schemas/queries/selectOrgUserById.js
new file mode 100644
index 00000000..ed086ca2
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/deleteOrganizationUser/schemas/queries/selectOrgUserById.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectOrgUserById = ({ orgId, orgUserId }) => submitQuery`
+ SELECT
+ user_organization_id
+ FROM user_organizations
+ WHERE user_id = ${orgUserId} && organization_id = ${orgId}
+`;
+
+module.exports = getFirst(selectOrgUserById, "user_organization_id");
diff --git a/api-sample/src/app/controllers/organizations/getOrganizationInvitations/index.js b/api-sample/src/app/controllers/organizations/getOrganizationInvitations/index.js
new file mode 100644
index 00000000..f1d28d85
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/getOrganizationInvitations/index.js
@@ -0,0 +1,29 @@
+const fetchOrganizationInvitations = require("~root/actions/organizations/fetchOrganizationInvitations");
+const handleAPIError = require("~root/utils/handleAPIError");
+const getOrganizationInvitationsSchema = require("./schemas/getOrganizationInvitationsSchema");
+
+const getOrganizationInvitations = async (req, res) => {
+ const { userId } = req.user;
+ const { orgId } = req.params;
+
+ try {
+ await getOrganizationInvitationsSchema.validate(
+ {
+ orgId,
+ userId
+ },
+ {
+ abortEarly: false
+ }
+ );
+ const { invitations } = await fetchOrganizationInvitations({ orgId });
+
+ res.status(201).send({
+ invitations
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = getOrganizationInvitations;
diff --git a/api-sample/src/app/controllers/organizations/getOrganizationInvitations/schemas/getOrganizationInvitationsSchema.js b/api-sample/src/app/controllers/organizations/getOrganizationInvitations/schemas/getOrganizationInvitationsSchema.js
new file mode 100644
index 00000000..debdfd42
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/getOrganizationInvitations/schemas/getOrganizationInvitationsSchema.js
@@ -0,0 +1,39 @@
+const yup = require("yup");
+const selectUserByOrgId = require("~root/actions/schemaHelpers/queries/selectUserById/selectUserByOrgId");
+const selectUserById = require("~root/actions/schemaHelpers/queries/selectUserById/selectUserById");
+
+const getOrganizationInvitationsSchema = yup.object().shape({
+ orgId: yup
+ .number()
+ .positive()
+ .label("OrganizationId")
+ .typeError("Organization Id must be valid."),
+ userId: yup
+ .number()
+ .positive()
+ .label("userId")
+ .typeError("The userId must be a number")
+ .test(
+ "UserMustExistAndBelongToThatOrganization",
+ "The user does not exist or does not belong to that organization",
+ async function test(userId) {
+ const { orgId } = this.parent;
+
+ const user = await selectUserById({ userId });
+
+ if (!user) {
+ return false;
+ }
+
+ const orgUser = await selectUserByOrgId({ userId, orgId });
+
+ if (!orgUser) {
+ return false;
+ }
+
+ return true;
+ }
+ )
+});
+
+module.exports = getOrganizationInvitationsSchema;
diff --git a/api-sample/src/app/controllers/organizations/getOrganizationUsers/index.js b/api-sample/src/app/controllers/organizations/getOrganizationUsers/index.js
new file mode 100644
index 00000000..8e350c98
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/getOrganizationUsers/index.js
@@ -0,0 +1,28 @@
+const handleAPIError = require("~root/utils/handleAPIError");
+const fetchOrganizationUsers = require("~root/actions/organizations/fetchOrganizationUsers");
+const getOrganizationUsersSchema = require("./schemas/getOrganizationUsersSchema");
+
+const getOrganizationUsers = async (req, res) => {
+ const { userId } = req.user;
+ const { orgId } = req.params;
+ try {
+ await getOrganizationUsersSchema.validate(
+ {
+ orgId,
+ userId
+ },
+ {
+ abortEarly: false
+ }
+ );
+ const { organizationUsers } = await fetchOrganizationUsers({ orgId });
+
+ res.status(200).send({
+ organizationUsers
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = getOrganizationUsers;
diff --git a/api-sample/src/app/controllers/organizations/getOrganizationUsers/schemas/getOrganizationUsersSchema.js b/api-sample/src/app/controllers/organizations/getOrganizationUsers/schemas/getOrganizationUsersSchema.js
new file mode 100644
index 00000000..d67689eb
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/getOrganizationUsers/schemas/getOrganizationUsersSchema.js
@@ -0,0 +1,35 @@
+const yup = require("yup");
+const selectUserByOrgId = require("~root/actions/schemaHelpers/queries/selectUserById/selectUserByOrgId");
+const selectUserById = require("~root/actions/users/fetchUserById/queries/selectUserById");
+
+const getOrganizationUsersSchema = yup.object().shape({
+ orgId: yup
+ .number()
+ .positive()
+ .label("OrganizationId")
+ .typeError("Organization Id must be valid."),
+
+ userId: yup
+ .number()
+ .positive()
+ .label("userId")
+ .typeError("The userId must be a number")
+ .test(
+ "UserMustExistAndBelongToThatOrganization",
+ "The user does not exist or does not belong to that organization",
+ async function test(userId) {
+ const { orgId } = this.parent;
+ const user = await selectUserById({ userId });
+ if (!user) {
+ return false;
+ }
+ const orgUser = await selectUserByOrgId({ userId, orgId });
+ if (!orgUser) {
+ return false;
+ }
+ return true;
+ }
+ )
+});
+
+module.exports = getOrganizationUsersSchema;
diff --git a/api-sample/src/app/controllers/organizations/getOrganizationsByEmail/index.js b/api-sample/src/app/controllers/organizations/getOrganizationsByEmail/index.js
new file mode 100644
index 00000000..bca31b92
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/getOrganizationsByEmail/index.js
@@ -0,0 +1,26 @@
+const fetchOrganizationsByEmail = require("~root/actions/organizations/fetchOrganizationsByEmail");
+const handleAPIError = require("~root/utils/handleAPIError");
+const getOrgOverviewSchema = require("./schemas/getOrganizationsByEmailSchema");
+
+const getOrganizationsByEmail = async (req, res) => {
+ const { email } = req.params;
+
+ try {
+ await getOrgOverviewSchema.validate(
+ {
+ email
+ },
+ {
+ abortEarly: false
+ }
+ );
+ const { organizations } = await fetchOrganizationsByEmail({ email });
+ res.status(200).send({
+ organizations
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = getOrganizationsByEmail;
diff --git a/api-sample/src/app/controllers/organizations/getOrganizationsByEmail/schemas/getOrganizationsByEmailSchema.js b/api-sample/src/app/controllers/organizations/getOrganizationsByEmail/schemas/getOrganizationsByEmailSchema.js
new file mode 100644
index 00000000..43d6e5ae
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/getOrganizationsByEmail/schemas/getOrganizationsByEmailSchema.js
@@ -0,0 +1,26 @@
+const yup = require("yup");
+const selectUserByEmail = require("./queries/selectUserByEmail");
+
+const getOrganizationsByEmailSchema = yup.object().shape({
+ email: yup
+ .string()
+ .email()
+ .label("email")
+ .typeError("Not a valid Email")
+ .test(
+ "DoesEmailExist",
+ "The Email address does not exist for any user",
+ async function test(email) {
+ const user = await selectUserByEmail({
+ email
+ });
+ if (user) {
+ return true;
+ }
+
+ return false;
+ }
+ )
+});
+
+module.exports = getOrganizationsByEmailSchema;
diff --git a/api-sample/src/app/controllers/organizations/getOrganizationsByEmail/schemas/queries/selectUserByEmail.js b/api-sample/src/app/controllers/organizations/getOrganizationsByEmail/schemas/queries/selectUserByEmail.js
new file mode 100644
index 00000000..9264495f
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/getOrganizationsByEmail/schemas/queries/selectUserByEmail.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUserByEmail = ({ email }) => submitQuery`
+ SELECT
+ user_id
+ FROM users
+ WHERE email = ${email}
+`;
+
+module.exports = getFirst(selectUserByEmail, "user_id");
diff --git a/api-sample/src/app/controllers/organizations/getOrganizationsByUserId/index.js b/api-sample/src/app/controllers/organizations/getOrganizationsByUserId/index.js
new file mode 100644
index 00000000..e50e7f08
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/getOrganizationsByUserId/index.js
@@ -0,0 +1,25 @@
+const fetchOrganizationsByUserId = require("~root/actions/organizations/fetchOrganizationsByUserId");
+const handleAPIError = require("~root/utils/handleAPIError");
+const getOrganizationsByUserIdSchema = require("./schemas/getOrganizationsByUserIdSchema");
+
+const getOrganizationsByUserId = async (req, res) => {
+ const { userId } = req.user;
+ try {
+ await getOrganizationsByUserIdSchema.validate(
+ {
+ userId
+ },
+ {
+ abortEarly: false
+ }
+ );
+ const { organizations } = await fetchOrganizationsByUserId({ userId });
+ res.status(200).send({
+ organizations
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = getOrganizationsByUserId;
diff --git a/api-sample/src/app/controllers/organizations/getOrganizationsByUserId/schemas/getOrganizationsByUserIdSchema.js b/api-sample/src/app/controllers/organizations/getOrganizationsByUserId/schemas/getOrganizationsByUserIdSchema.js
new file mode 100644
index 00000000..ce60cabd
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/getOrganizationsByUserId/schemas/getOrganizationsByUserIdSchema.js
@@ -0,0 +1,23 @@
+const yup = require("yup");
+const selectUser = require("./queries/selectUser");
+
+const getOrganizationsByUserIdSchema = yup.object().shape({
+ userId: yup
+ .number()
+ .positive()
+ .min(1, "This field can not be empty!")
+ .label("User Id")
+ .typeError("The user id must be a valid number")
+ .test("doesUserExist", "The user must exist", async function test(userId) {
+ const user = await selectUser({
+ userId
+ });
+ if (user) {
+ return true;
+ }
+
+ return false;
+ })
+});
+
+module.exports = getOrganizationsByUserIdSchema;
diff --git a/api-sample/src/app/controllers/organizations/getOrganizationsByUserId/schemas/queries/selectUser.js b/api-sample/src/app/controllers/organizations/getOrganizationsByUserId/schemas/queries/selectUser.js
new file mode 100644
index 00000000..fd48bc18
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/getOrganizationsByUserId/schemas/queries/selectUser.js
@@ -0,0 +1,13 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUser = ({ userId }) => submitQuery`
+ SELECT
+ user_id,
+ first_name,
+ last_name,
+ email
+ FROM users
+ WHERE user_id = ${userId}
+`;
+
+module.exports = getFirst(selectUser);
diff --git a/api-sample/src/app/controllers/organizations/patchOrganizationUser/index.js b/api-sample/src/app/controllers/organizations/patchOrganizationUser/index.js
new file mode 100644
index 00000000..7c9a0ada
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/patchOrganizationUser/index.js
@@ -0,0 +1,36 @@
+const modifyOrganizationUser = require("~root/actions/organizations/modifyOrganizationUser");
+const handleAPIError = require("~root/utils/handleAPIError");
+const patchOrganizationUserSchema = require("./schemas/patchOrganizationUserSchema");
+
+const patchOrganizationUser = async (req, res) => {
+ const { userId } = req.user;
+ const { orgId, userId: orgUserId } = req.params;
+ const { userRoleId, enabled } = req.body;
+
+ try {
+ await patchOrganizationUserSchema.validate(
+ {
+ userId,
+ orgId,
+ orgUserId,
+ userRoleId,
+ enabled
+ },
+ {
+ abortEarly: false
+ }
+ );
+ const { updatedorgUserId } = await modifyOrganizationUser({
+ userId,
+ orgId,
+ orgUserId,
+ userRoleId,
+ enabled
+ });
+ res.status(201).send({ updatedorgUserId });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = patchOrganizationUser;
diff --git a/api-sample/src/app/controllers/organizations/patchOrganizationUser/schemas/patchOrganizationUserSchema.js b/api-sample/src/app/controllers/organizations/patchOrganizationUser/schemas/patchOrganizationUserSchema.js
new file mode 100644
index 00000000..f031cf4d
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/patchOrganizationUser/schemas/patchOrganizationUserSchema.js
@@ -0,0 +1,89 @@
+const yup = require("yup");
+const selectSuperAdmin = require("~root/actions/schemaHelpers/queries/selectSuperAdmin/selectSuperAdmin");
+const selectOrganizationUser = require("~root/actions/schemaHelpers/queries/selectOrganizationUser/selectOrganizationUser");
+const selectOrgUserByUserIdAndOrgId = require("./queries/selectOrgUserByUserIdAndOrgId");
+const selectUserRole = require("./queries/selectUserRole");
+
+const patchOrganizationUserSchema = yup.object().shape({
+ orgId: yup
+ .number()
+ .required()
+ .label("Organization ID")
+ .typeError("Organization Id must be a number"),
+
+ userId: yup
+ .number()
+ .positive()
+ .min(1, "This field can not be empty!")
+ .label("User Id")
+ .typeError("The user id must be a valid number")
+ .test(
+ "doesUserBelongToOrganization",
+ "The user must belong to the Organization and have the appropriate access (organization admin or event manager) or be a superAdmin",
+ async function test(userId) {
+ const { orgId } = this.parent;
+
+ const isUserSuperAdmin = await selectSuperAdmin({
+ userId
+ });
+ if (isUserSuperAdmin) {
+ return true;
+ }
+
+ const user = await selectOrganizationUser({
+ orgId,
+ userId
+ });
+ if (user) {
+ return true;
+ }
+
+ return false;
+ }
+ ),
+
+ orgUserId: yup
+ .number()
+ .positive()
+ .label("Org User Id")
+ .typeError("The orgUserId must be a valid number")
+ .test(
+ "orgUserIdMustExist",
+ "The orgUserId must exist in the user_organization table",
+ async function test(orgUserId) {
+ const { orgId } = this.parent;
+ const orgUser = await selectOrgUserByUserIdAndOrgId({
+ orgUserId,
+ orgId
+ });
+ if (orgUser) {
+ return true;
+ }
+ return false;
+ }
+ ),
+
+ userRoleId: yup
+ .number()
+ .positive()
+ .label("userRoleId")
+ .typeError("The eventRoleId must a valid number")
+ .test(
+ "DoesuserRoleIdExists",
+ "The userRoleId must exist in the user_roles table",
+ async function test(userRoleId) {
+ const userRole = await selectUserRole({ userRoleId });
+ if (userRole) {
+ return true;
+ }
+ return false;
+ }
+ ),
+
+ enabled: yup
+ .boolean()
+ .label("enabled")
+ .typeError("enabled must be a boolean")
+});
+
+module.exports = patchOrganizationUserSchema;
diff --git a/api-sample/src/app/controllers/organizations/patchOrganizationUser/schemas/queries/selectEventByOrgId.js b/api-sample/src/app/controllers/organizations/patchOrganizationUser/schemas/queries/selectEventByOrgId.js
new file mode 100644
index 00000000..dff9c45c
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/patchOrganizationUser/schemas/queries/selectEventByOrgId.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectEventByOrgId = ({ eventId, orgId }) => submitQuery`
+ SELECT
+ event_id
+ FROM events
+ WHERE event_id = ${eventId} && organization_id = ${orgId}
+`;
+
+module.exports = getFirst(selectEventByOrgId, "event_id");
diff --git a/api-sample/src/app/controllers/organizations/patchOrganizationUser/schemas/queries/selectOrgUserByUserIdAndOrgId.js b/api-sample/src/app/controllers/organizations/patchOrganizationUser/schemas/queries/selectOrgUserByUserIdAndOrgId.js
new file mode 100644
index 00000000..3fb63034
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/patchOrganizationUser/schemas/queries/selectOrgUserByUserIdAndOrgId.js
@@ -0,0 +1,13 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectOrgUserByUserIdAndOrgId = ({ orgUserId, orgId }) => submitQuery`
+ SELECT
+ user_organization_id
+ FROM user_organizations
+ WHERE user_id = ${orgUserId} && organization_id = ${orgId}
+`;
+
+module.exports = getFirst(
+ selectOrgUserByUserIdAndOrgId,
+ "user_organization_id"
+);
diff --git a/api-sample/src/app/controllers/organizations/patchOrganizationUser/schemas/queries/selectUserRole.js b/api-sample/src/app/controllers/organizations/patchOrganizationUser/schemas/queries/selectUserRole.js
new file mode 100644
index 00000000..1251374a
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/patchOrganizationUser/schemas/queries/selectUserRole.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUserRole = ({ userRoleId }) => submitQuery`
+ SELECT
+ user_role
+ FROM user_roles
+ WHERE user_role_id = ${userRoleId}
+`;
+
+module.exports = getFirst(selectUserRole, "user_role");
diff --git a/api-sample/src/app/controllers/organizations/postNewOrganization/index.js b/api-sample/src/app/controllers/organizations/postNewOrganization/index.js
new file mode 100644
index 00000000..10d9937e
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/postNewOrganization/index.js
@@ -0,0 +1,27 @@
+const createNewOrganization = require("~root/actions/organizations/createNewOrganization");
+const handleAPIError = require("~root/utils/handleAPIError");
+const postOrganizationSchema = require("./schemas/postOrganizationSchema");
+
+const postNewOrganization = async (req, res) => {
+ const { userId } = req.user;
+ const { organizationName } = req.body;
+ try {
+ await postOrganizationSchema.validate(
+ {
+ userId,
+ organizationName
+ },
+ {
+ abortEarly: false
+ }
+ );
+ const { organizationId } = await createNewOrganization({
+ organizationName
+ });
+ res.status(201).send({ organizationId });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = postNewOrganization;
diff --git a/api-sample/src/app/controllers/organizations/postNewOrganization/schemas/postOrganizationSchema.js b/api-sample/src/app/controllers/organizations/postNewOrganization/schemas/postOrganizationSchema.js
new file mode 100644
index 00000000..36f59dd9
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/postNewOrganization/schemas/postOrganizationSchema.js
@@ -0,0 +1,19 @@
+const yup = require("yup");
+
+const postOrganizationSchema = yup.object().shape({
+ organizationName: yup
+ .string()
+ .min(1, "This field must not be empty")
+ .max(100, "The number of characters should not be more than 100")
+ .label("organization name")
+ .typeError("The organization name must be a valid string"),
+
+ userId: yup
+ .number()
+ .positive()
+ .required()
+ .label("User Id")
+ .typeError("The user id must be a valid number")
+});
+
+module.exports = postOrganizationSchema;
diff --git a/api-sample/src/app/controllers/organizations/postNewOrganization/schemas/queries/selectOrganizationByName.js b/api-sample/src/app/controllers/organizations/postNewOrganization/schemas/queries/selectOrganizationByName.js
new file mode 100644
index 00000000..05572123
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/postNewOrganization/schemas/queries/selectOrganizationByName.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectOrganizationByName = ({ organizationName }) => submitQuery`
+ SELECT
+ organization_id
+ FROM organizations
+ WHERE organization_name = ${organizationName}
+`;
+
+module.exports = getFirst(selectOrganizationByName, "organization_id");
diff --git a/api-sample/src/app/controllers/organizations/postOrganization/index.js b/api-sample/src/app/controllers/organizations/postOrganization/index.js
new file mode 100644
index 00000000..25951ffd
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/postOrganization/index.js
@@ -0,0 +1,39 @@
+const handleAPIError = require("~root/utils/handleAPIError");
+const createOrganization = require("~root/actions/organizations/createOrganization");
+const createOrganizationUser = require("~root/actions/organizations/createOrganizationUser");
+const postOrganizationSchema = require("./schemas/postOrganizationSchema");
+
+const postOrganization = async (req, res) => {
+ const { userId } = req.user;
+ const { organizationName } = req.body;
+
+ try {
+ await postOrganizationSchema.validate(
+ {
+ userId,
+ organizationName
+ },
+ {
+ abortEarly: false
+ }
+ );
+
+ const { organizationId } = await createOrganization({ organizationName });
+
+ await createOrganizationUser({
+ userId,
+ userOrganizationRoleId: 1,
+ organizationId,
+ addedBy: userId
+ });
+
+ res.status(201).send({
+ organizationId,
+ organizationAdmin: userId
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = postOrganization;
diff --git a/api-sample/src/app/controllers/organizations/postOrganization/schemas/postOrganizationSchema.js b/api-sample/src/app/controllers/organizations/postOrganization/schemas/postOrganizationSchema.js
new file mode 100644
index 00000000..507937f2
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/postOrganization/schemas/postOrganizationSchema.js
@@ -0,0 +1,28 @@
+const yup = require("yup");
+const selectUser = require("./queries/selectuser");
+
+const postOrganizationSchema = yup.object().shape({
+ userId: yup
+ .number()
+ .required("The user Id is required")
+ .label("User Id")
+ .typeError("The User Id must be a valid number")
+ .test("DoesUserExist", "The User does not exist", async function test(
+ userId
+ ) {
+ const isUser = await selectUser({
+ userId
+ });
+ if (isUser) {
+ return true;
+ }
+
+ return false;
+ }),
+ organizationName: yup
+ .string()
+ .required("The Organization Name is a required field")
+ .label("Name")
+ .typeError("Name must be a string.")
+});
+module.exports = postOrganizationSchema;
diff --git a/api-sample/src/app/controllers/organizations/postOrganization/schemas/queries/selectuser.js b/api-sample/src/app/controllers/organizations/postOrganization/schemas/queries/selectuser.js
new file mode 100644
index 00000000..385174ec
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/postOrganization/schemas/queries/selectuser.js
@@ -0,0 +1,13 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUser = ({ userId }) => submitQuery`
+ SELECT
+ user_id,
+ first_name,
+ last_name,
+ email
+ FROM users
+ WHERE user_id = ${userId}
+`;
+
+module.exports = getFirst(selectUser, "email");
diff --git a/api-sample/src/app/controllers/organizations/postOrganizationUser/index.js b/api-sample/src/app/controllers/organizations/postOrganizationUser/index.js
new file mode 100644
index 00000000..2eb46454
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/postOrganizationUser/index.js
@@ -0,0 +1,40 @@
+const createOrganizationUser = require("~root/actions/organizations/createOrganizationUser");
+const fetchUserIdByEmail = require("~root/actions/users/fetchUserByEmail");
+const handleAPIError = require("~root/utils/handleAPIError");
+const postOrganizationUserSchema = require("./schemas/postOrganizationUserSchema");
+
+const postOrganizationUser = async (req, res) => {
+ const { userId } = req.user;
+ const { email, userRoleId, orgId } = req.body;
+
+ try {
+ const { newOrgUserUserId } = await fetchUserIdByEmail({ email });
+
+ await postOrganizationUserSchema.validate(
+ {
+ orgId,
+ userId,
+ userRoleId,
+ newOrgUserUserId
+ },
+ {
+ abortEarly: false
+ }
+ );
+
+ const { organizationUserId } = await createOrganizationUser({
+ userId: newOrgUserUserId,
+ userOrganizationRoleId: userRoleId,
+ organizationId: orgId,
+ addedBy: userId
+ });
+
+ res.send({
+ organizationUserId
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = postOrganizationUser;
diff --git a/api-sample/src/app/controllers/organizations/postOrganizationUser/schemas/postOrganizationUserSchema.js b/api-sample/src/app/controllers/organizations/postOrganizationUser/schemas/postOrganizationUserSchema.js
new file mode 100644
index 00000000..d5eddfb6
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/postOrganizationUser/schemas/postOrganizationUserSchema.js
@@ -0,0 +1,86 @@
+const yup = require("yup");
+const selectSuperAdmin = require("~root/actions/schemaHelpers/queries/selectSuperAdmin/selectSuperAdmin");
+const selectOrganizationUser = require("~root/actions/schemaHelpers/queries/selectOrganizationUser/selectOrganizationUser");
+const selectUserRoleIfInUserRoles = require("./queries/selectUserRoleIfInUserRoles");
+const selectOrgUserByUserId = require("./queries/selectOrgUserByUserId");
+
+const postOrganizationUserSchema = yup.object().shape({
+ newOrgUserUserId: yup
+ .number()
+ .positive()
+ .min(1, "This field can not be empty!")
+ .label("New organization user user_id")
+ .typeError("The new organization user user_id must be a valid number")
+ .test(
+ "doesOrganizationUserExist",
+ "User should exist and not belong to the given organization.",
+ async function test(newOrgUserUserId) {
+ const { orgId } = this.parent;
+
+ const user = await selectOrgUserByUserId({
+ newOrgUserUserId,
+ orgId
+ });
+ if (user) {
+ return false;
+ }
+
+ return true;
+ }
+ ),
+
+ userId: yup
+ .number()
+ .positive()
+ .min(1, "This field can not be empty!")
+ .label("User Id")
+ .typeError("The user id must be a valid number")
+ .test(
+ "doesUserBelongToOrganization",
+ "The user must belong to the Organization and have the appropriate access (organization admin or event manager) or be a superAdmin",
+ async function test(userId) {
+ const { orgId } = this.parent;
+
+ const isUserSuperAdmin = await selectSuperAdmin({
+ userId
+ });
+ if (isUserSuperAdmin) {
+ return true;
+ }
+
+ const user = await selectOrganizationUser({
+ orgId,
+ userId
+ });
+ if (user) {
+ return true;
+ }
+
+ return false;
+ }
+ ),
+ userRoleId: yup
+ .number()
+ .positive()
+ .min(1, "This field can noy be empty!")
+ .label("user role id")
+ .typeError("The user role id must be a valid number")
+ .test(
+ "UserRoleIdbelongToUserRoles",
+ "The user role id should be in the user roles table.",
+ async function test(userRoleId) {
+ const userRole = await selectUserRoleIfInUserRoles({ userRoleId });
+ if (userRole) {
+ return true;
+ }
+ return false;
+ }
+ ),
+ orgId: yup
+ .number()
+ .required()
+ .label("Organization ID")
+ .typeError("Organization Id must be a number")
+});
+
+module.exports = postOrganizationUserSchema;
diff --git a/api-sample/src/app/controllers/organizations/postOrganizationUser/schemas/queries/selectOrgUserByUserId.js b/api-sample/src/app/controllers/organizations/postOrganizationUser/schemas/queries/selectOrgUserByUserId.js
new file mode 100644
index 00000000..691cf5ac
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/postOrganizationUser/schemas/queries/selectOrgUserByUserId.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectOrgUserByUserId = ({ newOrgUserUserId, orgId }) => submitQuery`
+ SELECT
+ user_organization_id
+ FROM user_organizations
+ WHERE user_id = ${newOrgUserUserId} && organization_id = ${orgId}
+`;
+
+module.exports = getFirst(selectOrgUserByUserId, "user_organization_id");
diff --git a/api-sample/src/app/controllers/organizations/postOrganizationUser/schemas/queries/selectUserRoleIfInUserRoles.js b/api-sample/src/app/controllers/organizations/postOrganizationUser/schemas/queries/selectUserRoleIfInUserRoles.js
new file mode 100644
index 00000000..e1e2c802
--- /dev/null
+++ b/api-sample/src/app/controllers/organizations/postOrganizationUser/schemas/queries/selectUserRoleIfInUserRoles.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUserRoleIfInUserRoles = ({ userRoleId }) => submitQuery`
+ SELECT
+ user_role_id
+ FROM user_roles
+ WHERE user_role_id = ${userRoleId}
+`;
+
+module.exports = getFirst(selectUserRoleIfInUserRoles, "user_role_id");
diff --git a/api-sample/src/app/controllers/password-recovery/postRecoveryRequest/index.js b/api-sample/src/app/controllers/password-recovery/postRecoveryRequest/index.js
index b7b5ace9..2ad6eff7 100644
--- a/api-sample/src/app/controllers/password-recovery/postRecoveryRequest/index.js
+++ b/api-sample/src/app/controllers/password-recovery/postRecoveryRequest/index.js
@@ -1,8 +1,8 @@
const { v4: uuidv4 } = require("uuid");
const createRecoveryRequest = require("~root/actions/password-recovery/createRecoveryRequest");
const handleApiError = require("~root/utils/handleAPIError");
+const sendEmail = require("~root/services/emails/sendEmail");
const postRecoverRequestSchema = require("./schemas/postRecoveryRequestSchema");
-const sendEmail = require("~root/lib/services/emails/sendEmail");
const postRecoveryRequest = async (req, res) => {
const { requestedEmail } = req.body;
diff --git a/api-sample/src/app/controllers/userOrganizationInvitations/deleteUserOrganizationInvitations/index.js b/api-sample/src/app/controllers/userOrganizationInvitations/deleteUserOrganizationInvitations/index.js
new file mode 100644
index 00000000..665b1993
--- /dev/null
+++ b/api-sample/src/app/controllers/userOrganizationInvitations/deleteUserOrganizationInvitations/index.js
@@ -0,0 +1,24 @@
+const handleAPIError = require("~root/utils/handleAPIError");
+const removeUserOrganizationInvitations = require("~root/actions/userOrganizationInvitations/removeUserOrganizationInvitations");
+const newDeleteUserOrganizationInvitationsSchema = require("./schema/newDeleteUserOrganizationInvitationsSchema");
+
+const deleteUserOrganizationInvitations = async (req, res) => {
+ const { userOrganizationInvitationId, orgId } = req.params;
+
+ try {
+ await newDeleteUserOrganizationInvitationsSchema.validate(
+ { userOrganizationInvitationId },
+ { abortEarly: false }
+ );
+ await removeUserOrganizationInvitations({
+ userOrganizationInvitationId,
+ orgId
+ });
+
+ res.status(204).send({ success: true });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = deleteUserOrganizationInvitations;
diff --git a/api-sample/src/app/controllers/userOrganizationInvitations/deleteUserOrganizationInvitations/schema/newDeleteUserOrganizationInvitationsSchema.js b/api-sample/src/app/controllers/userOrganizationInvitations/deleteUserOrganizationInvitations/schema/newDeleteUserOrganizationInvitationsSchema.js
new file mode 100644
index 00000000..3bf64497
--- /dev/null
+++ b/api-sample/src/app/controllers/userOrganizationInvitations/deleteUserOrganizationInvitations/schema/newDeleteUserOrganizationInvitationsSchema.js
@@ -0,0 +1,30 @@
+const yup = require("yup");
+const selectUserOrganizationInvitationsById = require("./queries/selectUserOrganizationInvitationsById");
+
+const newDeleteUserOrganizationInvitationsSchema = yup.object().shape({
+ userOrganizationInvitationId: yup
+ .number()
+ .integer()
+ .required()
+ .label("userOrganizationInvitationId")
+ .typeError("The userOrganizationInvitationId field must be a number")
+ .test(
+ "doesExist",
+ "The userOrganizationInvitationId field must be a valid userOrganizationInvitationId",
+ async function test(userOrganizationInvitationId) {
+ if (!userOrganizationInvitationId) {
+ return false;
+ }
+
+ const userOrganizationInvitationIdCount = await selectUserOrganizationInvitationsById(
+ { userOrganizationInvitationId }
+ );
+ if (userOrganizationInvitationIdCount === 0) {
+ return false;
+ }
+ return true;
+ }
+ )
+});
+
+module.exports = newDeleteUserOrganizationInvitationsSchema;
diff --git a/api-sample/src/app/controllers/userOrganizationInvitations/deleteUserOrganizationInvitations/schema/queries/selectUserOrganizationInvitationsById.js b/api-sample/src/app/controllers/userOrganizationInvitations/deleteUserOrganizationInvitations/schema/queries/selectUserOrganizationInvitationsById.js
new file mode 100644
index 00000000..e403fc99
--- /dev/null
+++ b/api-sample/src/app/controllers/userOrganizationInvitations/deleteUserOrganizationInvitations/schema/queries/selectUserOrganizationInvitationsById.js
@@ -0,0 +1,14 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUserOrganizationInvitationsById = ({
+ userOrganizationInvitationId
+}) => submitQuery`
+ SELECT
+ COUNT(*) AS count
+ FROM
+ user_organization_invitations
+ WHERE
+ user_organization_invitation_id = ${userOrganizationInvitationId}
+`;
+
+module.exports = getFirst(selectUserOrganizationInvitationsById, "count");
diff --git a/api-sample/src/app/controllers/userOrganizationInvitations/getUserOrganizationInvitations/index.js b/api-sample/src/app/controllers/userOrganizationInvitations/getUserOrganizationInvitations/index.js
new file mode 100644
index 00000000..33835e0d
--- /dev/null
+++ b/api-sample/src/app/controllers/userOrganizationInvitations/getUserOrganizationInvitations/index.js
@@ -0,0 +1,68 @@
+const { sql } = require("~root/lib/database");
+const { paginate, FILTERS } = require("~root/lib/paginate");
+
+const handleAPIError = require("~root/utils/handleAPIError");
+const getUserOrganizationInvitationsSchema = require("./schema/getUserOrganizationInvitationsSchema");
+
+const getUserOrganizationInvitations = async (req, res) => {
+ const { userId } = req.user;
+ const { orgId } = req.params;
+ try {
+ await getUserOrganizationInvitationsSchema.validate(
+ { orgId, userId },
+ { abortEarly: false }
+ );
+ const resultset = await paginate({
+ basePath: req.path,
+ baseTable: "user_organization_invitations",
+ selectFields: [
+ "user_organization_invitations.user_organization_invitation_id",
+ "user_organization_invitations.accepted_at",
+ "user_organization_invitations.comment",
+ "user_organization_invitations.email",
+ "user_organization_invitations.invitation_shortcode",
+ "user_organization_invitations.invited_by",
+ "user_organization_invitations.organization_id",
+ "user_organization_invitations.sent_at",
+ "user_organization_invitations.updated_at",
+ "user_organization_invitations.user_role_id",
+ sql`users.first_name as invited_by_first_name`,
+ sql`users.last_name as invited_by_last_name`
+ ],
+ joinStatements: [
+ sql`LEFT JOIN users ON user_organization_invitations.invited_by = users.user_id`
+ ],
+ sortableAttributes: ["user_organization_invitations.sent_at"],
+ filterableAttributes: [
+ {
+ column:
+ "user_organization_invitations.user_organization_invitation_id",
+ operations: [FILTERS.equals]
+ },
+ {
+ column: "user_organization_invitations.user_role_id",
+ operations: [FILTERS.equals]
+ },
+ {
+ column: "user_organization_invitations.email",
+ operations: [FILTERS.containsIgnoreCase]
+ }
+ ],
+ mandatoryFilter: sql`AND user_organization_invitations.organization_id=${orgId}`,
+ groupBy: [
+ "user_organization_invitations.user_organization_invitation_id"
+ ],
+ sortBy: req.query.sort_by,
+ limit: req.query.page_size,
+ page: req.query.page, // "first" | "last" | null
+ direction: req.query.direction, // next | previous
+ filters: req.query.filters,
+ cursorValues: req.query.cursor
+ });
+ return res.send(resultset);
+ } catch (err) {
+ return handleAPIError(res, err);
+ }
+};
+
+module.exports = getUserOrganizationInvitations;
diff --git a/api-sample/src/app/controllers/userOrganizationInvitations/getUserOrganizationInvitations/schema/getUserOrganizationInvitationsSchema.js b/api-sample/src/app/controllers/userOrganizationInvitations/getUserOrganizationInvitations/schema/getUserOrganizationInvitationsSchema.js
new file mode 100644
index 00000000..fd878037
--- /dev/null
+++ b/api-sample/src/app/controllers/userOrganizationInvitations/getUserOrganizationInvitations/schema/getUserOrganizationInvitationsSchema.js
@@ -0,0 +1,48 @@
+const yup = require("yup");
+const selectEventUser = require("~root/actions/schemaHelpers/queries/selectEventUser/selectEventUser");
+const selectOrganizationUser = require("~root/actions/schemaHelpers/queries/selectOrganizationUser/selectOrganizationUser");
+const selectSuperAdmin = require("~root/actions/schemaHelpers/queries/selectSuperAdmin/selectSuperAdmin");
+
+const getUserOrganizationInvitationsSchema = yup.object().shape({
+ orgId: yup
+ .number()
+ .positive()
+ .min(1, "This field can not be empty!")
+ .label("Org Id")
+ .typeError("The org id must be a valid number"),
+
+ userId: yup
+ .number()
+ .positive()
+ .min(1, "This field can not be empty!")
+ .label("User Id")
+ .typeError("The user id must be a valid number")
+ .test(
+ "doesUserBelongToOrganization",
+ "The user must belong to the Organization and have the appropriate access.",
+ async function test(userId) {
+ const { orgId } = this.parent;
+
+ const user = await selectOrganizationUser({
+ orgId,
+ userId
+ });
+ if (user) {
+ return true;
+ }
+ const isUserSuperAdmin = await selectSuperAdmin({
+ userId
+ });
+ if (isUserSuperAdmin) {
+ return true;
+ }
+ const eventStaffOrRep = await selectEventUser({ orgId, userId });
+ if (eventStaffOrRep) {
+ return true;
+ }
+ return false;
+ }
+ )
+});
+
+module.exports = getUserOrganizationInvitationsSchema;
diff --git a/api-sample/src/app/controllers/userOrganizations/getAllUsersByOrgId/index.js b/api-sample/src/app/controllers/userOrganizations/getAllUsersByOrgId/index.js
new file mode 100644
index 00000000..60b8e47c
--- /dev/null
+++ b/api-sample/src/app/controllers/userOrganizations/getAllUsersByOrgId/index.js
@@ -0,0 +1,103 @@
+const { sql } = require("~root/lib/database");
+const { paginate, FILTERS } = require("~root/lib/paginate");
+const handleAPIError = require("~root/utils/handleAPIError");
+const getAllUsersByOrgIdSchema = require("./schemas/getAllUsersByOrgIdSchema");
+
+const getAllUsersByOrgId = async (req, res) => {
+ const { userId } = req.user;
+ const { orgId } = req.params;
+ try {
+ await getAllUsersByOrgIdSchema.validate(
+ { userId, orgId },
+ { abortEarly: false }
+ );
+ const resultset = await paginate({
+ basePath: req.path,
+ baseTable: "user_organizations",
+ selectFields: [
+ "user_organizations.user_organization_id",
+ "users.first_name",
+ "users.last_name",
+ "users.email",
+ "user_organizations.organization_id",
+ "users.current_situation",
+ "users.is_employed",
+ "user_organizations.user_role_id",
+ "user_organizations.created_at",
+ "user_organizations.disabled_at",
+ "user_organizations.disabled_by",
+ "user_organizations.updated_at",
+ "user_organizations.user_id",
+ "user_organizations.added_by",
+ sql`u1.first_name as added_by_firstname`,
+ sql`u1.last_name as added_by_lastname`
+ ],
+ joinStatements: [
+ sql`LEFT JOIN user_roles ON user_organizations.user_role_id = user_roles.user_role_id`,
+ sql`LEFT JOIN users ON user_organizations.user_id = users.user_id`,
+ sql`LEFT JOIN users u1 ON u1.user_id = user_organizations.added_by`
+ ],
+ sortableAttributes: ["user_organizations.user_organization_id"],
+ groupBy: ["user_organizations.user_organization_id"],
+ filterableAttributes: [
+ {
+ column: "user_organizations.user_role_id",
+ operations: [FILTERS.equals]
+ },
+ {
+ column: "users.user_id",
+ operations: [
+ FILTERS.containsIgnoreCase,
+ {
+ operator: "searchOrgUsersByFullName",
+ description: "search org users by fullname",
+ minimumNumberOfOperands: 1,
+ maximumNumberOfOperands: 1,
+ filterFn: operands => {
+ const filterString = operands[0];
+ if (filterString) {
+ return sql`(LOWER(users.first_name) LIKE ${`%${filterString}%`} OR LOWER(users.last_name) LIKE ${`%${filterString}%`}) `;
+ }
+ return sql``;
+ }
+ },
+ {
+ operator: "showInactiveOnly",
+ description:
+ "show users who are not assigned for particular event",
+ minimumNumberOfOperands: 1,
+ maximumNumberOfOperands: 999,
+ filterFn: operands => {
+ const eventId = operands[0];
+ if (eventId) {
+ return sql`users.user_id NOT IN (
+ select user_id from event_staff
+ inner join user_organizations ON event_staff.user_organization_id = user_organizations.user_organization_id
+ where event_staff.event_id = ${eventId})`;
+ }
+
+ return sql``;
+ }
+ }
+ ]
+ },
+ {
+ column: "user_organizations.organization_id",
+ operations: [FILTERS.equals]
+ }
+ ],
+ mandatoryFilter: sql`AND user_organizations.organization_id=${orgId}`,
+ sortBy: req.query.sort_by,
+ limit: req.query.page_size,
+ page: req.query.page, // "first" | "last" | null
+ direction: req.query.direction, // next | previous
+ filters: req.query.filters,
+ cursorValues: req.query.cursor
+ });
+ return res.send(resultset);
+ } catch (err) {
+ return handleAPIError(res, err);
+ }
+};
+
+module.exports = getAllUsersByOrgId;
diff --git a/api-sample/src/app/controllers/userOrganizations/getAllUsersByOrgId/schemas/getAllUsersByOrgIdSchema.js b/api-sample/src/app/controllers/userOrganizations/getAllUsersByOrgId/schemas/getAllUsersByOrgIdSchema.js
new file mode 100644
index 00000000..57997ae5
--- /dev/null
+++ b/api-sample/src/app/controllers/userOrganizations/getAllUsersByOrgId/schemas/getAllUsersByOrgIdSchema.js
@@ -0,0 +1,48 @@
+const yup = require("yup");
+const selectEventUser = require("~root/actions/schemaHelpers/queries/selectEventUser/selectEventUser");
+const selectOrganizationUser = require("~root/actions/schemaHelpers/queries/selectOrganizationUser/selectOrganizationUser");
+const selectSuperAdmin = require("~root/actions/schemaHelpers/queries/selectSuperAdmin/selectSuperAdmin");
+
+const getAllUsersByOrgIdSchema = yup.object().shape({
+ orgId: yup
+ .number()
+ .positive()
+ .min(1, "This field can not be empty!")
+ .label("Org Id")
+ .typeError("The org id must be a valid number"),
+
+ userId: yup
+ .number()
+ .positive()
+ .min(1, "This field can not be empty!")
+ .label("User Id")
+ .typeError("The user id must be a valid number")
+ .test(
+ "doesUserBelongToOrganization",
+ "The user must belong to the Organization and have the appropriate access.",
+ async function test(userId) {
+ const { orgId } = this.parent;
+
+ const user = await selectOrganizationUser({
+ orgId,
+ userId
+ });
+ if (user) {
+ return true;
+ }
+ const isUserSuperAdmin = await selectSuperAdmin({
+ userId
+ });
+ if (isUserSuperAdmin) {
+ return true;
+ }
+ const eventStaffOrRep = await selectEventUser({ orgId, userId });
+ if (eventStaffOrRep) {
+ return true;
+ }
+ return false;
+ }
+ )
+});
+
+module.exports = getAllUsersByOrgIdSchema;
diff --git a/api-sample/src/app/controllers/users/getPasswordRecoveryRequestByShortcode/index.js b/api-sample/src/app/controllers/users/getPasswordRecoveryRequestByShortcode/index.js
new file mode 100644
index 00000000..83d80ac3
--- /dev/null
+++ b/api-sample/src/app/controllers/users/getPasswordRecoveryRequestByShortcode/index.js
@@ -0,0 +1,24 @@
+const fetchRequestByShortcode = require("~root/actions/users/fetchRequestByShortcode");
+const handleAPIError = require("~root/utils/handleAPIError");
+
+const getPasswordRecoveryRequestByShortcode = async (req, res) => {
+ const { shortcode } = req.params;
+ try {
+ await getRequestByShortcode.validate(
+ {
+ shortcode
+ },
+ {
+ abortEarly: false
+ }
+ );
+ const { request } = await fetchRequestByShortcode({ shortcode });
+ res.status(201).send({
+ request
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = getPasswordRecoveryRequestByShortcode;
diff --git a/api-sample/src/app/controllers/users/getPasswordRecoveryRequestByShortcode/schemas/getRequestByShortcodeSchema.js b/api-sample/src/app/controllers/users/getPasswordRecoveryRequestByShortcode/schemas/getRequestByShortcodeSchema.js
new file mode 100644
index 00000000..4564c581
--- /dev/null
+++ b/api-sample/src/app/controllers/users/getPasswordRecoveryRequestByShortcode/schemas/getRequestByShortcodeSchema.js
@@ -0,0 +1,19 @@
+const yup = require("yup");
+const selectRequestByShortcode = require("./queries/selectRequestByShortcode");
+
+const getRequestByShortcode = yup.object().shape({
+ shortcode: yup
+ .string()
+ .label("Shortcode")
+ .typeError("The shortcode must be valid.")
+ .test("RequestMustExist", "The request doesnot exist", async function test(
+ shortcode
+ ) {
+ const isShortcodeExit = await selectRequestByShortcode({ shortcode });
+ if (isShortcodeExit) {
+ return true;
+ }
+ return false;
+ })
+});
+module.exports = getRequestByShortcode;
diff --git a/api-sample/src/app/controllers/users/getPasswordRecoveryRequestByShortcode/schemas/queries/selectRequestByShortcode.js b/api-sample/src/app/controllers/users/getPasswordRecoveryRequestByShortcode/schemas/queries/selectRequestByShortcode.js
new file mode 100644
index 00000000..cc4b04fc
--- /dev/null
+++ b/api-sample/src/app/controllers/users/getPasswordRecoveryRequestByShortcode/schemas/queries/selectRequestByShortcode.js
@@ -0,0 +1,18 @@
+const { submitQuery, camelKeys, getFirst } = require("~root/lib/database");
+
+const selectRequestByShortcode = ({ shortcode }) => submitQuery`
+ SELECT
+ password_recovery_request_id,
+ email,
+ shortcode,
+ recovered_at,
+ expiry_date,
+ created_at
+ FROM password_recovery_requests
+ WHERE shortcode = ${shortcode}
+`;
+
+module.exports = getFirst(
+ camelKeys(selectRequestByShortcode),
+ "passwordRecoveryRequestId"
+);
diff --git a/api-sample/src/app/controllers/users/getRegisterWithInvitation/index.js b/api-sample/src/app/controllers/users/getRegisterWithInvitation/index.js
new file mode 100644
index 00000000..8f224c11
--- /dev/null
+++ b/api-sample/src/app/controllers/users/getRegisterWithInvitation/index.js
@@ -0,0 +1,37 @@
+const handleAPIError = require("~root/utils/handleAPIError");
+const fetchInvitationByShortCode = require("~root/actions/invitations/fetchInvitationByShortCode");
+const fetchOrganizationNameById = require("~root/actions/organizations/fetchOrganizationNameById");
+const postRegisterWithInvitationSchema = require("./schema/postRegisterWithInvitationSchema");
+
+const getRegisterWithInvitation = async (req, res) => {
+ const { invitationShortcode } = req.params;
+
+ try {
+ await postRegisterWithInvitationSchema.validate(
+ {
+ invitationShortcode
+ },
+ {
+ abortEarly: false
+ }
+ );
+
+ const { invitation } = await fetchInvitationByShortCode({
+ invitationShortcode
+ });
+ const { email, organizationId } = invitation;
+
+ const { organizationName } = await fetchOrganizationNameById({
+ organizationId
+ });
+
+ res.status(201).send({
+ email,
+ organizationName
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = getRegisterWithInvitation;
diff --git a/api-sample/src/app/controllers/users/getRegisterWithInvitation/schema/postRegisterWithInvitationSchema.js b/api-sample/src/app/controllers/users/getRegisterWithInvitation/schema/postRegisterWithInvitationSchema.js
new file mode 100644
index 00000000..15976dc8
--- /dev/null
+++ b/api-sample/src/app/controllers/users/getRegisterWithInvitation/schema/postRegisterWithInvitationSchema.js
@@ -0,0 +1,29 @@
+const yup = require("yup");
+const selectInvitationByShortCode = require("./queries/selectInvitationByShortCode");
+
+const postRegisterWithInvitationSchema = yup.object().shape({
+ invitationShortcode: yup
+ .string()
+ .min(1, "This field can not be empty!")
+ .max(150, "This field has to be less than 150 characters")
+ .required()
+ .label("Invitation Shortcode")
+ .typeError("Not a valid Invitation Shortcode")
+ .test(
+ "ShortCodeMustExist",
+ "The Invitation ShortCode must belong to an email",
+ async function test(invitationShortcode) {
+ const { email } = this.parent;
+ const invitation = await selectInvitationByShortCode({
+ invitationShortcode,
+ email
+ });
+ if (invitation) {
+ return true;
+ }
+ return false;
+ }
+ )
+});
+
+module.exports = postRegisterWithInvitationSchema;
diff --git a/api-sample/src/app/controllers/users/getRegisterWithInvitation/schema/queries/selectInvitationByShortCode.js b/api-sample/src/app/controllers/users/getRegisterWithInvitation/schema/queries/selectInvitationByShortCode.js
new file mode 100644
index 00000000..19ddd277
--- /dev/null
+++ b/api-sample/src/app/controllers/users/getRegisterWithInvitation/schema/queries/selectInvitationByShortCode.js
@@ -0,0 +1,13 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectInvitationByShortCode = ({
+ invitationShortcode,
+ email
+}) => submitQuery`
+ SELECT
+ email
+ FROM user_organization_invitations
+ WHERE invitation_shortcode = ${invitationShortcode} && email = ${email}
+`;
+
+module.exports = getFirst(selectInvitationByShortCode);
diff --git a/api-sample/src/app/controllers/users/getUserOrganizations/index.js b/api-sample/src/app/controllers/users/getUserOrganizations/index.js
new file mode 100644
index 00000000..c901cb14
--- /dev/null
+++ b/api-sample/src/app/controllers/users/getUserOrganizations/index.js
@@ -0,0 +1,25 @@
+const handleAPIError = require("~root/utils/handleAPIError");
+const fetchUserOrganizations = require("~root/actions/users/fetchUserOrganizations");
+const getUserOrganizationsSchema = require("./schemas/getUserOrganizationsSchema");
+
+const getUserOrganizations = async (req, res) => {
+ const { userId } = req.user;
+ try {
+ await getUserOrganizationsSchema.validate(
+ {
+ userId
+ },
+ {
+ abortEarly: false
+ }
+ );
+ const { organizations } = await fetchUserOrganizations({ userId });
+ res.status(201).send({
+ organizations
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = getUserOrganizations;
diff --git a/api-sample/src/app/controllers/users/getUserOrganizations/schemas/getUserOrganizationsSchema.js b/api-sample/src/app/controllers/users/getUserOrganizations/schemas/getUserOrganizationsSchema.js
new file mode 100644
index 00000000..e4f4c792
--- /dev/null
+++ b/api-sample/src/app/controllers/users/getUserOrganizations/schemas/getUserOrganizationsSchema.js
@@ -0,0 +1,22 @@
+const yup = require("yup");
+const selectUserById = require("./queries/selectUserById");
+
+const getUserOrganizationsSchema = yup.object().shape({
+ userId: yup
+ .number()
+ .label("userId")
+ .typeError("userId is invalid.")
+ .test(
+ "doesUserExists",
+ "User account does not exists.",
+ async function test(userId) {
+ const user = await selectUserById({ userId });
+ if (user) {
+ return true;
+ }
+ return false;
+ }
+ )
+});
+
+module.exports = getUserOrganizationsSchema;
diff --git a/api-sample/src/app/controllers/users/getUserOrganizations/schemas/queries/selectUserById.js b/api-sample/src/app/controllers/users/getUserOrganizations/schemas/queries/selectUserById.js
new file mode 100644
index 00000000..78f5a09a
--- /dev/null
+++ b/api-sample/src/app/controllers/users/getUserOrganizations/schemas/queries/selectUserById.js
@@ -0,0 +1,10 @@
+const { submitQuery, camelKeys, getFirst } = require("~root/lib/database");
+
+const selectUserById = ({ userId }) => submitQuery`
+ SELECT
+ user_id
+ FROM users
+ WHERE user_id=${userId};
+`;
+
+module.exports = getFirst(camelKeys(selectUserById), "userId");
diff --git a/api-sample/src/app/controllers/users/patchUserInvitation/index.js b/api-sample/src/app/controllers/users/patchUserInvitation/index.js
new file mode 100644
index 00000000..64f9eb67
--- /dev/null
+++ b/api-sample/src/app/controllers/users/patchUserInvitation/index.js
@@ -0,0 +1,50 @@
+const createOrganizationUser = require("~root/actions/organizations/createOrganizationUser");
+const fetchUserInvitation = require("~root/actions/users/fetchUserInvitation");
+const modifyUserInvitation = require("~root/actions/users/modifyUserInvitation");
+const handleAPIError = require("~root/utils/handleAPIError");
+const patchUserInvitationSchema = require("./schemas/patchUserInvitationSchema");
+
+const patchUserInvitation = async (req, res) => {
+ const { userId, email } = req.user;
+ const { invitationId, shortcode } = req.body;
+ try {
+ await patchUserInvitationSchema.validate(
+ {
+ userId,
+ invitationId,
+ shortcode,
+ email
+ },
+ {
+ abortEarly: false
+ }
+ );
+
+ const { invitation } = await fetchUserInvitation({
+ email,
+ invitationId,
+ shortcode
+ });
+ const { userOrganizationRoleId, organizationId, invitedBy } = invitation;
+
+ const { patchedInvitationId } = await modifyUserInvitation({
+ invitationId,
+ shortcode
+ });
+ const { organizationUserId } = await createOrganizationUser({
+ userId,
+ userOrganizationRoleId,
+ organizationId,
+ addedBy: invitedBy
+ });
+
+ res.status(201).send({
+ patchedInvitationId,
+ organizationUserId
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = patchUserInvitation;
diff --git a/api-sample/src/app/controllers/users/patchUserInvitation/schemas/patchUserInvitationSchema.js b/api-sample/src/app/controllers/users/patchUserInvitation/schemas/patchUserInvitationSchema.js
new file mode 100644
index 00000000..1a103249
--- /dev/null
+++ b/api-sample/src/app/controllers/users/patchUserInvitation/schemas/patchUserInvitationSchema.js
@@ -0,0 +1,60 @@
+const yup = require("yup");
+const selectUserById = require("./queries/selectUserById");
+const selectUserByInvitationId = require("./queries/selectUserByInvitationId");
+
+const patchUserInvitationSchema = yup.object().shape({
+ email: yup
+ .string()
+ .email()
+ .label("Email")
+ .typeError("Email is invalid."),
+ userId: yup
+ .number()
+ .positive()
+ .min(1, "This field can not be empty!")
+ .label("User Id")
+ .typeError("The user id must be a valid number")
+ .test(
+ "doesUserBelongToOrganization",
+ "The user must belong to the Organization and have the appropriate access (organization admin or event manager) or be a superAdmin",
+ async function test(userId) {
+ const { email } = this.parent;
+ const userID = await selectUserById({
+ userId,
+ email
+ });
+ if (userID) {
+ return true;
+ }
+
+ return false;
+ }
+ ),
+ invitationId: yup
+ .number()
+ .positive()
+ .label("Invitation Id")
+ .typeError("The invitation id must be valid number.")
+ .test(
+ "doesInvitationIdExist",
+ "The invitation must be exist in the organization",
+ async function test(invitationId) {
+ const { shortcode } = this.parent;
+ const hasInvitationId = await selectUserByInvitationId({
+ invitationId,
+ shortcode
+ });
+ if (hasInvitationId) {
+ return true;
+ }
+
+ return false;
+ }
+ ),
+ shortcode: yup
+ .string()
+ .label("Shortcode")
+ .typeError("The shortcode must be string.")
+});
+
+module.exports = patchUserInvitationSchema;
diff --git a/api-sample/src/app/controllers/users/patchUserInvitation/schemas/queries/selectUserById.js b/api-sample/src/app/controllers/users/patchUserInvitation/schemas/queries/selectUserById.js
new file mode 100644
index 00000000..37238fb9
--- /dev/null
+++ b/api-sample/src/app/controllers/users/patchUserInvitation/schemas/queries/selectUserById.js
@@ -0,0 +1,11 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUserById = ({ userId, email }) => submitQuery`
+ SELECT
+ first_name,
+ email
+ FROM users
+ WHERE user_id = ${userId} && email=${email}
+`;
+
+module.exports = getFirst(selectUserById);
diff --git a/api-sample/src/app/controllers/users/patchUserInvitation/schemas/queries/selectUserByInvitationId.js b/api-sample/src/app/controllers/users/patchUserInvitation/schemas/queries/selectUserByInvitationId.js
new file mode 100644
index 00000000..06d18e4e
--- /dev/null
+++ b/api-sample/src/app/controllers/users/patchUserInvitation/schemas/queries/selectUserByInvitationId.js
@@ -0,0 +1,12 @@
+const { submitQuery, getFirst, camelKeys } = require("~root/lib/database");
+
+const selectUserByInvitationId = ({ invitationId, shortcode }) => submitQuery`
+ SELECT
+ invitation_shortcode,
+ email,
+ user_organization_invitation_id
+ FROM user_organization_invitations
+ WHERE user_organization_invitation_id = ${invitationId} && invitation_shortcode = ${shortcode}
+`;
+
+module.exports = getFirst(camelKeys(selectUserByInvitationId));
diff --git a/api-sample/src/app/controllers/users/postCompleteRegistration/index.js b/api-sample/src/app/controllers/users/postCompleteRegistration/index.js
new file mode 100644
index 00000000..9efae140
--- /dev/null
+++ b/api-sample/src/app/controllers/users/postCompleteRegistration/index.js
@@ -0,0 +1,62 @@
+const jwt = require("jsonwebtoken");
+const createCompleteRegistration = require("~root/actions/users/createCompleteRegistration");
+const handleApiError = require("~root/utils/handleAPIError");
+const fetchRegistrationRequestByShortcode = require("~root/actions/users/fetchRegistrationRequestByShortcode");
+const modifyRegistrationRequestStatus = require("~root/actions/users/modifyRegistrationRequestStatus");
+const createOrganizationUser = require("~root/actions/organizations/createOrganizationUser");
+const createOrganization = require("~root/actions/organizations/createOrganization");
+
+const postCompleteRegistration = async (req, res) => {
+ const { registrationShortcode } = req.params;
+
+ try {
+ const { potentialNewUserData } = await fetchRegistrationRequestByShortcode({
+ registrationShortcode
+ });
+
+ const {
+ firstName,
+ lastName,
+ email,
+ password,
+ organizationName
+ } = potentialNewUserData;
+
+ const { userId } = await createCompleteRegistration({
+ firstName,
+ lastName,
+ email,
+ password
+ });
+
+ const { organizationId } = await createOrganization({
+ organizationName
+ });
+
+ await createOrganizationUser({
+ userId,
+ userOrganizationRoleId: 1,
+ organizationId,
+ addedBy: userId
+ });
+
+ await modifyRegistrationRequestStatus({ registrationShortcode });
+
+ const accessToken = jwt.sign(
+ { ...potentialNewUserData, userId },
+ process.env.JWT_SECRET,
+ {
+ expiresIn: "2h" // 2 Hours
+ }
+ );
+
+ res.send({
+ userId,
+ accessToken
+ });
+ } catch (err) {
+ handleApiError(res, err);
+ }
+};
+
+module.exports = postCompleteRegistration;
diff --git a/api-sample/src/app/controllers/users/postRegisterWithInvitation/index.js b/api-sample/src/app/controllers/users/postRegisterWithInvitation/index.js
new file mode 100644
index 00000000..fa7f2f19
--- /dev/null
+++ b/api-sample/src/app/controllers/users/postRegisterWithInvitation/index.js
@@ -0,0 +1,81 @@
+const axios = require("axios");
+const handleAPIError = require("~root/utils/handleAPIError");
+const fetchInvitationByShortCode = require("~root/actions/invitations/fetchInvitationByShortCode");
+const createUser = require("~root/actions/users/createUser");
+const createOrganizationUser = require("~root/actions/organizations/createOrganizationUser");
+const postRegisterWithInvitationSchema = require("./schema/postRegisterWithInvitationSchema");
+
+// eslint-disable-next-line consistent-return
+const postRegisterWithInvitation = async (req, res) => {
+ const { firstName, lastName, password, captcha } = req.body;
+
+ const { invitationShortcode } = req.params;
+
+ try {
+ await postRegisterWithInvitationSchema.validate(
+ {
+ firstName,
+ lastName,
+ password,
+ invitationShortcode
+ },
+ {
+ abortEarly: false
+ }
+ );
+
+ if (!captcha) {
+ return res.status(400).json({ error: "captcha is required" });
+ }
+
+ const secretKey = process.env.RECAPTCHA_SECRET_KEY;
+
+ const verificationParams = {
+ secret: secretKey,
+ response: captcha
+ };
+
+ const verificationUrl = `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${captcha}`;
+
+ const { data } = await axios.post(verificationUrl, verificationParams);
+
+ if (!data.success) {
+ return res.status(500).send({ error: "Captcha verification failed" });
+ }
+
+ const { invitation } = await fetchInvitationByShortCode({
+ invitationShortcode
+ });
+
+ const {
+ organizationId,
+ invitedBy,
+ userOrganizationRoleId,
+ email
+ } = invitation;
+
+ const { user } = await createUser({
+ firstName,
+ lastName,
+ password,
+ email,
+ isSuperAdmin: 0
+ });
+
+ const { organizationUserId } = await createOrganizationUser({
+ userId: user,
+ userOrganizationRoleId,
+ organizationId,
+ addedBy: invitedBy
+ });
+
+ res.status(201).send({
+ user,
+ organizationUserId
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = postRegisterWithInvitation;
diff --git a/api-sample/src/app/controllers/users/postRegisterWithInvitation/schema/postRegisterWithInvitationSchema.js b/api-sample/src/app/controllers/users/postRegisterWithInvitation/schema/postRegisterWithInvitationSchema.js
new file mode 100644
index 00000000..f267a1ef
--- /dev/null
+++ b/api-sample/src/app/controllers/users/postRegisterWithInvitation/schema/postRegisterWithInvitationSchema.js
@@ -0,0 +1,57 @@
+const yup = require("yup");
+const selectInvitationByShortCode = require("./queries/selectInvitationByShortCode");
+
+const postRegisterWithInvitationSchema = yup.object().shape({
+ firstName: yup
+ .string()
+ .min(1, "This field can not be empty!")
+ .max(50, "This field has to be less than 50 characters")
+ .required()
+ .label("First Name")
+ .typeError("Not a valid First Name"),
+
+ lastName: yup
+ .string()
+ .min(1, "This field can not be empty!")
+ .max(50, "This field has to be less than 50 characters")
+ .required()
+ .label("Last Name")
+ .typeError("Not a valid Last Name"),
+
+ password: yup
+ .string()
+ .min(8, "Password must be at least 8 characters long!")
+ .max(500, "This field has to be less than 500 characters")
+ .matches(
+ /^(?=.*[A-Z])(?=.*[!@#$&*])(?=.*[0-9])(?=.*[a-z]).{8,}$/,
+ "Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character"
+ )
+ .required()
+ .label("Password")
+ .typeError("Not a valid Password"),
+
+ invitationShortcode: yup
+ .string()
+ .min(1, "This field can not be empty!")
+ .max(150, "This field has to be less than 150 characters")
+ .required()
+ .label("Invitation Shortcode")
+ .typeError("Not a valid Invitation Shortcode")
+ .test(
+ "ShortCodeMustExist",
+ "The Invitation ShortCode must belong to an email",
+ async function test(invitationShortcode) {
+ const { email } = this.parent;
+ const invitation = await selectInvitationByShortCode({
+ invitationShortcode,
+ email
+ });
+ if (invitation) {
+ return true;
+ }
+ return false;
+ }
+ )
+});
+
+module.exports = postRegisterWithInvitationSchema;
diff --git a/api-sample/src/app/controllers/users/postRegisterWithInvitation/schema/queries/selectInvitationByShortCode.js b/api-sample/src/app/controllers/users/postRegisterWithInvitation/schema/queries/selectInvitationByShortCode.js
new file mode 100644
index 00000000..19ddd277
--- /dev/null
+++ b/api-sample/src/app/controllers/users/postRegisterWithInvitation/schema/queries/selectInvitationByShortCode.js
@@ -0,0 +1,13 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectInvitationByShortCode = ({
+ invitationShortcode,
+ email
+}) => submitQuery`
+ SELECT
+ email
+ FROM user_organization_invitations
+ WHERE invitation_shortcode = ${invitationShortcode} && email = ${email}
+`;
+
+module.exports = getFirst(selectInvitationByShortCode);
diff --git a/api-sample/src/app/controllers/users/postRegistrationRequest/index.js b/api-sample/src/app/controllers/users/postRegistrationRequest/index.js
new file mode 100644
index 00000000..c44e1d33
--- /dev/null
+++ b/api-sample/src/app/controllers/users/postRegistrationRequest/index.js
@@ -0,0 +1,82 @@
+const { v4: uuidv4 } = require("uuid");
+const axios = require("axios");
+const handleAPIError = require("~root/utils/handleAPIError");
+const createRegistrationRequest = require("~root/actions/users/createRegistrationRequest");
+const sendEmail = require("~root/services/emails/sendEmail");
+const postRegistrationRequestSchema = require("./schema/postRegistrationRequestSchema");
+
+// eslint-disable-next-line consistent-return
+const postRegistrationRequest = async (req, res) => {
+ const registrationShortcode = uuidv4();
+
+ const {
+ firstName,
+ lastName,
+ email,
+ password,
+ organizationName,
+ captcha
+ } = req.body;
+
+ try {
+ await postRegistrationRequestSchema.validate(
+ {
+ firstName,
+ lastName,
+ email,
+ password,
+ organizationName
+ },
+ {
+ abortEarly: false
+ }
+ );
+
+ // if (!captcha) {
+ // return res.status(400).json({ error: "captcha is required" });
+ // }
+
+ // const secretKey = process.env.RECAPTCHA_SECRET_KEY;
+
+ // const verificationParams = {
+ // secret: secretKey,
+ // response: captcha
+ // };
+
+ // const verificationUrl = `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${captcha}`;
+
+ // const { data } = await axios.post(verificationUrl, verificationParams);
+
+ // if (!data.success) {
+ // return res.status(500).send({ error: "Captcha verification failed" });
+ // }
+
+ const { createdRegistrationRequest } = await createRegistrationRequest({
+ firstName,
+ lastName,
+ email,
+ password,
+ organizationName,
+ registrationShortcode
+ });
+
+ // const emailPayload = {
+ // to: email,
+ // template: "email-verification",
+ // version: "0.0.1",
+ // metadata: {
+ // firstName,
+ // emailVerification: `${process.env.APP_BASE_URL}/complete-registration/${registrationShortcode}`
+ // }
+ // };
+ // sendEmail(emailPayload);
+
+ res.status(201).send({
+ createdRegistrationRequest
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = postRegistrationRequest;
diff --git a/api-sample/src/app/controllers/users/postRegistrationRequest/schema/postRegistrationRequestSchema.js b/api-sample/src/app/controllers/users/postRegistrationRequest/schema/postRegistrationRequestSchema.js
new file mode 100644
index 00000000..d3d8ab3e
--- /dev/null
+++ b/api-sample/src/app/controllers/users/postRegistrationRequest/schema/postRegistrationRequestSchema.js
@@ -0,0 +1,67 @@
+const yup = require("yup");
+const selectUserByEmail = require("./queries/selectUserByEmail");
+
+const postRegistrationRequestSchema = yup.object().shape({
+ firstName: yup
+ .string()
+ .min(1, "This field can not be empty!")
+ .max(50, "This field has to be less than 50 characters")
+ .required()
+ .label("First Name")
+ .typeError("Not a valid First Name"),
+
+ lastName: yup
+ .string()
+ .min(1, "This field can not be empty!")
+ .max(50, "This field has to be less than 50 characters")
+ .required()
+ .label("Last Name")
+ .typeError("Not a valid Last Name"),
+
+ email: yup
+ .string()
+ .min(1, "This field can not be empty!")
+ .max(50, "This field has to be less than 50 characters")
+ .email()
+ .required()
+ .label("Email")
+ .typeError("Not a valid Email")
+ .test(
+ "Does User already exist in platform",
+ "This email already registered to platform",
+
+ async function test(email) {
+ const userEmail = await selectUserByEmail({
+ email
+ });
+
+ if (!userEmail) {
+ return true;
+ }
+
+ return false;
+ }
+ ),
+
+ password: yup
+ .string()
+ .min(8, "Password must be at least 8 characters long!")
+ .max(500, "This field has to be less than 500 characters")
+ .matches(
+ /^(?=.*[A-Z])(?=.*[!@#$&*])(?=.*[0-9])(?=.*[a-z]).{8,}$/,
+ "Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character"
+ )
+ .required()
+ .label("Password")
+ .typeError("Not a valid Password"),
+
+ organizationName: yup
+ .string()
+ .min(1, "This field can not be empty!")
+ .max(50, "This field has to be less than 50 characters")
+ .required()
+ .label("Company Name")
+ .typeError("Not a valid Company Name")
+});
+
+module.exports = postRegistrationRequestSchema;
diff --git a/api-sample/src/app/controllers/users/postRegistrationRequest/schema/queries/selectUserByEmail.js b/api-sample/src/app/controllers/users/postRegistrationRequest/schema/queries/selectUserByEmail.js
new file mode 100644
index 00000000..264d3e68
--- /dev/null
+++ b/api-sample/src/app/controllers/users/postRegistrationRequest/schema/queries/selectUserByEmail.js
@@ -0,0 +1,10 @@
+const { submitQuery, getFirst } = require("~root/lib/database");
+
+const selectUserByEmail = ({ email }) => submitQuery`
+ SELECT
+ email
+ FROM registration_requests
+ WHERE email IN (${email});
+`;
+
+module.exports = getFirst(selectUserByEmail, "email");
diff --git a/api-sample/src/app/controllers/users/putUserDetails/index.js b/api-sample/src/app/controllers/users/putUserDetails/index.js
index be7bb7d6..98d447d0 100644
--- a/api-sample/src/app/controllers/users/putUserDetails/index.js
+++ b/api-sample/src/app/controllers/users/putUserDetails/index.js
@@ -5,14 +5,13 @@ const handleAPIError = require("~root/utils/handleAPIError");
const putUserDetails = async (req, res) => {
const { userId } = req.user;
- const { firstName, lastName, password, jobTitle } = req.body;
+ const { firstName, lastName, password } = req.body;
try {
const { userDetails } = await modifyUser({
userId,
firstName,
lastName,
- password,
- jobTitle
+ password
});
const { user } = await fetchUserById({
@@ -20,7 +19,7 @@ const putUserDetails = async (req, res) => {
});
const accessToken = jwt.sign({ ...user }, process.env.JWT_SECRET, {
- expiresIn: "365d" // 1 year
+ expiresIn: "2h" // 2 Hours
});
res.send({
diff --git a/api-sample/src/app/controllers/users/register/index.js b/api-sample/src/app/controllers/users/register/index.js
index 02d7fbe3..4f1c60e7 100644
--- a/api-sample/src/app/controllers/users/register/index.js
+++ b/api-sample/src/app/controllers/users/register/index.js
@@ -3,7 +3,14 @@ const handleAPIError = require("~root/utils/handleAPIError");
const postUserSchema = require("./schemas/postUserSchema");
const postUser = async (req, res) => {
- const { firstName, lastName, email, password, userTypeId } = req.body;
+ const {
+ firstName,
+ lastName,
+ email,
+ password,
+ currentSituation,
+ isEmployed
+ } = req.body;
try {
await postUserSchema.validate(
@@ -12,23 +19,25 @@ const postUser = async (req, res) => {
lastName,
email,
password,
- userTypeId
+ currentSituation,
+ isEmployed
},
{
abortEarly: false
}
);
- const { user } = await createUser({
+ const { userId } = await createUser({
firstName,
lastName,
email,
password,
- userTypeId
+ currentSituation,
+ isEmployed
});
res.status(201).send({
- user
+ userId
});
} catch (err) {
handleAPIError(res, err);
diff --git a/api-sample/src/app/controllers/users/register/schemas/postUserSchema.js b/api-sample/src/app/controllers/users/register/schemas/postUserSchema.js
index 25831f32..dc17ba11 100644
--- a/api-sample/src/app/controllers/users/register/schemas/postUserSchema.js
+++ b/api-sample/src/app/controllers/users/register/schemas/postUserSchema.js
@@ -7,22 +7,20 @@ const postUserSchema = yup.object().shape({
.required()
.label("First Name")
.typeError("First Name must be a number."),
+
lastName: yup
.string()
.required()
.label("Last Name")
.typeError("Last Name must be a number."),
+
password: yup
.string()
.min(8)
.required()
.label("password")
.typeError("password must be a number."),
- userTypeId: yup
- .number()
- .required()
- .label("User Type ID")
- .typeError("User Type Id must be a number."),
+
email: yup
.string()
.email()
@@ -38,6 +36,9 @@ const postUserSchema = yup.object().shape({
}
return true;
});
- })
+ }),
+ currentSituation: yup.string(),
+ isEmployed: yup.number()
});
+
module.exports = postUserSchema;
diff --git a/api-sample/src/app/controllers/users/registerAdmin/index.js b/api-sample/src/app/controllers/users/registerAdmin/index.js
new file mode 100644
index 00000000..6f7536e3
--- /dev/null
+++ b/api-sample/src/app/controllers/users/registerAdmin/index.js
@@ -0,0 +1,38 @@
+const createUser = require("~root/actions/users/createUser");
+const handleAPIError = require("~root/utils/handleAPIError");
+const postUserSchema = require("./schemas/postUserSchema");
+
+const postUser = async (req, res) => {
+ const { firstName, lastName, email, password, isSuperAdmin } = req.body;
+
+ try {
+ await postUserSchema.validate(
+ {
+ firstName,
+ lastName,
+ email,
+ password,
+ isSuperAdmin
+ },
+ {
+ abortEarly: false
+ }
+ );
+
+ const { user } = await createUser({
+ firstName,
+ lastName,
+ email,
+ password,
+ isSuperAdmin
+ });
+
+ res.status(201).send({
+ user
+ });
+ } catch (err) {
+ handleAPIError(res, err);
+ }
+};
+
+module.exports = postUser;
diff --git a/api-sample/src/app/controllers/users/registerAdmin/schemas/postUserSchema.js b/api-sample/src/app/controllers/users/registerAdmin/schemas/postUserSchema.js
new file mode 100644
index 00000000..c0c445d3
--- /dev/null
+++ b/api-sample/src/app/controllers/users/registerAdmin/schemas/postUserSchema.js
@@ -0,0 +1,43 @@
+const yup = require("yup");
+const selectUserByEmail = require("./queries/selectUserById");
+
+const postUserSchema = yup.object().shape({
+ firstName: yup
+ .string()
+ .required()
+ .label("First Name")
+ .typeError("First Name must be a number."),
+ lastName: yup
+ .string()
+ .required()
+ .label("Last Name")
+ .typeError("Last Name must be a number."),
+ password: yup
+ .string()
+ .min(8)
+ .required()
+ .label("password")
+ .typeError("password must be a number."),
+ email: yup
+ .string()
+ .email()
+ .required()
+ .label("Email")
+ .typeError("Email is invalid.")
+ .test("doesEmailExist", "User account already exists.", function test(
+ email
+ ) {
+ return selectUserByEmail({ email }).then(account => {
+ if (account) {
+ return false;
+ }
+ return true;
+ });
+ }),
+ isSuperAdmin: yup
+ .boolean()
+ .required()
+ .label("isSuperAdmin")
+ .typeError("isSuperAdmin must be a boolean.")
+});
+module.exports = postUserSchema;
diff --git a/api-sample/src/app/controllers/users/registerAdmin/schemas/queries/selectUserById.js b/api-sample/src/app/controllers/users/registerAdmin/schemas/queries/selectUserById.js
new file mode 100644
index 00000000..4dacc07e
--- /dev/null
+++ b/api-sample/src/app/controllers/users/registerAdmin/schemas/queries/selectUserById.js
@@ -0,0 +1,10 @@
+const { submitQuery, camelKeys, getFirst } = require("~root/lib/database");
+
+const selectUserByEmail = ({ email }) => submitQuery`
+ SELECT
+ user_id
+ FROM users
+ WHERE email=${email};
+`;
+
+module.exports = getFirst(camelKeys(selectUserByEmail));
diff --git a/api-sample/src/app/controllers/users/userTypes/index.js b/api-sample/src/app/controllers/users/userTypes/index.js
deleted file mode 100644
index c52ca15b..00000000
--- a/api-sample/src/app/controllers/users/userTypes/index.js
+++ /dev/null
@@ -1,16 +0,0 @@
-const fetchUserTypes = require("~root/actions/users/fetchUserTypes");
-const handleAPIError = require("~root/utils/handleAPIError");
-
-const getUserTypes = async (req, res) => {
- try {
- const { userTypes } = await fetchUserTypes();
-
- res.status(201).send({
- userTypes
- });
- } catch (err) {
- handleAPIError(res, err);
- }
-};
-
-module.exports = getUserTypes;
diff --git a/api-sample/src/app/routes.js b/api-sample/src/app/routes.js
index a8a1f01d..8aee5993 100644
--- a/api-sample/src/app/routes.js
+++ b/api-sample/src/app/routes.js
@@ -1,33 +1,128 @@
const express = require("express");
+const { ADMIN } = require("~root/constants/userTypes");
const postLogin = require("./controllers/users/login");
-const postUser = require("./controllers/users/register");
+const postUser = require("./controllers/users/registerAdmin");
const putUserDetails = require("./controllers/users/putUserDetails");
const authentication = require("./middlewares/authentication");
const authorise = require("./middlewares/authorisation");
-const getUserTypes = require("./controllers/users/userTypes");
-const { ADMIN } = require("~root/constants/userTypes");
+
+const postRegistrationRequest = require("./controllers/users/postRegistrationRequest");
+const postCompleteRegistration = require("./controllers/users/postCompleteRegistration");
+
+const getPasswordRecoveryRequestByShortcode = require("./controllers/users/getPasswordRecoveryRequestByShortcode");
const putPassword = require("./controllers/password-recovery/putPassword");
const postRecoveryRequest = require("./controllers/password-recovery/postRecoveryRequest");
+const postOrganizationInvitation = require("./controllers/invitations/postOrganizationInvitation");
+const getUserInvitations = require("./controllers/invitations/getUserInvitations");
+const acceptInvitation = require("./controllers/invitations/acceptInvitation");
+const patchUserInvitation = require("./controllers/users/patchUserInvitation");
+const getOrganizationUsers = require("./controllers/organizations/getOrganizationUsers");
+const getUserOrganizations = require("./controllers/users/getUserOrganizations");
+
+const postOrganization = require("./controllers/organizations/postOrganization");
+const postOrganizationInvitations = require("./controllers/invitations/postOrganizationInvitations");
+const patchOrganizationInvitation = require("./controllers/invitations/patchOrganizationInvitation");
+const deleteInvitation = require("./controllers/invitations/deleteInvitation");
+const postOrganizationUser = require("./controllers/organizations/postOrganizationUser");
+const patchOrganizationUser = require("./controllers/organizations/patchOrganizationUser");
+const deleteOrganizationUser = require("./controllers/organizations/deleteOrganizationUser");
+const getOrganizationInvitations = require("./controllers/organizations/getOrganizationInvitations");
+
+const postNewOrganization = require("./controllers/organizations/postNewOrganization");
+const getOrganizationsByEmail = require("./controllers/organizations/getOrganizationsByEmail");
+const getOrganizationsByUserId = require("./controllers/organizations/getOrganizationsByUserId");
+const getAllUsersByOrgId = require("./controllers/userOrganizations/getAllUsersByOrgId");
+const getUserOrganizationInvitations = require("./controllers/userOrganizationInvitations/getUserOrganizationInvitations");
+const deleteInvitationByOrgUser = require("./controllers/invitations/deleteInvitationByOrgUser");
+const postRegisterWithInvitation = require("./controllers/users/postRegisterWithInvitation");
+const getRegisterWithInvitation = require("./controllers/users/getRegisterWithInvitation");
+
const router = express.Router();
-// USER MANAGEMENT
router.post("/login", postLogin);
-
router.post(
- "/register",
+ "/register/admin",
authentication,
authorise({ roles: [ADMIN] }),
postUser
);
-
+router.post("/registration-request", postRegistrationRequest);
+router.post(
+ "/complete-registration/:registrationShortcode",
+ postCompleteRegistration
+);
+router.post("/recovery-request", postRecoveryRequest);
+router.get(
+ "/edit/user/request/shortcode/:shortcode",
+ getPasswordRecoveryRequestByShortcode
+);
+router.put("/update-password/:shortcode", putPassword);
router.put("/edit/user", authentication, putUserDetails);
-router.get("/user-types", getUserTypes);
+router.post("/organizations/create", authentication, postOrganization);
+router.get("/organizations/:orgId/users", authentication, getOrganizationUsers);
+router.get("/users/organizations", authentication, getUserOrganizations);
+router.post(
+ "/organizations/invitation",
+ authentication,
+ postOrganizationInvitation
+);
-router.post("/recovery-request", postRecoveryRequest);
+router.patch("/user/invitation-details", authentication, patchUserInvitation);
+router.post(
+ "/organizations/:orgId/invitations",
+ authentication,
+ postOrganizationInvitations
+);
+router.get(
+ "/organizations/:orgId/invitations",
+ authentication,
+ getOrganizationInvitations
+);
+router.patch(
+ "/organizations/:orgId/invitation",
+ authentication,
+ patchOrganizationInvitation
+);
+router.patch(
+ "/user/invitations/:invitationId",
+ authentication,
+ acceptInvitation
+);
+router.delete("/user/invitations", authentication, deleteInvitation);
+router.delete(
+ "/organizations/invitations/:invitationId/:orgId",
+ authentication,
+ deleteInvitationByOrgUser
+);
+router.get("/user/invitations", authentication, getUserInvitations);
+router.post("/organizations/users", authentication, postOrganizationUser);
+router.patch(
+ "/organizations/:orgId/users/:userId",
+ authentication,
+ patchOrganizationUser
+);
+router.delete("/organizations/users", authentication, deleteOrganizationUser);
+router.post("/organization", authentication, postNewOrganization);
+router.get("/organizations/:email", getOrganizationsByEmail);
+router.get("/organizations", authentication, getOrganizationsByUserId);
+router.get("/organizations/:orgId/users", authentication, getAllUsersByOrgId);
+router.get(
+ "/organizations/:orgId/invitations",
+ authentication,
+ getUserOrganizationInvitations
+);
-router.put("/update-password/:shortcode", putPassword);
+router.post(
+ "/register-with-invitation/:invitationShortcode",
+ postRegisterWithInvitation
+);
+
+router.get(
+ "/register-with-invitation/:invitationShortcode",
+ getRegisterWithInvitation
+);
module.exports = router;
diff --git a/api-sample/src/lib/services/emails/sendEmail/templates/recovery-email/0.0.1.txt b/api-sample/src/lib/services/emails/sendEmail/templates/recovery-email/0.0.1.txt
deleted file mode 100644
index d59203f8..00000000
--- a/api-sample/src/lib/services/emails/sendEmail/templates/recovery-email/0.0.1.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-This e-mail is in response to your recent request to recover a forgotten password. This link will remain active for the next 8 hours to reset your password.
-
-Please click the link below and follow the instructions provided.
-Recover Password
-{{recoveryUrl}}
-
-In case you have any questions, feel free to get in touch at {{replyToEmail}}.
-
-Best Regards,
-The Productive Machines Team
--https://productivemachines.co.uk/
diff --git a/api-sample/src/lib/services/emails/sendEmail/index.js b/api-sample/src/services/emails/sendEmail/index.js
similarity index 98%
rename from api-sample/src/lib/services/emails/sendEmail/index.js
rename to api-sample/src/services/emails/sendEmail/index.js
index 1ff75be4..a3e709f0 100644
--- a/api-sample/src/lib/services/emails/sendEmail/index.js
+++ b/api-sample/src/services/emails/sendEmail/index.js
@@ -83,4 +83,4 @@ const sendMail = async ({
}
};
-module.exports = sendMail;
\ No newline at end of file
+module.exports = sendMail;
diff --git a/api-sample/src/services/emails/sendEmail/templates/company-manager-invitation/0.0.1.mjml b/api-sample/src/services/emails/sendEmail/templates/company-manager-invitation/0.0.1.mjml
new file mode 100644
index 00000000..a5bdfb68
--- /dev/null
+++ b/api-sample/src/services/emails/sendEmail/templates/company-manager-invitation/0.0.1.mjml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+ @import
+ url('https://fonts.googleapis.com/css2?family=Inter&family=Lato&family=Rubik:wght@400;500&display=swap');
+ .no-link-decoration { color: #ffffff; text-decoration: none }
+
+
+
+
+
+
+ {{userRole}} Invitation
+
+
+
+
+
+ {{sender}} invited you to become a
+ {{userRole}}
+
+
+
+ this is only included is a message has been input when the invite was
+ sent.
+
+
+
+ {{messageTitle}}
+
+
+
+
+ {{senderMessage}}
+
+
+
+
+
+
+ View Invitation
+
+
+
+
diff --git a/api-sample/src/services/emails/sendEmail/templates/company-manager-invitation/0.0.1.subject b/api-sample/src/services/emails/sendEmail/templates/company-manager-invitation/0.0.1.subject
new file mode 100644
index 00000000..6e10a0c5
--- /dev/null
+++ b/api-sample/src/services/emails/sendEmail/templates/company-manager-invitation/0.0.1.subject
@@ -0,0 +1 @@
+{{userRole}} Invitation
\ No newline at end of file
diff --git a/api-sample/src/services/emails/sendEmail/templates/company-manager-invitation/0.0.1.txt b/api-sample/src/services/emails/sendEmail/templates/company-manager-invitation/0.0.1.txt
new file mode 100644
index 00000000..3ac0f5ab
--- /dev/null
+++ b/api-sample/src/services/emails/sendEmail/templates/company-manager-invitation/0.0.1.txt
@@ -0,0 +1,7 @@
+{{userRole}} Invitation
+
+{{sender}} invited you to become a {{userRole}} for {{organizationName}}
+
+{{senderMessage}}
+
+View Invitation
\ No newline at end of file
diff --git a/api-sample/src/services/emails/sendEmail/templates/email-verification/0.0.1.mjml b/api-sample/src/services/emails/sendEmail/templates/email-verification/0.0.1.mjml
new file mode 100644
index 00000000..da869eca
--- /dev/null
+++ b/api-sample/src/services/emails/sendEmail/templates/email-verification/0.0.1.mjml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+ .no-link-decoration { color: #ffffff; text-decoration: none }
+
+
+
+
+
+
+
+
+ Complete Registration
+
+
+
+
+
+ Hi {{name}}, please click link below to complete your registration.
+
+
+
+ Complete Registration
+
+
+
+
+
+
\ No newline at end of file
diff --git a/api-sample/src/services/emails/sendEmail/templates/email-verification/0.0.1.subject b/api-sample/src/services/emails/sendEmail/templates/email-verification/0.0.1.subject
new file mode 100644
index 00000000..b1325b1d
--- /dev/null
+++ b/api-sample/src/services/emails/sendEmail/templates/email-verification/0.0.1.subject
@@ -0,0 +1 @@
+Complete Registration
\ No newline at end of file
diff --git a/api-sample/src/services/emails/sendEmail/templates/email-verification/0.0.1.txt b/api-sample/src/services/emails/sendEmail/templates/email-verification/0.0.1.txt
new file mode 100644
index 00000000..ed0b1395
--- /dev/null
+++ b/api-sample/src/services/emails/sendEmail/templates/email-verification/0.0.1.txt
@@ -0,0 +1 @@
+Hi {{name}}, please click link below to complete your registration.
\ No newline at end of file
diff --git a/api-sample/src/services/emails/sendEmail/templates/invitation-status/0.0.1.mjml b/api-sample/src/services/emails/sendEmail/templates/invitation-status/0.0.1.mjml
new file mode 100644
index 00000000..986ba579
--- /dev/null
+++ b/api-sample/src/services/emails/sendEmail/templates/invitation-status/0.0.1.mjml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+ @import
+ url('https://fonts.googleapis.com/css2?family=Inter&family=Lato&family=Rubik:wght@400;500&display=swap');
+ .no-link-decoration { color: #ffffff; text-decoration: none }
+
+
+
+
+
+
+ Company Manager Invitate Accepted
+
+
+
+
+
+ {{sender}} {{invitationStatus}} your invite to become
+ {{userRole}} for
+ {{organizationName}}
+
+
+
+
+
+
+ View Invite
+
+
+
+
diff --git a/api-sample/src/services/emails/sendEmail/templates/invitation-status/0.0.1.subject b/api-sample/src/services/emails/sendEmail/templates/invitation-status/0.0.1.subject
new file mode 100644
index 00000000..7d1c0c3a
--- /dev/null
+++ b/api-sample/src/services/emails/sendEmail/templates/invitation-status/0.0.1.subject
@@ -0,0 +1 @@
+{{userRole}} Invitation Accepted
diff --git a/api-sample/src/services/emails/sendEmail/templates/invitation-status/0.0.1.txt b/api-sample/src/services/emails/sendEmail/templates/invitation-status/0.0.1.txt
new file mode 100644
index 00000000..300bd281
--- /dev/null
+++ b/api-sample/src/services/emails/sendEmail/templates/invitation-status/0.0.1.txt
@@ -0,0 +1,7 @@
+{{userRole}} Invitation Accepted
+
+{{sender}} {{invitationStatus}} your invite to become
+
+{{userRole}} for {{organizationName}}
+
+View Invitation
\ No newline at end of file
diff --git a/api-sample/src/services/emails/sendEmail/templates/new-user-organization-invitation/0.0.1.mjml b/api-sample/src/services/emails/sendEmail/templates/new-user-organization-invitation/0.0.1.mjml
new file mode 100644
index 00000000..3b4df895
--- /dev/null
+++ b/api-sample/src/services/emails/sendEmail/templates/new-user-organization-invitation/0.0.1.mjml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+ @import
+ url('https://fonts.googleapis.com/css2?family=Inter&family=Lato&family=Rubik:wght@400;500&display=swap');
+ .no-link-decoration { color: #ffffff; text-decoration: none }
+
+
+
+
+
+
+ Company Invite
+
+
+
+
+
+ {{firstName}} {{lastName}} invited you to become a
+ {{userRole}}
+ of company.
+
+
+
+ this is only included is a message has been input when the invite was
+ sent.
+
+
+
+ Below is a message from {{firstName}} {{lastName}}
+
+
+
+
+ {{senderMessage}}
+
+
+
+
+ Please click here to register and
+ accept the invitation.
+
+
+
+
+
diff --git a/api-sample/src/services/emails/sendEmail/templates/new-user-organization-invitation/0.0.1.subject b/api-sample/src/services/emails/sendEmail/templates/new-user-organization-invitation/0.0.1.subject
new file mode 100644
index 00000000..dfa7dc1c
--- /dev/null
+++ b/api-sample/src/services/emails/sendEmail/templates/new-user-organization-invitation/0.0.1.subject
@@ -0,0 +1 @@
+Company Invite
\ No newline at end of file
diff --git a/api-sample/src/services/emails/sendEmail/templates/new-user-organization-invitation/0.0.1.txt b/api-sample/src/services/emails/sendEmail/templates/new-user-organization-invitation/0.0.1.txt
new file mode 100644
index 00000000..e92e7261
--- /dev/null
+++ b/api-sample/src/services/emails/sendEmail/templates/new-user-organization-invitation/0.0.1.txt
@@ -0,0 +1,8 @@
+Company Invite
+
+{{sender}} invited you to become a {{userRole}} for {{organizationName}}
+
+Below is a message from {{sender}}
+{{senderMessage}}
+
+Follow the link to register and accept invitation {{notificationsPageUrl}}
\ No newline at end of file
diff --git a/api-sample/src/services/emails/sendEmail/templates/password-confirmation/0.0.1.mjml b/api-sample/src/services/emails/sendEmail/templates/password-confirmation/0.0.1.mjml
new file mode 100644
index 00000000..c29c17ec
--- /dev/null
+++ b/api-sample/src/services/emails/sendEmail/templates/password-confirmation/0.0.1.mjml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+ .no-link-decoration { color: #ffffff; text-decoration: none }
+
+
+
+
+
+
+
+
+ Confirmation of Account Recovery
+
+
+
+
+
+ Hi {{name}}, Your password has been successfully changed for the
+ following account;
+
+ If you didn't change your password, follow this link to reset your
+ password to a new one.
+
+
+
+ Reset Password
+
+
+
+
+
+
diff --git a/api-sample/src/services/emails/sendEmail/templates/password-confirmation/0.0.1.subject b/api-sample/src/services/emails/sendEmail/templates/password-confirmation/0.0.1.subject
new file mode 100644
index 00000000..290b5dc4
--- /dev/null
+++ b/api-sample/src/services/emails/sendEmail/templates/password-confirmation/0.0.1.subject
@@ -0,0 +1 @@
+Confirmation of Account Recovery
\ No newline at end of file
diff --git a/api-sample/src/services/emails/sendEmail/templates/password-confirmation/0.0.1.txt b/api-sample/src/services/emails/sendEmail/templates/password-confirmation/0.0.1.txt
new file mode 100644
index 00000000..6b2f9692
--- /dev/null
+++ b/api-sample/src/services/emails/sendEmail/templates/password-confirmation/0.0.1.txt
@@ -0,0 +1,12 @@
+Confirmation of Account Recovery
+
+ Hi {{name}}, Your password has been successfully changed for the following account;
+Just click the link below to get started:
+
+If you didn't change your password, follow this link to reset your password to a new one.
+In case you have any questions, feel free to get in touch at {{replyToEmail}}.
+
+Recover Password
+
+Best regards,
+Guestlist Team
diff --git a/api-sample/src/lib/services/emails/sendEmail/templates/recovery-email/0.0.1.mjml b/api-sample/src/services/emails/sendEmail/templates/recovery-email/0.0.1.mjml
similarity index 99%
rename from api-sample/src/lib/services/emails/sendEmail/templates/recovery-email/0.0.1.mjml
rename to api-sample/src/services/emails/sendEmail/templates/recovery-email/0.0.1.mjml
index a1e80a04..6758d1f9 100644
--- a/api-sample/src/lib/services/emails/sendEmail/templates/recovery-email/0.0.1.mjml
+++ b/api-sample/src/services/emails/sendEmail/templates/recovery-email/0.0.1.mjml
@@ -25,7 +25,6 @@
padding-bottom="15px"
padding-top="20px"
>
-
+
+
+
+
+
+
+
+
+
+ @import
+ url('https://fonts.googleapis.com/css2?family=Inter&family=Lato&family=Rubik:wght@400;500&display=swap');
+ .no-link-decoration { color: #ffffff; text-decoration: none }
+
+
+
+
+
+
+ Company Invite
+
+
+
+
+
+ {{firstName}} {{lastName}} invited you to become a
+ {{userRole}}
+ of company.
+
+
+
+ this is only included is a message has been input when the invite was
+ sent.
+
+
+
+ Below is a message from {{firstName}} {{lastName}}
+
+
+
+
+ {{senderMessage}}
+
+
+
+
+ Please click here to login and
+ view the invitation.
+
+
+
+
+
diff --git a/api-sample/src/services/emails/sendEmail/templates/user-organization-invitation/0.0.1.subject b/api-sample/src/services/emails/sendEmail/templates/user-organization-invitation/0.0.1.subject
new file mode 100644
index 00000000..dfa7dc1c
--- /dev/null
+++ b/api-sample/src/services/emails/sendEmail/templates/user-organization-invitation/0.0.1.subject
@@ -0,0 +1 @@
+Company Invite
\ No newline at end of file
diff --git a/api-sample/src/services/emails/sendEmail/templates/user-organization-invitation/0.0.1.txt b/api-sample/src/services/emails/sendEmail/templates/user-organization-invitation/0.0.1.txt
new file mode 100644
index 00000000..75dd3104
--- /dev/null
+++ b/api-sample/src/services/emails/sendEmail/templates/user-organization-invitation/0.0.1.txt
@@ -0,0 +1,8 @@
+Company Invite
+
+{{sender}} invited you to become a {{userRole}} for {{organizationName}}
+
+Below is a message from {{sender}}
+{{senderMessage}}
+
+Follow the link to login and view invitation {{notificationsPageUrl}}
\ No newline at end of file
diff --git a/cli/utils/writeDotEnvFile.js b/cli/utils/writeDotEnvFile.js
index 01821633..3f97a6bf 100644
--- a/cli/utils/writeDotEnvFile.js
+++ b/cli/utils/writeDotEnvFile.js
@@ -18,10 +18,9 @@ PASSWORD_SALT=SECRET_SALT
// MINIO_SECRET_KEY=
// mailgun credentials
-// MAILGUN_API_KEY=
-// MAILGUN_API_BASE_URL=
-// MAILGUN_DOMAIN=
-
+MAILGUN_API_KEY=change_me
+MAILGUN_API_BASE_URL=change_me
+MAILGUN_DOMAIN=change_me
`;
const filePath = path.join(projectRoot, "./.env");
const doesItExist = fs.ensureFileSync(filePath);
diff --git a/package.json b/package.json
index b25ef281..f2585531 100644
--- a/package.json
+++ b/package.json
@@ -48,4 +48,4 @@
"url": "https://github.com/CyprusCodes/xest/issues"
},
"homepage": "https://github.com/CyprusCodes/xest#readme"
-}
+}
\ No newline at end of file