Forced merge: merge ai-dev into master
This commit is contained in:
commit
8436f0dee7
5
.gitignore
vendored
5
.gitignore
vendored
@ -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
44
backend/src/db/api/invitations.js
Normal file
44
backend/src/db/api/invitations.js
Normal 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,
|
||||
};
|
||||
@ -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');
|
||||
},
|
||||
};
|
||||
51
backend/src/db/models/invitations.js
Normal file
51
backend/src/db/models/invitations.js
Normal 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;
|
||||
};
|
||||
@ -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',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user