This commit is contained in:
Flatlogic Bot 2025-05-05 08:44:55 +00:00
parent 4dfa230e6f
commit cfd5c7ee20
6 changed files with 166 additions and 4 deletions

5
.gitignore vendored
View File

@ -1,3 +1,8 @@
node_modules/
*/node_modules/
*/build/
**/node_modules/
**/build/
.DS_Store
.env

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,44 @@
'use strict';
const { invitations } = require('../models');
const { Op } = require('sequelize');
/**
* Create a new invitation
* @param {Object} data - invitation fields: email, role, organizationId, token, expiresAt
* @returns {Promise<Invitation>}
*/
async function createInvitation(data) {
return invitations.create(data);
}
/**
* Find invitation by token
* @param {string} token
* @returns {Promise<Invitation|null>}
*/
async function findByToken(token) {
return invitations.findOne({
where: {
token,
expiresAt: { [Op.gt]: new Date() },
acceptedAt: { [Op.is]: null },
},
});
}
/**
* Mark invitation as accepted
* @param {Invitation} invite
* @returns {Promise<Invitation>}
*/
async function acceptInvitation(invite) {
invite.acceptedAt = new Date();
return invite.save();
}
module.exports = {
createInvitation,
findByToken,
acceptInvitation,
};

View File

@ -0,0 +1,60 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('invitations', {
id: {
type: Sequelize.UUID,
defaultValue: Sequelize.literal('uuid_generate_v4()'),
primaryKey: true,
},
email: {
type: Sequelize.STRING,
allowNull: false,
},
role: {
type: Sequelize.STRING,
allowNull: false,
},
organizationId: {
type: Sequelize.UUID,
allowNull: false,
},
token: {
type: Sequelize.STRING,
unique: true,
allowNull: false,
},
expiresAt: {
type: Sequelize.DATE,
allowNull: false,
},
acceptedAt: {
type: Sequelize.DATE,
allowNull: true,
},
createdAt: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.literal('NOW()'),
},
updatedAt: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.literal('NOW()'),
},
});
await queryInterface.addConstraint('invitations', {
fields: ['organizationId'],
type: 'foreign key',
name: 'fk_invitations_organization',
references: { table: 'organizations', field: 'id' },
onDelete: 'cascade',
onUpdate: 'cascade',
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('invitations');
},
};

View File

@ -0,0 +1,51 @@
'use strict';
module.exports = (sequelize, DataTypes) => {
const Invitation = sequelize.define(
'Invitation',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
email: {
type: DataTypes.STRING,
allowNull: false,
validate: { isEmail: true },
},
role: {
type: DataTypes.STRING,
allowNull: false,
},
organizationId: {
type: DataTypes.UUID,
allowNull: false,
},
token: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
expiresAt: {
type: DataTypes.DATE,
allowNull: false,
},
acceptedAt: {
type: DataTypes.DATE,
},
},
{
tableName: 'invitations',
}
);
Invitation.associate = (models) => {
Invitation.belongsTo(models.organizations, {
foreignKey: 'organizationId',
onDelete: 'CASCADE',
});
};
return Invitation;
};

View File

@ -46,6 +46,7 @@ const rolesRoutes = require('./routes/roles');
const permissionsRoutes = require('./routes/permissions');
const organizationsRoutes = require('./routes/organizations');
const invitationsRoutes = require('./routes/invitations');
const getBaseUrl = (url) => {
if (!url) return '';
@ -195,6 +196,8 @@ app.use(
passport.authenticate('jwt', { session: false }),
organizationsRoutes,
);
app.use('/api/invitations', passport.authenticate('jwt', { session: false }), invitationsRoutes);
app.use(
'/api/openai',