admin area

This commit is contained in:
2026-01-01 12:10:35 +01:00
parent 02c0cc44e1
commit f001df3744
68 changed files with 10324 additions and 32 deletions

View File

@@ -0,0 +1,180 @@
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';
import type { IAdminUserRepository } from '@core/admin/domain/repositories/IAdminUserRepository';
import { AuthorizationService } from '@core/admin/domain/services/AuthorizationService';
import { UserId } from '@core/admin/domain/value-objects/UserId';
export interface DashboardStatsResult {
totalUsers: number;
activeUsers: number;
suspendedUsers: number;
deletedUsers: number;
systemAdmins: number;
recentLogins: number;
newUsersToday: number;
userGrowth: {
label: string;
value: number;
color: string;
}[];
roleDistribution: {
label: string;
value: number;
color: string;
}[];
statusDistribution: {
active: number;
suspended: number;
deleted: number;
};
activityTimeline: {
date: string;
newUsers: number;
logins: number;
}[];
}
export type GetDashboardStatsInput = {
actorId: string;
};
export type GetDashboardStatsErrorCode = 'AUTHORIZATION_ERROR' | 'REPOSITORY_ERROR';
export type GetDashboardStatsApplicationError = ApplicationErrorCode<GetDashboardStatsErrorCode, { message: string; details?: unknown }>;
export class GetDashboardStatsUseCase {
constructor(
private readonly adminUserRepo: IAdminUserRepository,
private readonly output: UseCaseOutputPort<DashboardStatsResult>,
) {}
async execute(input: GetDashboardStatsInput): Promise<Result<void, GetDashboardStatsApplicationError>> {
try {
// Get actor (current user)
const actor = await this.adminUserRepo.findById(UserId.fromString(input.actorId));
if (!actor) {
return Result.err({
code: 'AUTHORIZATION_ERROR',
details: { message: 'Actor not found' },
});
}
// Check authorization
if (!AuthorizationService.canListUsers(actor)) {
return Result.err({
code: 'AUTHORIZATION_ERROR',
details: { message: 'User is not authorized to view dashboard' },
});
}
// Get all users
const allUsersResult = await this.adminUserRepo.list();
const allUsers = allUsersResult.users;
// Calculate basic stats
const totalUsers = allUsers.length;
const activeUsers = allUsers.filter(u => u.status.value === 'active').length;
const suspendedUsers = allUsers.filter(u => u.status.value === 'suspended').length;
const deletedUsers = allUsers.filter(u => u.status.value === 'deleted').length;
const systemAdmins = allUsers.filter(u => u.isSystemAdmin()).length;
// Recent logins (last 24 hours)
const oneDayAgo = new Date();
oneDayAgo.setDate(oneDayAgo.getDate() - 1);
const recentLogins = allUsers.filter(u => u.lastLoginAt && u.lastLoginAt > oneDayAgo).length;
// New users today
const today = new Date();
today.setHours(0, 0, 0, 0);
const newUsersToday = allUsers.filter(u => u.createdAt > today).length;
// Role distribution
const roleCounts: Record<string, number> = {};
allUsers.forEach(user => {
user.roles.forEach(role => {
const roleValue = role.value;
roleCounts[roleValue] = (roleCounts[roleValue] || 0) + 1;
});
});
const roleDistribution = Object.entries(roleCounts).map(([role, count]) => ({
label: role.charAt(0).toUpperCase() + role.slice(1),
value: count,
color: role === 'owner' ? 'text-purple-500' : role === 'admin' ? 'text-blue-500' : 'text-gray-500',
}));
// User growth (last 7 days)
const userGrowth: DashboardStatsResult['userGrowth'] = [];
for (let i = 6; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
const dateStr = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
const count = allUsers.filter(u => {
const userDate = new Date(u.createdAt);
return userDate.toDateString() === date.toDateString();
}).length;
userGrowth.push({
label: dateStr,
value: count,
color: 'text-primary-blue',
});
}
// Activity timeline (last 7 days)
const activityTimeline: DashboardStatsResult['activityTimeline'] = [];
for (let i = 6; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
const dateStr = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
const newUsers = allUsers.filter(u => {
const userDate = new Date(u.createdAt);
return userDate.toDateString() === date.toDateString();
}).length;
const logins = allUsers.filter(u => {
const loginDate = u.lastLoginAt;
return loginDate && loginDate.toDateString() === date.toDateString();
}).length;
activityTimeline.push({
date: dateStr,
newUsers,
logins,
});
}
const result: DashboardStatsResult = {
totalUsers,
activeUsers,
suspendedUsers,
deletedUsers,
systemAdmins,
recentLogins,
newUsersToday,
userGrowth,
roleDistribution,
statusDistribution: {
active: activeUsers,
suspended: suspendedUsers,
deleted: deletedUsers,
},
activityTimeline,
};
this.output.present(result);
return Result.ok(undefined);
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to get dashboard stats';
return Result.err({
code: 'REPOSITORY_ERROR',
details: { message },
});
}
}
}