v2.0
This commit is contained in:
parent
4dfa230e6f
commit
cfd5c7ee20
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,8 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
*/node_modules/
|
*/node_modules/
|
||||||
*/build/
|
*/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 permissionsRoutes = require('./routes/permissions');
|
||||||
|
|
||||||
const organizationsRoutes = require('./routes/organizations');
|
const organizationsRoutes = require('./routes/organizations');
|
||||||
|
const invitationsRoutes = require('./routes/invitations');
|
||||||
|
|
||||||
const getBaseUrl = (url) => {
|
const getBaseUrl = (url) => {
|
||||||
if (!url) return '';
|
if (!url) return '';
|
||||||
@ -195,6 +196,8 @@ app.use(
|
|||||||
passport.authenticate('jwt', { session: false }),
|
passport.authenticate('jwt', { session: false }),
|
||||||
organizationsRoutes,
|
organizationsRoutes,
|
||||||
);
|
);
|
||||||
|
app.use('/api/invitations', passport.authenticate('jwt', { session: false }), invitationsRoutes);
|
||||||
|
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
'/api/openai',
|
'/api/openai',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user