recoder-code
Version:
🚀 AI-powered development platform - Chat with 32+ models, build projects, automate workflows. Free models included!
369 lines (302 loc) • 9.46 kB
text/typescript
/**
* User Entity
* Represents users who can publish and manage packages
*/
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany, Index } from 'typeorm';
import { Package } from './Package';
import { Download } from './Download';
import { ApiKey } from './ApiKey';
export enum UserRole {
USER = 'user',
MAINTAINER = 'maintainer',
ADMIN = 'admin',
MODERATOR = 'moderator'
}
export enum UserStatus {
ACTIVE = 'active',
SUSPENDED = 'suspended',
PENDING = 'pending',
BANNED = 'banned'
}
export class User {
id!: string;
username!: string;
email!: string;
full_name?: string;
bio?: string;
avatar_url?: string;
website?: string;
location?: string;
timezone?: string;
password_hash!: string;
npm_username?: string;
npm_email?: string;
role!: UserRole;
status!: UserStatus;
email_verified!: boolean;
is_active!: boolean;
github_username?: string;
twitter_username?: string;
preferences?: {
email_notifications: boolean;
weekly_digest: boolean;
security_alerts: boolean;
marketing_emails: boolean;
theme: 'light' | 'dark' | 'auto';
language: string;
};
permissions?: {
can_publish: boolean;
can_unpublish: boolean;
can_deprecate: boolean;
can_manage_teams: boolean;
can_moderate: boolean;
max_package_size: number;
max_packages: number;
};
stats?: {
packages_published: number;
total_downloads: number;
followers: number;
following: number;
packages_maintained: number;
};
organizations?: string[];
verification_token?: string;
verification_expires?: Date;
reset_token?: string;
reset_expires?: Date;
last_login?: Date;
last_login_ip?: string;
login_count!: number;
suspended_until?: Date;
suspension_reason?: string;
created_at!: Date;
updated_at!: Date;
// Relationships
packages!: Package[];
downloads!: Download[];
api_keys!: ApiKey[];
// Virtual properties
get is_admin(): boolean {
return this.role === UserRole.ADMIN;
}
get is_moderator(): boolean {
return this.role === UserRole.MODERATOR || this.is_admin;
}
get is_maintainer(): boolean {
return this.role === UserRole.MAINTAINER || this.is_moderator;
}
get can_publish(): boolean {
return this.permissions?.can_publish !== false && this.status === UserStatus.ACTIVE;
}
get can_manage(): boolean {
return this.permissions?.can_manage_teams !== false && this.is_maintainer;
}
get display_name(): string {
return this.full_name || this.username;
}
get npm_display_name(): string {
return this.npm_username || this.username;
}
// Methods
recordLogin(ip?: string) {
this.last_login = new Date();
this.login_count += 1;
if (ip) {
this.last_login_ip = ip;
}
}
suspend(reason: string, until?: Date) {
this.status = UserStatus.SUSPENDED;
this.suspension_reason = reason;
this.suspended_until = until;
}
unsuspend() {
this.status = UserStatus.ACTIVE;
this.suspension_reason = undefined;
this.suspended_until = undefined;
}
ban(reason: string) {
this.status = UserStatus.BANNED;
this.suspension_reason = reason;
this.is_active = false;
}
activate() {
this.status = UserStatus.ACTIVE;
this.is_active = true;
this.suspension_reason = undefined;
this.suspended_until = undefined;
}
verifyEmail() {
this.email_verified = true;
this.verification_token = undefined;
this.verification_expires = undefined;
if (this.status === UserStatus.PENDING) {
this.status = UserStatus.ACTIVE;
}
}
generateVerificationToken(): string {
const token = require('crypto').randomBytes(32).toString('hex');
this.verification_token = token;
this.verification_expires = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
return token;
}
generateResetToken(): string {
const token = require('crypto').randomBytes(32).toString('hex');
this.reset_token = token;
this.reset_expires = new Date(Date.now() + 60 * 60 * 1000); // 1 hour
return token;
}
clearResetToken() {
this.reset_token = undefined;
this.reset_expires = undefined;
}
updateStats(statsUpdate: Partial<User['stats']>) {
if (!statsUpdate) return;
const currentStats = this.stats || {
packages_published: 0,
total_downloads: 0,
followers: 0,
following: 0,
packages_maintained: 0
};
this.stats = {
packages_published: statsUpdate.packages_published ?? currentStats.packages_published,
total_downloads: statsUpdate.total_downloads ?? currentStats.total_downloads,
followers: statsUpdate.followers ?? currentStats.followers,
following: statsUpdate.following ?? currentStats.following,
packages_maintained: statsUpdate.packages_maintained ?? currentStats.packages_maintained
};
}
addOrganization(org: string) {
if (!this.organizations) {
this.organizations = [];
}
if (!this.organizations.includes(org)) {
this.organizations.push(org);
}
}
removeOrganization(org: string) {
if (this.organizations) {
this.organizations = this.organizations.filter(o => o !== org);
}
}
hasPermission(permission: keyof User['permissions']): boolean {
if (this.is_admin) return true;
return this.permissions?.[permission] === true;
}
canAccessPackage(pkg: Package): boolean {
if (this.is_admin) return true;
if (pkg.owner_id === this.id) return true;
if (pkg.organization && this.organizations?.includes(pkg.organization)) return true;
return false;
}
canModifyPackage(pkg: Package): boolean {
if (this.is_admin) return true;
if (pkg.owner_id === this.id) return true;
// Check if user is maintainer of the package
if (pkg.maintainers) {
const maintainer = pkg.maintainers.find(m =>
m.name === this.username || m.email === this.email
);
if (maintainer) return true;
}
return false;
}
getRateLimits(): { requests: number; window: number } {
if (this.is_admin) {
return { requests: 10000, window: 3600 }; // 10k/hour
}
if (this.is_maintainer) {
return { requests: 5000, window: 3600 }; // 5k/hour
}
return { requests: 1000, window: 3600 }; // 1k/hour
}
toNpmFormat(): any {
return {
_id: `org.couchdb.user:${this.npm_username || this.username}`,
name: this.npm_username || this.username,
email: this.npm_email || this.email,
type: 'user',
roles: [],
date: this.created_at.toISOString()
};
}
toPublicFormat(): any {
return {
id: this.id,
username: this.username,
full_name: this.full_name,
bio: this.bio,
avatar_url: this.avatar_url,
website: this.website,
location: this.location,
github_username: this.github_username,
twitter_username: this.twitter_username,
created_at: this.created_at,
stats: this.stats,
organizations: this.organizations || []
};
}
toPrivateFormat(): any {
return {
...this.toPublicFormat(),
email: this.email,
email_verified: this.email_verified,
role: this.role,
status: this.status,
preferences: this.preferences,
permissions: this.permissions,
last_login: this.last_login
};
}
}