- Added SettingsComponent for user profile management, including display name and password change functionality. - Introduced TokensComponent for managing API tokens, including creation and revocation. - Created LayoutComponent for consistent application layout with navigation and user information. - Established main application structure in index.html and main.ts. - Integrated Tailwind CSS for styling and responsive design. - Configured TypeScript settings for strict type checking and module resolution.
261 lines
7.3 KiB
TypeScript
261 lines
7.3 KiB
TypeScript
/**
|
|
* User API handlers
|
|
*/
|
|
|
|
import type { IApiContext, IApiResponse } from '../router.ts';
|
|
import { PermissionService } from '../../services/permission.service.ts';
|
|
import { AuthService } from '../../services/auth.service.ts';
|
|
import { User } from '../../models/user.ts';
|
|
|
|
export class UserApi {
|
|
private permissionService: PermissionService;
|
|
|
|
constructor(permissionService: PermissionService) {
|
|
this.permissionService = permissionService;
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/users
|
|
*/
|
|
public async list(ctx: IApiContext): Promise<IApiResponse> {
|
|
if (!ctx.actor?.userId) {
|
|
return { status: 401, body: { error: 'Authentication required' } };
|
|
}
|
|
|
|
// Only system admins can list all users
|
|
if (!ctx.actor.user?.isSystemAdmin) {
|
|
return { status: 403, body: { error: 'System admin access required' } };
|
|
}
|
|
|
|
try {
|
|
const users = await User.getInstances({});
|
|
return {
|
|
status: 200,
|
|
body: {
|
|
users: users.map((u) => ({
|
|
id: u.id,
|
|
email: u.email,
|
|
username: u.username,
|
|
displayName: u.displayName,
|
|
isSystemAdmin: u.isSystemAdmin,
|
|
isActive: u.isActive,
|
|
createdAt: u.createdAt,
|
|
lastLoginAt: u.lastLoginAt,
|
|
})),
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error('[UserApi] List error:', error);
|
|
return { status: 500, body: { error: 'Failed to list users' } };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/v1/users/:id
|
|
*/
|
|
public async get(ctx: IApiContext): Promise<IApiResponse> {
|
|
if (!ctx.actor?.userId) {
|
|
return { status: 401, body: { error: 'Authentication required' } };
|
|
}
|
|
|
|
const { id } = ctx.params;
|
|
|
|
// Users can view their own profile, admins can view any
|
|
if (id !== ctx.actor.userId && !ctx.actor.user?.isSystemAdmin) {
|
|
return { status: 403, body: { error: 'Access denied' } };
|
|
}
|
|
|
|
try {
|
|
const user = await User.findById(id);
|
|
if (!user) {
|
|
return { status: 404, body: { error: 'User not found' } };
|
|
}
|
|
|
|
return {
|
|
status: 200,
|
|
body: {
|
|
id: user.id,
|
|
email: user.email,
|
|
username: user.username,
|
|
displayName: user.displayName,
|
|
avatarUrl: user.avatarUrl,
|
|
isSystemAdmin: user.isSystemAdmin,
|
|
isActive: user.isActive,
|
|
createdAt: user.createdAt,
|
|
lastLoginAt: user.lastLoginAt,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error('[UserApi] Get error:', error);
|
|
return { status: 500, body: { error: 'Failed to get user' } };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/v1/users
|
|
*/
|
|
public async create(ctx: IApiContext): Promise<IApiResponse> {
|
|
if (!ctx.actor?.userId) {
|
|
return { status: 401, body: { error: 'Authentication required' } };
|
|
}
|
|
|
|
// Only system admins can create users
|
|
if (!ctx.actor.user?.isSystemAdmin) {
|
|
return { status: 403, body: { error: 'System admin access required' } };
|
|
}
|
|
|
|
try {
|
|
const body = await ctx.request.json();
|
|
const { email, username, password, displayName, isSystemAdmin } = body;
|
|
|
|
if (!email || !username || !password) {
|
|
return {
|
|
status: 400,
|
|
body: { error: 'Email, username, and password are required' },
|
|
};
|
|
}
|
|
|
|
// Check if email already exists
|
|
const existing = await User.findByEmail(email);
|
|
if (existing) {
|
|
return { status: 409, body: { error: 'Email already in use' } };
|
|
}
|
|
|
|
// Check if username already exists
|
|
const existingUsername = await User.findByUsername(username);
|
|
if (existingUsername) {
|
|
return { status: 409, body: { error: 'Username already in use' } };
|
|
}
|
|
|
|
// Hash password
|
|
const passwordHash = await AuthService.hashPassword(password);
|
|
|
|
// Create user
|
|
const user = new User();
|
|
user.id = await User.getNewId();
|
|
user.email = email;
|
|
user.username = username;
|
|
user.passwordHash = passwordHash;
|
|
user.displayName = displayName || username;
|
|
user.isSystemAdmin = isSystemAdmin || false;
|
|
user.isActive = true;
|
|
user.createdAt = new Date();
|
|
|
|
await user.save();
|
|
|
|
return {
|
|
status: 201,
|
|
body: {
|
|
id: user.id,
|
|
email: user.email,
|
|
username: user.username,
|
|
displayName: user.displayName,
|
|
isSystemAdmin: user.isSystemAdmin,
|
|
createdAt: user.createdAt,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error('[UserApi] Create error:', error);
|
|
return { status: 500, body: { error: 'Failed to create user' } };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PUT /api/v1/users/:id
|
|
*/
|
|
public async update(ctx: IApiContext): Promise<IApiResponse> {
|
|
if (!ctx.actor?.userId) {
|
|
return { status: 401, body: { error: 'Authentication required' } };
|
|
}
|
|
|
|
const { id } = ctx.params;
|
|
|
|
// Users can update their own profile, admins can update any
|
|
if (id !== ctx.actor.userId && !ctx.actor.user?.isSystemAdmin) {
|
|
return { status: 403, body: { error: 'Access denied' } };
|
|
}
|
|
|
|
try {
|
|
const user = await User.findById(id);
|
|
if (!user) {
|
|
return { status: 404, body: { error: 'User not found' } };
|
|
}
|
|
|
|
const body = await ctx.request.json();
|
|
const { displayName, avatarUrl, password, isActive, isSystemAdmin } = body;
|
|
|
|
if (displayName !== undefined) user.displayName = displayName;
|
|
if (avatarUrl !== undefined) user.avatarUrl = avatarUrl;
|
|
|
|
// Only admins can change these
|
|
if (ctx.actor.user?.isSystemAdmin) {
|
|
if (isActive !== undefined) user.isActive = isActive;
|
|
if (isSystemAdmin !== undefined) user.isSystemAdmin = isSystemAdmin;
|
|
}
|
|
|
|
// Password change
|
|
if (password) {
|
|
user.passwordHash = await AuthService.hashPassword(password);
|
|
}
|
|
|
|
await user.save();
|
|
|
|
return {
|
|
status: 200,
|
|
body: {
|
|
id: user.id,
|
|
email: user.email,
|
|
username: user.username,
|
|
displayName: user.displayName,
|
|
avatarUrl: user.avatarUrl,
|
|
isSystemAdmin: user.isSystemAdmin,
|
|
isActive: user.isActive,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
console.error('[UserApi] Update error:', error);
|
|
return { status: 500, body: { error: 'Failed to update user' } };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DELETE /api/v1/users/:id
|
|
*/
|
|
public async delete(ctx: IApiContext): Promise<IApiResponse> {
|
|
if (!ctx.actor?.userId) {
|
|
return { status: 401, body: { error: 'Authentication required' } };
|
|
}
|
|
|
|
// Only system admins can delete users
|
|
if (!ctx.actor.user?.isSystemAdmin) {
|
|
return { status: 403, body: { error: 'System admin access required' } };
|
|
}
|
|
|
|
const { id } = ctx.params;
|
|
|
|
// Cannot delete yourself
|
|
if (id === ctx.actor.userId) {
|
|
return { status: 400, body: { error: 'Cannot delete your own account' } };
|
|
}
|
|
|
|
try {
|
|
const user = await User.findById(id);
|
|
if (!user) {
|
|
return { status: 404, body: { error: 'User not found' } };
|
|
}
|
|
|
|
// Soft delete - deactivate instead of removing
|
|
user.isActive = false;
|
|
await user.save();
|
|
|
|
return {
|
|
status: 200,
|
|
body: { message: 'User deactivated successfully' },
|
|
};
|
|
} catch (error) {
|
|
console.error('[UserApi] Delete error:', error);
|
|
return { status: 500, body: { error: 'Failed to delete user' } };
|
|
}
|
|
}
|
|
}
|