feat: plantilla base workspace SaaS
This commit is contained in:
8
frontend/src/lib/api/dashboard/admin/index.ts
Normal file
8
frontend/src/lib/api/dashboard/admin/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Exportaciones centralizadas de APIs de administración
|
||||
*/
|
||||
|
||||
export * from './permissions';
|
||||
export * from './roles';
|
||||
export * from './role-permissions';
|
||||
export * from './user-roles';
|
||||
110
frontend/src/lib/api/dashboard/admin/permissions.ts
Normal file
110
frontend/src/lib/api/dashboard/admin/permissions.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* API para gestión de permisos del sistema
|
||||
*/
|
||||
|
||||
import { api } from '$lib/api';
|
||||
|
||||
export interface Permission {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
module: string;
|
||||
action: string;
|
||||
is_active: boolean;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface CreatePermissionData {
|
||||
code?: string;
|
||||
description?: string;
|
||||
module: string;
|
||||
action: string;
|
||||
is_active?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdatePermissionData {
|
||||
code?: string;
|
||||
description?: string;
|
||||
module?: string;
|
||||
action?: string;
|
||||
is_active?: boolean;
|
||||
}
|
||||
|
||||
export interface PermissionListResponse {
|
||||
items: Permission[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
}
|
||||
|
||||
export const permissionsAPI = {
|
||||
/**
|
||||
* Listar permisos con filtros
|
||||
*/
|
||||
async list(params?: {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
module?: string;
|
||||
action?: string;
|
||||
is_active?: boolean;
|
||||
search?: string;
|
||||
}): Promise<PermissionListResponse> {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (params?.page) queryParams.set('page', params.page.toString());
|
||||
if (params?.page_size) queryParams.set('page_size', params.page_size.toString());
|
||||
if (params?.module) queryParams.set('module', params.module);
|
||||
if (params?.action) queryParams.set('action', params.action);
|
||||
if (params?.search) queryParams.set('search', params.search);
|
||||
const query = queryParams.toString();
|
||||
const response = await api.get(`/v1/core/permissions/${query ? '?' + query : ''}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtener un permiso por ID
|
||||
*/
|
||||
async getById(id: number): Promise<Permission> {
|
||||
const response = await api.get(`/v1/core/permissions/${id}/`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Crear un nuevo permiso
|
||||
*/
|
||||
async create(data: CreatePermissionData): Promise<Permission> {
|
||||
const response = await api.post('/v1/core/permissions/', data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Actualizar un permiso
|
||||
*/
|
||||
async update(id: number, data: UpdatePermissionData): Promise<Permission> {
|
||||
const response = await api.put(`/v1/core/permissions/${id}/`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Eliminar un permiso
|
||||
*/
|
||||
async delete(id: number): Promise<void> {
|
||||
await api.delete(`/v1/core/permissions/${id}/`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtener módulos únicos
|
||||
*/
|
||||
async getModules(): Promise<string[]> {
|
||||
const response = await api.get('/v1/core/permissions/modules/');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtener acciones únicas
|
||||
*/
|
||||
async getActions(): Promise<string[]> {
|
||||
const response = await api.get('/v1/core/permissions/actions/');
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
76
frontend/src/lib/api/dashboard/admin/role-permissions.ts
Normal file
76
frontend/src/lib/api/dashboard/admin/role-permissions.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* API para gestión de permisos de roles
|
||||
*/
|
||||
|
||||
import { api } from '$lib/api';
|
||||
|
||||
export interface RolePermission {
|
||||
id: number;
|
||||
company_role_id: number;
|
||||
permission_id: number;
|
||||
granted_at?: string;
|
||||
granted_by?: number;
|
||||
tenant_id: number;
|
||||
permission?: {
|
||||
id: number;
|
||||
code: string;
|
||||
module: string;
|
||||
action: string;
|
||||
description?: string;
|
||||
is_active: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AssignPermissionData {
|
||||
permission_id: number;
|
||||
}
|
||||
|
||||
export interface RolePermissionsResponse {
|
||||
role_id: number;
|
||||
permissions: RolePermission[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export const rolePermissionsAPI = {
|
||||
/**
|
||||
* Listar todos los permisos asignados a un rol
|
||||
*/
|
||||
async listByRole(roleId: number, companyId: number): Promise<RolePermissionsResponse> {
|
||||
const response = await api.get(`/v1/core/permissions/roles/${roleId}/permissions?company_id=${companyId}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Asignar un permiso a un rol
|
||||
*/
|
||||
async assign(
|
||||
roleId: number,
|
||||
companyId: number,
|
||||
data: AssignPermissionData
|
||||
): Promise<RolePermission> {
|
||||
const response = await api.post(`/v1/core/permissions/roles/${roleId}/permissions?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remover un permiso de un rol
|
||||
*/
|
||||
async remove(roleId: number, permissionId: number, companyId: number): Promise<void> {
|
||||
await api.delete(`/v1/core/permissions/roles/${roleId}/permissions/${permissionId}?company_id=${companyId}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Asignar múltiples permisos a un rol
|
||||
*/
|
||||
async assignMultiple(
|
||||
roleId: number,
|
||||
companyId: number,
|
||||
permissionIds: number[]
|
||||
): Promise<RolePermission[]> {
|
||||
const response = await api.post(
|
||||
`/v1/core/permissions/roles/${roleId}/permissions/batch?company_id=${companyId}`,
|
||||
{ permission_ids: permissionIds }
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
89
frontend/src/lib/api/dashboard/admin/roles.ts
Normal file
89
frontend/src/lib/api/dashboard/admin/roles.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* API para gestión de roles por compañía
|
||||
*/
|
||||
|
||||
import { api, type ApiResponse } from '$lib/api';
|
||||
|
||||
export interface CompanyRole {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
description?: string;
|
||||
is_active: boolean;
|
||||
company_id: number;
|
||||
tenant_id: number;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface CreateRoleData {
|
||||
name: string;
|
||||
code: string;
|
||||
description?: string;
|
||||
is_active?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateRoleData {
|
||||
name?: string;
|
||||
code?: string;
|
||||
description?: string;
|
||||
is_active?: boolean;
|
||||
}
|
||||
|
||||
export interface RoleListResponse {
|
||||
items: CompanyRole[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
}
|
||||
|
||||
export const rolesAPI = {
|
||||
/**
|
||||
* Listar roles con filtros
|
||||
*/
|
||||
async list(
|
||||
companyId: number,
|
||||
params?: {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
is_active?: boolean;
|
||||
search?: string;
|
||||
}
|
||||
): Promise<ApiResponse<RoleListResponse>> {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.set('company_id', companyId.toString());
|
||||
if (params?.page) queryParams.set('page', params.page.toString());
|
||||
if (params?.page_size) queryParams.set('page_size', params.page_size.toString());
|
||||
if (params?.is_active !== undefined) queryParams.set('is_active', params.is_active.toString());
|
||||
if (params?.search) queryParams.set('search', params.search);
|
||||
return api.get(`/v1/core/permissions/roles?${queryParams.toString()}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtener un rol por ID
|
||||
*/
|
||||
async getById(id: number, companyId: number): Promise<ApiResponse<CompanyRole>> {
|
||||
return api.get(`/v1/core/permissions/roles/${id}?company_id=${companyId}`);
|
||||
},
|
||||
|
||||
/**
|
||||
* Crear un nuevo rol
|
||||
*/
|
||||
async create(companyId: number, data: CreateRoleData): Promise<ApiResponse<CompanyRole>> {
|
||||
return api.post(`/v1/core/permissions/roles?company_id=${companyId}`, data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Actualizar un rol
|
||||
*/
|
||||
async update(id: number, companyId: number, data: UpdateRoleData): Promise<ApiResponse<CompanyRole>> {
|
||||
return api.patch(`/v1/core/permissions/roles/${id}?company_id=${companyId}`, data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Eliminar un rol
|
||||
*/
|
||||
async delete(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return api.delete(`/v1/core/permissions/roles/${id}?company_id=${companyId}`);
|
||||
}
|
||||
};
|
||||
108
frontend/src/lib/api/dashboard/admin/user-permissions.ts
Normal file
108
frontend/src/lib/api/dashboard/admin/user-permissions.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* API para gestión de permisos individuales de usuario
|
||||
*/
|
||||
|
||||
import { api } from '$lib/api';
|
||||
import type { Permission } from './permissions';
|
||||
|
||||
export interface UserPermission {
|
||||
id: number;
|
||||
user_id: string;
|
||||
permission_id: number;
|
||||
company_id: number;
|
||||
tenant_id: number;
|
||||
is_granted: boolean;
|
||||
is_active: boolean;
|
||||
assigned_by?: string;
|
||||
expires_at?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
permission?: Permission;
|
||||
}
|
||||
|
||||
export interface AssignUserPermissionData {
|
||||
user_id: string;
|
||||
permission_id: number;
|
||||
is_granted?: boolean;
|
||||
expires_at?: string;
|
||||
}
|
||||
|
||||
export interface UserPermissionsListResponse {
|
||||
items: UserPermission[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface EffectiveUserPermissions {
|
||||
user_id: string;
|
||||
company_id: number;
|
||||
role_permissions: Permission[];
|
||||
granted_permissions: Permission[];
|
||||
revoked_permissions: Permission[];
|
||||
effective_permissions: Permission[];
|
||||
}
|
||||
|
||||
export const userPermissionsAPI = {
|
||||
/**
|
||||
* Obtener permisos individuales de un usuario
|
||||
*/
|
||||
async getIndividual(userId: string, companyId: number): Promise<UserPermissionsListResponse> {
|
||||
const response = await api.get(
|
||||
`/v1/core/permissions/users/${userId}/permissions?company_id=${companyId}`
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtener permisos efectivos de un usuario (roles + individuales - revocados)
|
||||
*/
|
||||
async getEffective(userId: string, companyId: number): Promise<EffectiveUserPermissions> {
|
||||
const response = await api.get(
|
||||
`/v1/core/permissions/users/${userId}/permissions/effective?company_id=${companyId}`
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Asignar un permiso individual a un usuario
|
||||
*/
|
||||
async assign(
|
||||
userId: string,
|
||||
companyId: number,
|
||||
data: Omit<AssignUserPermissionData, 'user_id'>
|
||||
): Promise<UserPermission> {
|
||||
const response = await api.post(
|
||||
`/v1/core/permissions/users/${userId}/permissions?company_id=${companyId}`,
|
||||
data
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Conceder un permiso extra a un usuario
|
||||
*/
|
||||
async grant(userId: string, companyId: number, permissionId: number): Promise<UserPermission> {
|
||||
return this.assign(userId, companyId, {
|
||||
permission_id: permissionId,
|
||||
is_granted: true
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Revocar un permiso específico (aunque venga del rol)
|
||||
*/
|
||||
async revoke(userId: string, companyId: number, permissionId: number): Promise<UserPermission> {
|
||||
return this.assign(userId, companyId, {
|
||||
permission_id: permissionId,
|
||||
is_granted: false
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Eliminar un permiso individual
|
||||
*/
|
||||
async remove(userId: string, companyId: number, permissionId: number): Promise<void> {
|
||||
await api.delete(
|
||||
`/v1/core/permissions/users/${userId}/permissions/${permissionId}?company_id=${companyId}`
|
||||
);
|
||||
}
|
||||
};
|
||||
94
frontend/src/lib/api/dashboard/admin/user-roles.ts
Normal file
94
frontend/src/lib/api/dashboard/admin/user-roles.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* API para gestión de roles de usuarios
|
||||
*/
|
||||
|
||||
import { api } from '$lib/api';
|
||||
|
||||
export interface UserRole {
|
||||
id: number;
|
||||
user_id: string;
|
||||
company_id: number;
|
||||
company_role_id: number;
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
assigned_by?: string;
|
||||
company_role?: {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
description?: string;
|
||||
};
|
||||
user?: {
|
||||
id: number;
|
||||
username: string;
|
||||
email?: string;
|
||||
full_name?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AssignUserRoleData {
|
||||
user_id: string;
|
||||
company_role_id: number;
|
||||
}
|
||||
|
||||
export interface UserRolesResponse {
|
||||
items: UserRole[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
}
|
||||
|
||||
export const userRolesAPI = {
|
||||
/**
|
||||
* Listar todos los roles asignados a usuarios
|
||||
*/
|
||||
async list(
|
||||
companyId: number,
|
||||
params?: {
|
||||
user_id?: string;
|
||||
company_role_id?: number;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}
|
||||
): Promise<UserRolesResponse> {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.set('company_id', companyId.toString());
|
||||
if (params?.user_id) queryParams.set('user_id', params.user_id);
|
||||
if (params?.company_role_id) queryParams.set('company_role_id', params.company_role_id.toString());
|
||||
if (params?.page) queryParams.set('page', params.page.toString());
|
||||
if (params?.page_size) queryParams.set('page_size', params.page_size.toString());
|
||||
const response = await api.get(`/v1/core/permissions/user-roles?${queryParams.toString()}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Listar roles de un usuario específico
|
||||
*/
|
||||
async listByUser(userId: number, companyId: number): Promise<UserRolesResponse> {
|
||||
const response = await api.get(`/v1/core/permissions/users/${userId}/roles?company_id=${companyId}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Listar usuarios con un rol específico
|
||||
*/
|
||||
async listByRole(roleId: number, companyId: number): Promise<UserRolesResponse> {
|
||||
const response = await api.get(`/v1/core/permissions/roles/${roleId}/users?company_id=${companyId}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Asignar un rol a un usuario
|
||||
*/
|
||||
async assign(companyId: number, data: AssignUserRoleData): Promise<UserRole> {
|
||||
const response = await api.post(`/v1/core/permissions/user-roles?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remover un rol de un usuario
|
||||
*/
|
||||
async remove(userRoleId: number, companyId: number): Promise<void> {
|
||||
await api.delete(`/v1/core/permissions/user-roles/${userRoleId}?company_id=${companyId}`);
|
||||
}
|
||||
};
|
||||
1
frontend/src/lib/api/dashboard/index.ts
Normal file
1
frontend/src/lib/api/dashboard/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type { KPIMetric, ActivityItem, ChartDataPoint } from './types';
|
||||
60
frontend/src/lib/api/dashboard/invite-codes.ts
Normal file
60
frontend/src/lib/api/dashboard/invite-codes.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { api } from '$lib/api';
|
||||
|
||||
export interface InviteCode {
|
||||
id: number;
|
||||
code: string;
|
||||
tenant_slug: string;
|
||||
company_id: number | null;
|
||||
role: string;
|
||||
max_uses: number | null;
|
||||
uses_count: number;
|
||||
expires_at: string | null;
|
||||
is_active: boolean;
|
||||
created_by: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface CreateInviteCodeRequest {
|
||||
company_id?: number | null;
|
||||
role: string;
|
||||
max_uses?: number | null;
|
||||
expires_at?: string | null;
|
||||
}
|
||||
|
||||
export interface ValidateInviteCodeResponse {
|
||||
code: string;
|
||||
tenant_slug: string;
|
||||
company_id: number | null;
|
||||
role: string;
|
||||
remaining_uses: number | null;
|
||||
expires_at: string | null;
|
||||
}
|
||||
|
||||
export const inviteCodesAPI = {
|
||||
async list(companyId: number, includeInactive = false): Promise<InviteCode[]> {
|
||||
const params = new URLSearchParams({ company_id: companyId.toString() });
|
||||
if (includeInactive) params.set('include_inactive', 'true');
|
||||
const response = await api.get<InviteCode[]>(`/v1/core/invite-codes?${params}`);
|
||||
if (response.error) throw new Error(response.error);
|
||||
return response.data!;
|
||||
},
|
||||
|
||||
async create(data: CreateInviteCodeRequest): Promise<InviteCode> {
|
||||
const response = await api.post<InviteCode>('/v1/core/invite-codes', data);
|
||||
if (response.error) throw new Error(response.error);
|
||||
return response.data!;
|
||||
},
|
||||
|
||||
async revoke(code: string): Promise<void> {
|
||||
const response = await api.delete(`/v1/core/invite-codes/${code}`);
|
||||
if (response.error) throw new Error(response.error);
|
||||
},
|
||||
|
||||
async validate(code: string): Promise<ValidateInviteCodeResponse> {
|
||||
const response = await api.get<ValidateInviteCodeResponse>(
|
||||
`/v1/core/invite-codes/validate/${code}`
|
||||
);
|
||||
if (response.error) throw new Error(response.error);
|
||||
return response.data!;
|
||||
}
|
||||
};
|
||||
24
frontend/src/lib/api/dashboard/types.ts
Normal file
24
frontend/src/lib/api/dashboard/types.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export interface KPIMetric {
|
||||
label: string;
|
||||
value: number;
|
||||
previous_value?: number;
|
||||
percentage_change?: number;
|
||||
trend?: 'up' | 'down' | 'stable';
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
export interface ActivityItem {
|
||||
id: number;
|
||||
type: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
timestamp: string;
|
||||
status?: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
export interface ChartDataPoint {
|
||||
label: string;
|
||||
value: number;
|
||||
category?: string;
|
||||
}
|
||||
196
frontend/src/lib/api/dashboard/users.ts
Normal file
196
frontend/src/lib/api/dashboard/users.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* Cliente API para gestión de usuarios
|
||||
*/
|
||||
|
||||
import { api } from '$lib/api';
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
enabled: boolean;
|
||||
email_verified: boolean;
|
||||
created_timestamp?: number;
|
||||
role?: string;
|
||||
}
|
||||
|
||||
export interface UserStats {
|
||||
total_users: number;
|
||||
active_users: number;
|
||||
inactive_users: number;
|
||||
max_users_allowed: number;
|
||||
users_available: number;
|
||||
usage_percentage: number;
|
||||
}
|
||||
|
||||
export interface UserListResponse {
|
||||
users: User[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
total_pages: number;
|
||||
}
|
||||
|
||||
export interface CreateUserRequest {
|
||||
email: string;
|
||||
username: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
password: string;
|
||||
role?: string;
|
||||
enabled?: boolean;
|
||||
email_verified?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateUserRequest {
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
email?: string;
|
||||
enabled?: boolean;
|
||||
email_verified?: boolean;
|
||||
role?: string;
|
||||
}
|
||||
|
||||
export interface ChangePasswordRequest {
|
||||
password: string;
|
||||
temporary?: boolean;
|
||||
}
|
||||
|
||||
export interface InviteUserRequest {
|
||||
email: string;
|
||||
company_id: number;
|
||||
role_id: number;
|
||||
}
|
||||
|
||||
export interface InviteUserResponse {
|
||||
id: number;
|
||||
email: string;
|
||||
role: string;
|
||||
expires_at: string;
|
||||
invite_url: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export const usersAPI = {
|
||||
/**
|
||||
* Obtiene estadísticas de usuarios del tenant
|
||||
*/
|
||||
async getStats(companyId: number): Promise<UserStats> {
|
||||
const response = await api.get<UserStats>(`/v1/core/users/stats?company_id=${companyId}`);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
return response.data!;
|
||||
},
|
||||
|
||||
/**
|
||||
* Lista usuarios del tenant con paginación
|
||||
*/
|
||||
async list(companyId: number, params?: {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
search?: string;
|
||||
}): Promise<UserListResponse> {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.set('company_id', companyId.toString());
|
||||
if (params?.page) queryParams.set('page', params.page.toString());
|
||||
if (params?.page_size) queryParams.set('page_size', params.page_size.toString());
|
||||
if (params?.search) queryParams.set('search', params.search);
|
||||
|
||||
const endpoint = `/v1/core/users/?${queryParams}`;
|
||||
const response = await api.get<UserListResponse>(endpoint);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
return response.data!;
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtiene un usuario específico
|
||||
*/
|
||||
async get(userId: string, companyId: number): Promise<User> {
|
||||
const response = await api.get<User>(`/v1/core/users/${userId}?company_id=${companyId}`);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
return response.data!;
|
||||
},
|
||||
|
||||
/**
|
||||
* Crea un nuevo usuario
|
||||
*/
|
||||
async create(data: CreateUserRequest, companyId: number): Promise<User> {
|
||||
const response = await api.post<User>(`/v1/core/users/?company_id=${companyId}`, data);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
return response.data!;
|
||||
},
|
||||
|
||||
/**
|
||||
* Actualiza un usuario existente
|
||||
*/
|
||||
async update(userId: string, data: UpdateUserRequest, companyId: number): Promise<User> {
|
||||
const response = await api.put<User>(`/v1/core/users/${userId}?company_id=${companyId}`, data);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
return response.data!;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retorna en cuántos tenants está registrado el usuario.
|
||||
*/
|
||||
async getTenantCount(userId: string, companyId: number): Promise<number> {
|
||||
const response = await api.get<{ tenant_count: number }>(
|
||||
`/v1/core/users/${userId}/tenant-count?company_id=${companyId}`
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
return response.data!.tenant_count;
|
||||
},
|
||||
|
||||
/**
|
||||
* Elimina un usuario
|
||||
*/
|
||||
async delete(
|
||||
userId: string,
|
||||
companyId: number,
|
||||
softDelete: boolean = true,
|
||||
scope: 'current' | 'all' = 'current'
|
||||
): Promise<void> {
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.set('company_id', companyId.toString());
|
||||
queryParams.set('soft_delete', softDelete.toString());
|
||||
queryParams.set('scope', scope);
|
||||
|
||||
const response = await api.delete(`/v1/core/users/${userId}?${queryParams}`);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Cambia la contraseña de un usuario
|
||||
*/
|
||||
async changePassword(userId: string, data: ChangePasswordRequest, companyId: number): Promise<void> {
|
||||
const response = await api.post(`/v1/core/users/${userId}/change-password?company_id=${companyId}`, data);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Genera un token de invitación y envía email al usuario
|
||||
*/
|
||||
async invite(data: InviteUserRequest): Promise<InviteUserResponse> {
|
||||
const response = await api.post<InviteUserResponse>('/v1/core/invites', data);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
return response.data!;
|
||||
}
|
||||
};
|
||||
128
frontend/src/lib/api/help.ts
Normal file
128
frontend/src/lib/api/help.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { getToken, authStore } from '$lib/auth';
|
||||
import { get } from 'svelte/store';
|
||||
|
||||
const api_url = import.meta.env.VITE_API_URL ?? '';
|
||||
const normalizedApiUrl = api_url ? (api_url.endsWith('/') ? api_url : `${api_url}/`) : '/';
|
||||
const BASE_URL = `${normalizedApiUrl}v1/core/help-center`;
|
||||
|
||||
function getAuthToken(): string | null {
|
||||
// 1. First try getToken() which checks Keycloak and localStorage
|
||||
let token = getToken();
|
||||
|
||||
// 2. If somehow empty, explicitly check authStore value
|
||||
if (!token) {
|
||||
const auth = get(authStore);
|
||||
token = auth.token;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
function getHeaders() {
|
||||
const token = getAuthToken();
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||||
};
|
||||
}
|
||||
|
||||
export interface HelpArticle {
|
||||
uuid: string;
|
||||
slug: string;
|
||||
title: string;
|
||||
content: string;
|
||||
updated_at: string;
|
||||
last_editor: string;
|
||||
category?: string;
|
||||
order?: number;
|
||||
content_type: string;
|
||||
file_url?: string;
|
||||
file_size?: number;
|
||||
mime_type?: string;
|
||||
context_path?: string;
|
||||
tags?: string;
|
||||
}
|
||||
|
||||
export const helpApi = {
|
||||
async listArticles(): Promise<HelpArticle[]> {
|
||||
const response = await fetch(`${BASE_URL}/articles/`, { headers: getHeaders() });
|
||||
if (!response.ok) throw new Error('Failed to fetch articles');
|
||||
return response.json();
|
||||
},
|
||||
|
||||
async getArticle(uuid: string): Promise<HelpArticle> {
|
||||
const response = await fetch(`${BASE_URL}/articles/${uuid}/`, { headers: getHeaders() });
|
||||
if (!response.ok) throw new Error('Failed to fetch article');
|
||||
return response.json();
|
||||
},
|
||||
|
||||
async updateArticle(uuid: string, data: Partial<HelpArticle>): Promise<HelpArticle> {
|
||||
const response = await fetch(`${BASE_URL}/articles/${uuid}/`, {
|
||||
method: 'PATCH',
|
||||
headers: getHeaders(),
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
if (response.status === 403) throw new Error('No tienes permisos para editar artículos (Requiere rol Admin)');
|
||||
if (!response.ok) throw new Error('Error al guardar cambios');
|
||||
return response.json();
|
||||
},
|
||||
|
||||
async createArticle(data: Partial<HelpArticle>): Promise<HelpArticle> {
|
||||
const response = await fetch(`${BASE_URL}/articles/`, {
|
||||
method: 'POST',
|
||||
headers: getHeaders(),
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
if (response.status === 403) throw new Error('No tienes permisos para crear artículos (Requiere rol Admin)');
|
||||
if (!response.ok) throw new Error('Error al crear el artículo');
|
||||
return response.json();
|
||||
},
|
||||
|
||||
async deleteArticle(uuid: string): Promise<void> {
|
||||
const response = await fetch(`${BASE_URL}/articles/${uuid}/`, {
|
||||
method: 'DELETE',
|
||||
headers: getHeaders()
|
||||
});
|
||||
if (response.status === 403) throw new Error('No tienes permisos para eliminar (Requiere rol Admin)');
|
||||
if (!response.ok) throw new Error('Error al eliminar');
|
||||
},
|
||||
|
||||
async triggerSync(): Promise<void> {
|
||||
// Opcional: endpoint para forzar sync desde UI si es necesario
|
||||
},
|
||||
|
||||
async uploadImage(file: File): Promise<{ url: string }> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const token = getAuthToken();
|
||||
const response = await fetch(`${BASE_URL}/upload-image/`, {
|
||||
method: 'POST',
|
||||
// No Content-Type header for FormData, browser sets it with boundary
|
||||
headers: {
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
if (response.status === 403) throw new Error('No tienes permisos para subir imágenes (Requiere rol Admin)');
|
||||
if (!response.ok) throw new Error('Error al subir imagen');
|
||||
return response.json();
|
||||
},
|
||||
|
||||
async uploadAsset(file: File): Promise<{ url: string, filename: string, size: number, mime_type: string }> {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const token = getAuthToken();
|
||||
const response = await fetch(`${BASE_URL}/upload-asset/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
if (response.status === 403) throw new Error('No tienes permisos para subir archivos (Requiere rol Admin)');
|
||||
if (!response.ok) throw new Error('Error al subir archivo');
|
||||
return response.json();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user