feat: implement account settings and API tokens management

- 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.
This commit is contained in:
2025-11-27 22:15:38 +00:00
parent a6c6ea1393
commit ab88ac896f
71 changed files with 9446 additions and 0 deletions

260
ts/api/handlers/user.api.ts Normal file
View File

@@ -0,0 +1,260 @@
/**
* 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' } };
}
}
}