update
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
/**
|
||||
* UserInvitation represents an invitation to join one or more organizations.
|
||||
*
|
||||
* Key characteristics:
|
||||
* - Unique by email (multiple orgs can share the same invitation)
|
||||
* - Converts to real User on registration
|
||||
* - Can fold into existing user if they add the email as secondary
|
||||
* - Auto-expires after 90 days
|
||||
*/
|
||||
@plugins.smartdata.Manager()
|
||||
export class UserInvitation extends plugins.smartdata.SmartDataDbDoc<
|
||||
UserInvitation,
|
||||
plugins.idpInterfaces.data.IUserInvitation
|
||||
> {
|
||||
// STATIC
|
||||
public static readonly EXPIRY_DAYS = 90;
|
||||
|
||||
public static generateToken(): string {
|
||||
return plugins.smartunique.shortId() + '-' + plugins.smartunique.shortId();
|
||||
}
|
||||
|
||||
public static async createNewInvitation(
|
||||
email: string,
|
||||
organizationId: string,
|
||||
invitedByUserId: string,
|
||||
roles: string[]
|
||||
): Promise<UserInvitation> {
|
||||
const invitation = new UserInvitation();
|
||||
invitation.id = plugins.smartunique.shortId();
|
||||
const now = Date.now();
|
||||
const expiresAt = now + (UserInvitation.EXPIRY_DAYS * 24 * 60 * 60 * 1000);
|
||||
|
||||
invitation.data = {
|
||||
email: email.toLowerCase().trim(),
|
||||
token: UserInvitation.generateToken(),
|
||||
status: 'pending',
|
||||
createdAt: now,
|
||||
expiresAt: expiresAt,
|
||||
organizationRefs: [{
|
||||
organizationId,
|
||||
invitedByUserId,
|
||||
invitedAt: now,
|
||||
roles,
|
||||
}],
|
||||
};
|
||||
|
||||
await invitation.save();
|
||||
return invitation;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
@plugins.smartdata.unI()
|
||||
id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public data: plugins.idpInterfaces.data.IUserInvitation['data'];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add another organization to this invitation
|
||||
*/
|
||||
public async addOrganization(
|
||||
organizationId: string,
|
||||
invitedByUserId: string,
|
||||
roles: string[]
|
||||
): Promise<void> {
|
||||
// Check if org already exists
|
||||
const existingRef = this.data.organizationRefs.find(
|
||||
ref => ref.organizationId === organizationId
|
||||
);
|
||||
|
||||
if (existingRef) {
|
||||
// Update roles for existing org ref
|
||||
existingRef.roles = roles;
|
||||
existingRef.invitedAt = Date.now();
|
||||
existingRef.invitedByUserId = invitedByUserId;
|
||||
} else {
|
||||
// Add new org ref
|
||||
this.data.organizationRefs.push({
|
||||
organizationId,
|
||||
invitedByUserId,
|
||||
invitedAt: Date.now(),
|
||||
roles,
|
||||
});
|
||||
}
|
||||
|
||||
await this.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an organization from this invitation
|
||||
*/
|
||||
public async removeOrganization(organizationId: string): Promise<void> {
|
||||
this.data.organizationRefs = this.data.organizationRefs.filter(
|
||||
ref => ref.organizationId !== organizationId
|
||||
);
|
||||
|
||||
// If no more org refs, cancel the invitation
|
||||
if (this.data.organizationRefs.length === 0) {
|
||||
this.data.status = 'cancelled';
|
||||
}
|
||||
|
||||
await this.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if invitation is expired
|
||||
*/
|
||||
public isExpired(): boolean {
|
||||
return Date.now() > this.data.expiresAt || this.data.status === 'expired';
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark invitation as accepted and record the user ID
|
||||
*/
|
||||
public async accept(userId: string): Promise<void> {
|
||||
this.data.status = 'accepted';
|
||||
this.data.acceptedAt = Date.now();
|
||||
this.data.convertedToUserId = userId;
|
||||
await this.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate token and extend expiry (for resend)
|
||||
*/
|
||||
public async regenerateToken(): Promise<void> {
|
||||
this.data.token = UserInvitation.generateToken();
|
||||
this.data.expiresAt = Date.now() + (UserInvitation.EXPIRY_DAYS * 24 * 60 * 60 * 1000);
|
||||
await this.save();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user