184 lines
6.0 KiB
TypeScript
184 lines
6.0 KiB
TypeScript
import type { AdminUser } from '@core/admin/domain/entities/AdminUser';
|
|
import type { AdminUserRepository } from '@core/admin/domain/repositories/AdminUserRepository';
|
|
import { AuthorizationService } from '@core/admin/domain/services/AuthorizationService';
|
|
import { UserId } from '@core/admin/domain/value-objects/UserId';
|
|
import type { UserRole } from '@core/admin/domain/value-objects/UserRole';
|
|
import { Result } from '@core/shared/domain/Result';
|
|
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
|
|
|
|
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: AdminUserRepository,
|
|
) {}
|
|
|
|
async execute(input: GetDashboardStatsInput): Promise<Result<DashboardStatsResult, 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: AdminUser) => u.status.value === 'active').length;
|
|
const suspendedUsers = allUsers.filter((u: AdminUser) => u.status.value === 'suspended').length;
|
|
const deletedUsers = allUsers.filter((u: AdminUser) => u.status.value === 'deleted').length;
|
|
const systemAdmins = allUsers.filter((u: AdminUser) => u.isSystemAdmin()).length;
|
|
|
|
// Recent logins (last 24 hours)
|
|
const oneDayAgo = new Date();
|
|
oneDayAgo.setDate(oneDayAgo.getDate() - 1);
|
|
const recentLogins = allUsers.filter((u: AdminUser) => u.lastLoginAt && u.lastLoginAt > oneDayAgo).length;
|
|
|
|
// New users today
|
|
const today = new Date();
|
|
today.setHours(0, 0, 0, 0);
|
|
const newUsersToday = allUsers.filter((u: AdminUser) => u.createdAt > today).length;
|
|
|
|
// Role distribution
|
|
const roleCounts: Record<string, number> = {};
|
|
allUsers.forEach((user: AdminUser) => {
|
|
user.roles.forEach((role: UserRole) => {
|
|
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'] = [];
|
|
if (allUsers.length > 0) {
|
|
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: AdminUser) => {
|
|
const userDate = 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'] = [];
|
|
if (allUsers.length > 0) {
|
|
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: AdminUser) => {
|
|
const userDate = u.createdAt;
|
|
return userDate.toDateString() === date.toDateString();
|
|
}).length;
|
|
|
|
const logins = allUsers.filter((u: AdminUser) => {
|
|
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,
|
|
};
|
|
|
|
return Result.ok(result);
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : 'Failed to get dashboard stats';
|
|
|
|
return Result.err({
|
|
code: 'REPOSITORY_ERROR',
|
|
details: { message },
|
|
});
|
|
}
|
|
}
|
|
}
|