260224:1606 20260224:1600 V1.8.0
All checks were successful
Build and Deploy / deploy (push) Successful in 6m25s
All checks were successful
Build and Deploy / deploy (push) Successful in 6m25s
This commit is contained in:
@@ -3,6 +3,7 @@ import NextAuth from "next-auth";
|
||||
import Credentials from "next-auth/providers/credentials";
|
||||
import { z } from "zod";
|
||||
import type { User } from "next-auth";
|
||||
import type { JWT } from "next-auth/jwt";
|
||||
|
||||
// Schema for input validation
|
||||
const loginSchema = z.object({
|
||||
@@ -17,12 +18,12 @@ function getJwtExpiry(token: string): number {
|
||||
try {
|
||||
const payload = JSON.parse(atob(token.split('.')[1]));
|
||||
return payload.exp * 1000; // Convert to ms
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return Date.now(); // If invalid, treat as expired
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshAccessToken(token: any) {
|
||||
async function refreshAccessToken(token: JWT) {
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/auth/refresh`, {
|
||||
method: "POST",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import apiClient from "@/lib/api/client";
|
||||
import { AuditQueryParams } from '@/types/dto/numbering.dto';
|
||||
|
||||
export interface AuditLog {
|
||||
auditId: string;
|
||||
@@ -12,16 +13,18 @@ export interface AuditLog {
|
||||
severity: string;
|
||||
entityType?: string;
|
||||
entityId?: string;
|
||||
detailsJson?: any;
|
||||
detailsJson?: Record<string, unknown>;
|
||||
ipAddress?: string;
|
||||
userAgent?: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export type AuditLogQueryParams = AuditQueryParams;
|
||||
|
||||
export const auditLogService = {
|
||||
getLogs: async (params?: any) => {
|
||||
const response = await apiClient.get<any>("/audit-logs", { params });
|
||||
getLogs: async (params?: AuditLogQueryParams) => {
|
||||
const response = await apiClient.get<{ data: AuditLog[] } | AuditLog[]>("/audit-logs", { params });
|
||||
// Support both wrapped and unwrapped scenarios
|
||||
return response.data.data || response.data;
|
||||
return (response.data as { data: AuditLog[] }).data ?? response.data;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ export const correspondenceService = {
|
||||
return response.data;
|
||||
},
|
||||
|
||||
update: async (id: string | number, data: any) => {
|
||||
update: async (id: string | number, data: Partial<CreateCorrespondenceDto>) => {
|
||||
const response = await apiClient.put(`/correspondences/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
@@ -4,9 +4,16 @@ import {
|
||||
ManualOverrideDto,
|
||||
VoidReplaceDto,
|
||||
CancelNumberDto,
|
||||
AuditQueryParams
|
||||
} from "@/types/dto/numbering.dto";
|
||||
|
||||
/** A bulk-import record row */
|
||||
export interface BulkImportRecord {
|
||||
documentNumber: string;
|
||||
projectId: number;
|
||||
sequenceNumber: number;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export const documentNumberingService = {
|
||||
// --- Admin Dashboard Metrics ---
|
||||
getMetrics: async (): Promise<NumberingMetrics> => {
|
||||
@@ -19,7 +26,7 @@ export const documentNumberingService = {
|
||||
await apiClient.post("/admin/document-numbering/manual-override", dto);
|
||||
},
|
||||
|
||||
voidAndReplace: async (dto: VoidReplaceDto): Promise<any> => {
|
||||
voidAndReplace: async (dto: VoidReplaceDto): Promise<{ documentNumber: string }> => {
|
||||
const response = await apiClient.post("/admin/document-numbering/void-and-replace", dto);
|
||||
return response.data;
|
||||
},
|
||||
@@ -28,7 +35,7 @@ export const documentNumberingService = {
|
||||
await apiClient.post("/admin/document-numbering/cancel", dto);
|
||||
},
|
||||
|
||||
bulkImport: async (data: FormData | any[]): Promise<any> => {
|
||||
bulkImport: async (data: FormData | BulkImportRecord[]): Promise<{ imported: number; errors: string[] }> => {
|
||||
const isFormData = data instanceof FormData;
|
||||
const config = isFormData ? { headers: { "Content-Type": "multipart/form-data" } } : {};
|
||||
const response = await apiClient.post("/admin/document-numbering/bulk-import", data, config);
|
||||
@@ -36,7 +43,7 @@ export const documentNumberingService = {
|
||||
},
|
||||
|
||||
// --- Audit Logs ---
|
||||
getAuditLogs: async (params?: AuditQueryParams) => {
|
||||
getAuditLogs: async () => {
|
||||
// NOTE: endpoint might be merged with metrics or separate
|
||||
// Currently controller has getMetrics returning audit logs too.
|
||||
// But if we want separate pagination later:
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// File: lib/services/json-schema.service.ts
|
||||
import apiClient from "@/lib/api/client";
|
||||
import {
|
||||
CreateJsonSchemaDto,
|
||||
UpdateJsonSchemaDto,
|
||||
SearchJsonSchemaDto
|
||||
import {
|
||||
CreateJsonSchemaDto,
|
||||
UpdateJsonSchemaDto,
|
||||
SearchJsonSchemaDto
|
||||
} from "@/types/dto/json-schema/json-schema.dto";
|
||||
|
||||
export const jsonSchemaService = {
|
||||
@@ -64,7 +64,7 @@ export const jsonSchemaService = {
|
||||
/**
|
||||
* (Optional) ตรวจสอบความถูกต้องของข้อมูลกับ Schema ฝั่ง Server
|
||||
*/
|
||||
validate: async (code: string, data: any) => {
|
||||
validate: async (code: string, data: Record<string, unknown>) => {
|
||||
// POST /json-schemas/validate
|
||||
const response = await apiClient.post(`/json-schemas/validate`, {
|
||||
schemaCode: code,
|
||||
@@ -72,4 +72,4 @@ export const jsonSchemaService = {
|
||||
});
|
||||
return response.data; // { valid: true, errors: [] }
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -6,6 +6,8 @@ import { CreateTagDto, UpdateTagDto, SearchTagDto } from "@/types/dto/master/tag
|
||||
import { CreateDisciplineDto } from "@/types/dto/master/discipline.dto";
|
||||
import { CreateSubTypeDto } from "@/types/dto/master/sub-type.dto";
|
||||
import { SaveNumberFormatDto } from "@/types/dto/master/number-format.dto";
|
||||
import { CreateRfaTypeDto, UpdateRfaTypeDto } from "@/types/dto/master/rfa-type.dto";
|
||||
import { CreateCorrespondenceTypeDto, UpdateCorrespondenceTypeDto } from "@/types/dto/master/correspondence-type.dto";
|
||||
import { Organization } from "@/types/organization";
|
||||
import {
|
||||
CreateOrganizationDto,
|
||||
@@ -137,21 +139,11 @@ export const masterDataService = {
|
||||
},
|
||||
|
||||
/** สร้างประเภท RFA ใหม่ */
|
||||
createRfaType: async (data: any) => {
|
||||
// Note: Assuming endpoint is /master/rfa-types (POST)
|
||||
// Currently RfaController handles /rfas, but master data usually goes to MasterController or dedicated
|
||||
// The previous implementation used direct apiClient calls in the page.
|
||||
// Let's assume we use the endpoint we just updated in MasterController which is GET only?
|
||||
// Wait, MasterController doesn't have createRfaType.
|
||||
// Let's check where RFA Types are created. RfaController creates RFAs (documents).
|
||||
// RFA Types are likely master data.
|
||||
// I need to add create/update/delete endpoints for RFA Types to MasterController if they don't exist.
|
||||
// Checking MasterController again... it DOES NOT have createRfaType.
|
||||
// I will add them to MasterController first.
|
||||
createRfaType: async (data: CreateRfaTypeDto) => {
|
||||
return apiClient.post("/master/rfa-types", data).then(res => res.data);
|
||||
},
|
||||
|
||||
updateRfaType: async (id: number, data: any) => {
|
||||
updateRfaType: async (id: number, data: UpdateRfaTypeDto) => {
|
||||
return apiClient.patch(`/master/rfa-types/${id}`, data).then(res => res.data);
|
||||
},
|
||||
|
||||
@@ -167,11 +159,11 @@ export const masterDataService = {
|
||||
return response.data.data || response.data;
|
||||
},
|
||||
|
||||
createCorrespondenceType: async (data: any) => {
|
||||
createCorrespondenceType: async (data: CreateCorrespondenceTypeDto) => {
|
||||
return apiClient.post("/master/correspondence-types", data).then(res => res.data);
|
||||
},
|
||||
|
||||
updateCorrespondenceType: async (id: number, data: any) => {
|
||||
updateCorrespondenceType: async (id: number, data: UpdateCorrespondenceTypeDto) => {
|
||||
return apiClient.patch(`/master/correspondence-types/${id}`, data).then(res => res.data);
|
||||
},
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ export interface Session {
|
||||
|
||||
export const sessionService = {
|
||||
getActiveSessions: async () => {
|
||||
const response = await apiClient.get<any>('/auth/sessions');
|
||||
return response.data.data || response.data;
|
||||
const response = await apiClient.get<Session[] | { data: Session[] }>('/auth/sessions');
|
||||
return (response.data as { data: Session[] }).data ?? response.data;
|
||||
},
|
||||
|
||||
revokeSession: async (sessionId: number) => {
|
||||
|
||||
@@ -1,54 +1,62 @@
|
||||
import apiClient from "@/lib/api/client";
|
||||
import { CreateUserDto, UpdateUserDto, SearchUserDto, User } from "@/types/user";
|
||||
|
||||
const transformUser = (user: any): User => {
|
||||
/** Raw API user shape (before transform) */
|
||||
interface RawUser {
|
||||
user_id?: number;
|
||||
userId?: number;
|
||||
assignments?: Array<{ role: unknown }>;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
const transformUser = (user: RawUser): User => {
|
||||
return {
|
||||
...user,
|
||||
userId: user.user_id,
|
||||
roles: user.assignments?.map((a: any) => a.role) || [],
|
||||
...(user as unknown as User),
|
||||
userId: (user.user_id ?? user.userId) as number,
|
||||
roles: (user.assignments?.map((a) => a.role) ?? []) as User['roles'],
|
||||
};
|
||||
};
|
||||
|
||||
/** Paginated or unwrapped response shape */
|
||||
type UserListResponse = User[] | { data: User[] | { data: User[] } };
|
||||
|
||||
export const userService = {
|
||||
getAll: async (params?: SearchUserDto) => {
|
||||
const response = await apiClient.get<any>("/users", { params });
|
||||
const response = await apiClient.get<UserListResponse>("/users", { params });
|
||||
|
||||
// Handle both paginated and non-paginated responses
|
||||
let rawData = response.data?.data || response.data;
|
||||
|
||||
// If paginated (has .data property which is array)
|
||||
if (rawData && Array.isArray(rawData.data)) {
|
||||
rawData = rawData.data;
|
||||
let rawData: RawUser[] | unknown = response.data;
|
||||
if (rawData && !Array.isArray(rawData) && 'data' in (rawData as object)) {
|
||||
rawData = (rawData as { data: unknown }).data;
|
||||
}
|
||||
|
||||
// If still not array (e.g. error or empty), default to []
|
||||
if (!Array.isArray(rawData)) {
|
||||
return [];
|
||||
if (rawData && !Array.isArray(rawData) && typeof rawData === 'object' && 'data' in (rawData as object)) {
|
||||
rawData = (rawData as { data: unknown }).data;
|
||||
}
|
||||
if (!Array.isArray(rawData)) return [];
|
||||
|
||||
return rawData.map(transformUser);
|
||||
return (rawData as RawUser[]).map(transformUser);
|
||||
},
|
||||
|
||||
getRoles: async () => {
|
||||
const response = await apiClient.get<any>("/users/roles");
|
||||
if (response.data?.data) {
|
||||
return response.data.data;
|
||||
const response = await apiClient.get<{ data: unknown } | unknown>("/users/roles");
|
||||
if (response.data && typeof response.data === 'object' && 'data' in (response.data as object)) {
|
||||
return (response.data as { data: unknown }).data;
|
||||
}
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getById: async (id: number) => {
|
||||
const response = await apiClient.get<User>(`/users/${id}`);
|
||||
const response = await apiClient.get<RawUser>(`/users/${id}`);
|
||||
return transformUser(response.data);
|
||||
},
|
||||
|
||||
create: async (data: CreateUserDto) => {
|
||||
const response = await apiClient.post<User>("/users", data);
|
||||
const response = await apiClient.post<RawUser>("/users", data);
|
||||
return transformUser(response.data);
|
||||
},
|
||||
|
||||
update: async (id: number, data: UpdateUserDto) => {
|
||||
const response = await apiClient.put<User>(`/users/${id}`, data);
|
||||
const response = await apiClient.put<RawUser>(`/users/${id}`, data);
|
||||
return transformUser(response.data);
|
||||
},
|
||||
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
|
||||
/** A draft can hold any serializable form data — typed as unknown for strictness */
|
||||
type DraftValue = Record<string, unknown>;
|
||||
|
||||
interface DraftState {
|
||||
drafts: Record<string, any>;
|
||||
saveDraft: (key: string, data: any) => void;
|
||||
getDraft: (key: string) => any;
|
||||
drafts: Record<string, DraftValue>;
|
||||
saveDraft: (key: string, data: DraftValue) => void;
|
||||
getDraft: (key: string) => DraftValue | undefined;
|
||||
clearDraft: (key: string) => void;
|
||||
}
|
||||
|
||||
@@ -26,4 +29,4 @@ export const useDraftStore = create<DraftState>()(
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
}
|
||||
)
|
||||
);
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user