generated from fahricansecer/boilerplate-fe
Initial commit
This commit is contained in:
22
src/lib/api/api-service.ts
Normal file
22
src/lib/api/api-service.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { clientMap } from '@/lib/api/client-map';
|
||||
import { Method } from 'axios';
|
||||
|
||||
interface ApiRequestOptions {
|
||||
url: string;
|
||||
client: keyof typeof clientMap;
|
||||
method?: Method;
|
||||
data?: any;
|
||||
params?: Record<string, any>;
|
||||
}
|
||||
|
||||
export async function apiRequest<T = any>(options: ApiRequestOptions): Promise<T> {
|
||||
const { url, client, method = 'get', data, params } = options;
|
||||
const clientInstance = clientMap[client];
|
||||
|
||||
if (!url || !clientInstance) {
|
||||
throw new Error(`Invalid API request: ${client} - ${url}`);
|
||||
}
|
||||
|
||||
const response = await clientInstance.request<T>({ method, url, data, params });
|
||||
return response.data;
|
||||
}
|
||||
9
src/lib/api/client-map.ts
Normal file
9
src/lib/api/client-map.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import baseUrl from '@/config/base-url';
|
||||
import { createApiClient } from '@/lib/api/create-api-client';
|
||||
import { AxiosInstance } from 'axios';
|
||||
|
||||
export const clientMap: Record<keyof typeof baseUrl, AxiosInstance> = {
|
||||
auth: createApiClient(baseUrl.auth!),
|
||||
admin: createApiClient(baseUrl.admin!),
|
||||
core: createApiClient(baseUrl.core!),
|
||||
};
|
||||
73
src/lib/api/create-api-client.ts
Normal file
73
src/lib/api/create-api-client.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
import { getSession, signOut } from 'next-auth/react';
|
||||
|
||||
// const MESSAGES = {
|
||||
// tr: {
|
||||
// title: 'Oturum Süresi Doldu',
|
||||
// description: 'Güvenliğiniz için çıkış yapıldı. Lütfen tekrar giriş yapınız.',
|
||||
// },
|
||||
// en: {
|
||||
// title: 'Session Expired',
|
||||
// description: 'You have been logged out for security reasons. Please log in again.',
|
||||
// },
|
||||
// };
|
||||
|
||||
export function createApiClient(baseURL: string): AxiosInstance {
|
||||
const client = axios.create({ baseURL });
|
||||
|
||||
// Helper to get locale from cookie or URL
|
||||
const getLocale = (): string => {
|
||||
if (typeof window !== 'undefined') {
|
||||
// Try to get from cookie first (NEXT_LOCALE is the default cookie name)
|
||||
const cookieLocale = document.cookie
|
||||
.split('; ')
|
||||
.find((row) => row.startsWith('NEXT_LOCALE='))
|
||||
?.split('=')[1];
|
||||
|
||||
if (cookieLocale) return cookieLocale;
|
||||
|
||||
// Fallback: get from URL path (e.g., /tr/generator -> tr)
|
||||
const pathLocale = window.location.pathname.split('/')[1];
|
||||
if (['en', 'tr', 'de'].includes(pathLocale)) return pathLocale;
|
||||
}
|
||||
return 'tr'; // Default locale
|
||||
};
|
||||
|
||||
client.interceptors.request.use(async (config) => {
|
||||
const session = await getSession();
|
||||
const token = session?.accessToken;
|
||||
|
||||
if (token) {
|
||||
config.headers.set('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
|
||||
// Set Accept-Language header based on current locale
|
||||
const locale = getLocale();
|
||||
config.headers.set('Accept-Language', locale);
|
||||
|
||||
if (!(config.data instanceof FormData)) {
|
||||
config.headers.set('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
client.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
if (error.response?.status === 401) {
|
||||
// Zaten giriş sayfasında değilsek veya auth ile ilgili bir istek değilse çıkış yap
|
||||
const isAuthPath =
|
||||
typeof window !== 'undefined' &&
|
||||
(window.location.pathname.includes('/api/auth') || window.location.pathname === '/');
|
||||
|
||||
if (!isAuthPath) {
|
||||
await signOut({ redirect: true, callbackUrl: '/' });
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
return client;
|
||||
}
|
||||
30
src/lib/api/example/admin/permissions/service.ts
Normal file
30
src/lib/api/example/admin/permissions/service.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { apiRequest } from "@/lib/api/api-service";
|
||||
import { ApiResponse } from "@/types/api-response";
|
||||
import { PermissionResponseDto, CreatePermissionDto } from "./types";
|
||||
|
||||
/**
|
||||
* Admin Permissions Service - Example Implementation
|
||||
* Matches Backend: /api/admin/permissions/*
|
||||
*/
|
||||
|
||||
const getAll = () => {
|
||||
return apiRequest<ApiResponse<PermissionResponseDto[]>>({
|
||||
url: "/admin/permissions",
|
||||
client: "admin",
|
||||
method: "get",
|
||||
});
|
||||
};
|
||||
|
||||
const create = (data: CreatePermissionDto) => {
|
||||
return apiRequest<ApiResponse<PermissionResponseDto>>({
|
||||
url: "/admin/permissions",
|
||||
client: "admin",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
export const adminPermissionsService = {
|
||||
getAll,
|
||||
create,
|
||||
};
|
||||
18
src/lib/api/example/admin/permissions/types.ts
Normal file
18
src/lib/api/example/admin/permissions/types.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
// Permission DTOs - Matches Backend
|
||||
export interface PermissionResponseDto {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
resource: string;
|
||||
action: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
// Create Permission
|
||||
export interface CreatePermissionDto {
|
||||
name: string;
|
||||
description?: string;
|
||||
resource: string;
|
||||
action: string;
|
||||
}
|
||||
40
src/lib/api/example/admin/permissions/use-hooks.ts
Normal file
40
src/lib/api/example/admin/permissions/use-hooks.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { ApiResponse } from "@/types/api-response";
|
||||
import { adminPermissionsService } from "./service";
|
||||
import { PermissionResponseDto, CreatePermissionDto } from "./types";
|
||||
|
||||
export const AdminPermissionsQueryKeys = {
|
||||
all: ["admin-permissions"] as const,
|
||||
list: () => [...AdminPermissionsQueryKeys.all, "list"] as const,
|
||||
};
|
||||
|
||||
export function useGetAllPermissions() {
|
||||
const queryKey = AdminPermissionsQueryKeys.list();
|
||||
|
||||
const { data, ...rest } = useQuery<ApiResponse<PermissionResponseDto[]>>({
|
||||
queryKey: queryKey,
|
||||
queryFn: adminPermissionsService.getAll,
|
||||
});
|
||||
|
||||
return { data: data?.data, ...rest };
|
||||
}
|
||||
|
||||
export function useCreatePermission() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, ...rest } = useMutation<
|
||||
ApiResponse<PermissionResponseDto>,
|
||||
Error,
|
||||
CreatePermissionDto
|
||||
>({
|
||||
mutationFn: (permissionData) =>
|
||||
adminPermissionsService.create(permissionData),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: AdminPermissionsQueryKeys.all,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return { data: data?.data, ...rest };
|
||||
}
|
||||
72
src/lib/api/example/admin/roles/service.ts
Normal file
72
src/lib/api/example/admin/roles/service.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { apiRequest } from "@/lib/api/api-service";
|
||||
import { ApiResponse } from "@/types/api-response";
|
||||
import {
|
||||
RoleResponseDto,
|
||||
CreateRoleDto,
|
||||
UpdateRoleDto,
|
||||
RolePermissionResponseDto,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* Admin Roles Service - Example Implementation
|
||||
* Matches Backend: /api/admin/roles/*
|
||||
*/
|
||||
|
||||
const getAll = () => {
|
||||
return apiRequest<ApiResponse<RoleResponseDto[]>>({
|
||||
url: "/admin/roles",
|
||||
client: "admin",
|
||||
method: "get",
|
||||
});
|
||||
};
|
||||
|
||||
const create = (data: CreateRoleDto) => {
|
||||
return apiRequest<ApiResponse<RoleResponseDto>>({
|
||||
url: "/admin/roles",
|
||||
client: "admin",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
const update = (roleId: string, data: UpdateRoleDto) => {
|
||||
return apiRequest<ApiResponse<RoleResponseDto>>({
|
||||
url: `/admin/roles/${roleId}`,
|
||||
client: "admin",
|
||||
method: "put",
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
const remove = (roleId: string) => {
|
||||
return apiRequest<ApiResponse<null>>({
|
||||
url: `/admin/roles/${roleId}`,
|
||||
client: "admin",
|
||||
method: "delete",
|
||||
});
|
||||
};
|
||||
|
||||
const assignPermission = (roleId: string, permissionId: string) => {
|
||||
return apiRequest<ApiResponse<RolePermissionResponseDto>>({
|
||||
url: `/admin/roles/${roleId}/permissions/${permissionId}`,
|
||||
client: "admin",
|
||||
method: "post",
|
||||
});
|
||||
};
|
||||
|
||||
const removePermission = (roleId: string, permissionId: string) => {
|
||||
return apiRequest<ApiResponse<null>>({
|
||||
url: `/admin/roles/${roleId}/permissions/${permissionId}`,
|
||||
client: "admin",
|
||||
method: "delete",
|
||||
});
|
||||
};
|
||||
|
||||
export const adminRolesService = {
|
||||
getAll,
|
||||
create,
|
||||
update,
|
||||
remove,
|
||||
assignPermission,
|
||||
removePermission,
|
||||
};
|
||||
38
src/lib/api/example/admin/roles/types.ts
Normal file
38
src/lib/api/example/admin/roles/types.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// Role DTOs - Matches Backend
|
||||
export interface RoleResponseDto {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
permissions: PermissionInfo[];
|
||||
_count?: {
|
||||
users: number;
|
||||
};
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface PermissionInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
resource: string;
|
||||
action: string;
|
||||
}
|
||||
|
||||
// Create/Update Role
|
||||
export interface CreateRoleDto {
|
||||
name: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface UpdateRoleDto {
|
||||
name?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// Role Permission Assignment
|
||||
export interface RolePermissionResponseDto {
|
||||
id: string;
|
||||
roleId: string;
|
||||
permissionId: string;
|
||||
createdAt: string;
|
||||
}
|
||||
112
src/lib/api/example/admin/roles/use-hooks.ts
Normal file
112
src/lib/api/example/admin/roles/use-hooks.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { ApiResponse } from "@/types/api-response";
|
||||
import { adminRolesService } from "./service";
|
||||
import {
|
||||
RoleResponseDto,
|
||||
CreateRoleDto,
|
||||
UpdateRoleDto,
|
||||
RolePermissionResponseDto,
|
||||
} from "./types";
|
||||
|
||||
export const AdminRolesQueryKeys = {
|
||||
all: ["admin-roles"] as const,
|
||||
list: () => [...AdminRolesQueryKeys.all, "list"] as const,
|
||||
};
|
||||
|
||||
export function useGetAllRoles() {
|
||||
const queryKey = AdminRolesQueryKeys.list();
|
||||
|
||||
const { data, ...rest } = useQuery<ApiResponse<RoleResponseDto[]>>({
|
||||
queryKey: queryKey,
|
||||
queryFn: adminRolesService.getAll,
|
||||
});
|
||||
|
||||
return { data: data?.data, ...rest };
|
||||
}
|
||||
|
||||
export function useCreateRole() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, ...rest } = useMutation<
|
||||
ApiResponse<RoleResponseDto>,
|
||||
Error,
|
||||
CreateRoleDto
|
||||
>({
|
||||
mutationFn: (roleData) => adminRolesService.create(roleData),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: AdminRolesQueryKeys.all });
|
||||
},
|
||||
});
|
||||
|
||||
return { data: data?.data, ...rest };
|
||||
}
|
||||
|
||||
export function useUpdateRole() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, ...rest } = useMutation<
|
||||
ApiResponse<RoleResponseDto>,
|
||||
Error,
|
||||
{ roleId: string; data: UpdateRoleDto }
|
||||
>({
|
||||
mutationFn: ({ roleId, data }) => adminRolesService.update(roleId, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: AdminRolesQueryKeys.all });
|
||||
},
|
||||
});
|
||||
|
||||
return { data: data?.data, ...rest };
|
||||
}
|
||||
|
||||
export function useDeleteRole() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, ...rest } = useMutation<
|
||||
ApiResponse<null>,
|
||||
Error,
|
||||
{ roleId: string }
|
||||
>({
|
||||
mutationFn: ({ roleId }) => adminRolesService.remove(roleId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: AdminRolesQueryKeys.all });
|
||||
},
|
||||
});
|
||||
|
||||
return { data: data?.data, ...rest };
|
||||
}
|
||||
|
||||
export function useAssignPermission() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, ...rest } = useMutation<
|
||||
ApiResponse<RolePermissionResponseDto>,
|
||||
Error,
|
||||
{ roleId: string; permissionId: string }
|
||||
>({
|
||||
mutationFn: ({ roleId, permissionId }) =>
|
||||
adminRolesService.assignPermission(roleId, permissionId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: AdminRolesQueryKeys.all });
|
||||
},
|
||||
});
|
||||
|
||||
return { data: data?.data, ...rest };
|
||||
}
|
||||
|
||||
export function useRemovePermission() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, ...rest } = useMutation<
|
||||
ApiResponse<null>,
|
||||
Error,
|
||||
{ roleId: string; permissionId: string }
|
||||
>({
|
||||
mutationFn: ({ roleId, permissionId }) =>
|
||||
adminRolesService.removePermission(roleId, permissionId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: AdminRolesQueryKeys.all });
|
||||
},
|
||||
});
|
||||
|
||||
return { data: data?.data, ...rest };
|
||||
}
|
||||
53
src/lib/api/example/admin/users/service.ts
Normal file
53
src/lib/api/example/admin/users/service.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { apiRequest } from "@/lib/api/api-service";
|
||||
import { ApiResponse } from "@/types/api-response";
|
||||
import { UserResponseDto } from "@/types/user";
|
||||
import {
|
||||
UsersQueryParams,
|
||||
PaginatedUsersResponse,
|
||||
UserRoleResponseDto,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* Admin Users Service - Example Implementation
|
||||
* Matches Backend: /api/admin/users/*
|
||||
*/
|
||||
|
||||
const getAll = (params?: UsersQueryParams) => {
|
||||
return apiRequest<ApiResponse<PaginatedUsersResponse>>({
|
||||
url: "/admin/users",
|
||||
client: "admin",
|
||||
method: "get",
|
||||
params,
|
||||
});
|
||||
};
|
||||
|
||||
const toggleActive = (userId: string) => {
|
||||
return apiRequest<ApiResponse<UserResponseDto>>({
|
||||
url: `/admin/users/${userId}/toggle-active`,
|
||||
client: "admin",
|
||||
method: "put",
|
||||
});
|
||||
};
|
||||
|
||||
const assignRole = (userId: string, roleId: string) => {
|
||||
return apiRequest<ApiResponse<UserRoleResponseDto>>({
|
||||
url: `/admin/users/${userId}/roles/${roleId}`,
|
||||
client: "admin",
|
||||
method: "post",
|
||||
});
|
||||
};
|
||||
|
||||
const removeRole = (userId: string, roleId: string) => {
|
||||
return apiRequest<ApiResponse<null>>({
|
||||
url: `/admin/users/${userId}/roles/${roleId}`,
|
||||
client: "admin",
|
||||
method: "delete",
|
||||
});
|
||||
};
|
||||
|
||||
export const adminUsersService = {
|
||||
getAll,
|
||||
toggleActive,
|
||||
assignRole,
|
||||
removeRole,
|
||||
};
|
||||
18
src/lib/api/example/admin/users/types.ts
Normal file
18
src/lib/api/example/admin/users/types.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { PaginatedData, PaginationDto } from "@/types/api-response";
|
||||
import { UserResponseDto } from "../../users/types";
|
||||
export type { UserResponseDto };
|
||||
export type { PaginationDto };
|
||||
|
||||
// User Role Assignment
|
||||
export interface UserRoleResponseDto {
|
||||
id: string;
|
||||
userId: string;
|
||||
roleId: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
// Pagination params
|
||||
export type UsersQueryParams = PaginationDto;
|
||||
|
||||
// Response type for paginated users
|
||||
export type PaginatedUsersResponse = PaginatedData<UserResponseDto>;
|
||||
79
src/lib/api/example/admin/users/use-hooks.ts
Normal file
79
src/lib/api/example/admin/users/use-hooks.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { ApiResponse } from "@/types/api-response";
|
||||
import { adminUsersService } from "./service";
|
||||
import {
|
||||
UserRoleResponseDto,
|
||||
UsersQueryParams,
|
||||
PaginatedUsersResponse,
|
||||
} from "./types";
|
||||
import { UserResponseDto } from "@/types/user";
|
||||
|
||||
export const AdminUsersQueryKeys = {
|
||||
all: ["admin-users"] as const,
|
||||
list: (params?: UsersQueryParams) =>
|
||||
[...AdminUsersQueryKeys.all, "list", params] as const,
|
||||
};
|
||||
|
||||
export function useGetAllUsers(params?: UsersQueryParams) {
|
||||
const queryKey = AdminUsersQueryKeys.list(params);
|
||||
|
||||
const { data, ...rest } = useQuery<ApiResponse<PaginatedUsersResponse>>({
|
||||
queryKey: queryKey,
|
||||
queryFn: () => adminUsersService.getAll(params),
|
||||
});
|
||||
|
||||
return { data: data?.data, ...rest };
|
||||
}
|
||||
|
||||
export function useToggleUserActive() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, ...rest } = useMutation<
|
||||
ApiResponse<UserResponseDto>,
|
||||
Error,
|
||||
{ userId: string }
|
||||
>({
|
||||
mutationFn: ({ userId }) => adminUsersService.toggleActive(userId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: AdminUsersQueryKeys.all });
|
||||
},
|
||||
});
|
||||
|
||||
return { data: data?.data, ...rest };
|
||||
}
|
||||
|
||||
export function useAssignRole() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, ...rest } = useMutation<
|
||||
ApiResponse<UserRoleResponseDto>,
|
||||
Error,
|
||||
{ userId: string; roleId: string }
|
||||
>({
|
||||
mutationFn: ({ userId, roleId }) =>
|
||||
adminUsersService.assignRole(userId, roleId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: AdminUsersQueryKeys.all });
|
||||
},
|
||||
});
|
||||
|
||||
return { data: data?.data, ...rest };
|
||||
}
|
||||
|
||||
export function useRemoveRole() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, ...rest } = useMutation<
|
||||
ApiResponse<null>,
|
||||
Error,
|
||||
{ userId: string; roleId: string }
|
||||
>({
|
||||
mutationFn: ({ userId, roleId }) =>
|
||||
adminUsersService.removeRole(userId, roleId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: AdminUsersQueryKeys.all });
|
||||
},
|
||||
});
|
||||
|
||||
return { data: data?.data, ...rest };
|
||||
}
|
||||
50
src/lib/api/example/auth/service.ts
Normal file
50
src/lib/api/example/auth/service.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { apiRequest } from "@/lib/api/api-service";
|
||||
import { ApiResponse } from "@/types/api-response";
|
||||
import { LoginDto, RegisterDto, RefreshTokenDto, AuthResponse } from "./types";
|
||||
|
||||
/**
|
||||
* Auth Service - Example Implementation
|
||||
* Matches Backend: /api/auth/*
|
||||
*/
|
||||
|
||||
const login = (data: LoginDto) => {
|
||||
return apiRequest<ApiResponse<AuthResponse>>({
|
||||
url: "/auth/login",
|
||||
client: "auth",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
const register = (data: RegisterDto) => {
|
||||
return apiRequest<ApiResponse<AuthResponse>>({
|
||||
url: "/auth/register",
|
||||
client: "auth",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
const refreshToken = (data: RefreshTokenDto) => {
|
||||
return apiRequest<ApiResponse<AuthResponse>>({
|
||||
url: "/auth/refresh",
|
||||
client: "auth",
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
return apiRequest<ApiResponse<null>>({
|
||||
url: "/auth/logout",
|
||||
client: "auth",
|
||||
method: "post",
|
||||
});
|
||||
};
|
||||
|
||||
export const authService = {
|
||||
login,
|
||||
register,
|
||||
refreshToken,
|
||||
logout,
|
||||
};
|
||||
32
src/lib/api/example/auth/types.ts
Normal file
32
src/lib/api/example/auth/types.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
// Auth Request DTOs
|
||||
export interface LoginDto {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface RegisterDto {
|
||||
email: string;
|
||||
password: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenDto {
|
||||
refreshToken: string;
|
||||
}
|
||||
|
||||
// Auth Response DTOs
|
||||
export interface AuthResponse {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
expiresIn: number;
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
isActive?: boolean;
|
||||
roles: string[];
|
||||
};
|
||||
}
|
||||
64
src/lib/api/example/auth/use-hooks.ts
Normal file
64
src/lib/api/example/auth/use-hooks.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { ApiResponse } from "@/types/api-response";
|
||||
import { authService } from "./service";
|
||||
import { LoginDto, RegisterDto, RefreshTokenDto, AuthResponse } from "./types";
|
||||
|
||||
export const AuthQueryKeys = {
|
||||
all: ["auth"] as const,
|
||||
session: () => [...AuthQueryKeys.all, "session"] as const,
|
||||
};
|
||||
|
||||
export function useLogin() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, ...rest } = useMutation<
|
||||
ApiResponse<AuthResponse>,
|
||||
Error,
|
||||
LoginDto
|
||||
>({
|
||||
mutationFn: (credentials: LoginDto) => authService.login(credentials),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: AuthQueryKeys.session() });
|
||||
},
|
||||
});
|
||||
|
||||
return { data: data?.data, ...rest };
|
||||
}
|
||||
|
||||
export function useRegister() {
|
||||
const { data, ...rest } = useMutation<
|
||||
ApiResponse<AuthResponse>,
|
||||
Error,
|
||||
RegisterDto
|
||||
>({
|
||||
mutationFn: (userData: RegisterDto) => authService.register(userData),
|
||||
});
|
||||
|
||||
return { data: data?.data, ...rest };
|
||||
}
|
||||
|
||||
export function useRefreshToken() {
|
||||
const { data, ...rest } = useMutation<
|
||||
ApiResponse<AuthResponse>,
|
||||
Error,
|
||||
RefreshTokenDto
|
||||
>({
|
||||
mutationFn: (tokenData: RefreshTokenDto) =>
|
||||
authService.refreshToken(tokenData),
|
||||
});
|
||||
|
||||
return { data: data?.data, ...rest };
|
||||
}
|
||||
|
||||
export function useLogout() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data, ...rest } = useMutation<ApiResponse<null>, Error, void>({
|
||||
mutationFn: () => authService.logout(),
|
||||
onSuccess: () => {
|
||||
queryClient.clear();
|
||||
},
|
||||
});
|
||||
|
||||
return { data: data?.data, ...rest };
|
||||
}
|
||||
86
src/lib/api/example/index.ts
Normal file
86
src/lib/api/example/index.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Example API Services - Barrel Export
|
||||
* Import all services and hooks from this single file
|
||||
*
|
||||
* Usage:
|
||||
* import { authService, useLogin, adminRolesService, useGetAllRoles } from '@/lib/api/Example';
|
||||
*/
|
||||
|
||||
// Services
|
||||
export { authService } from "./auth/service";
|
||||
export { usersService } from "./users/service";
|
||||
export { adminUsersService } from "./admin/users/service";
|
||||
export { adminRolesService } from "./admin/roles/service";
|
||||
export { adminPermissionsService } from "./admin/permissions/service";
|
||||
|
||||
// Hooks - Auth
|
||||
export {
|
||||
useLogin,
|
||||
useRegister,
|
||||
useRefreshToken,
|
||||
useLogout,
|
||||
AuthQueryKeys,
|
||||
} from "./auth/use-hooks";
|
||||
|
||||
// Hooks - Users
|
||||
export { useGetMe, UsersQueryKeys } from "./users/use-hooks";
|
||||
|
||||
// Hooks - Admin Users
|
||||
export {
|
||||
useGetAllUsers,
|
||||
useToggleUserActive,
|
||||
useAssignRole,
|
||||
useRemoveRole,
|
||||
AdminUsersQueryKeys,
|
||||
} from "./admin/users/use-hooks";
|
||||
|
||||
// Hooks - Admin Roles
|
||||
export {
|
||||
useGetAllRoles,
|
||||
useCreateRole,
|
||||
useUpdateRole,
|
||||
useDeleteRole,
|
||||
useAssignPermission,
|
||||
useRemovePermission,
|
||||
AdminRolesQueryKeys,
|
||||
} from "./admin/roles/use-hooks";
|
||||
|
||||
// Hooks - Admin Permissions
|
||||
export {
|
||||
useGetAllPermissions,
|
||||
useCreatePermission,
|
||||
AdminPermissionsQueryKeys,
|
||||
} from "./admin/permissions/use-hooks";
|
||||
|
||||
// Types - Auth
|
||||
export type {
|
||||
LoginDto,
|
||||
RegisterDto,
|
||||
RefreshTokenDto,
|
||||
AuthResponse,
|
||||
} from "./auth/types";
|
||||
|
||||
// Types - Users (Common)
|
||||
export type { UserResponseDto, RoleInfo } from "./users/types";
|
||||
|
||||
// Types - Admin Users (Specific)
|
||||
export type {
|
||||
UserRoleResponseDto,
|
||||
UsersQueryParams,
|
||||
PaginatedUsersResponse,
|
||||
} from "./admin/users/types";
|
||||
|
||||
// Types - Admin Roles
|
||||
export type {
|
||||
RoleResponseDto,
|
||||
CreateRoleDto,
|
||||
UpdateRoleDto,
|
||||
RolePermissionResponseDto,
|
||||
PermissionInfo,
|
||||
} from "./admin/roles/types";
|
||||
|
||||
// Types - Admin Permissions
|
||||
export type {
|
||||
PermissionResponseDto,
|
||||
CreatePermissionDto,
|
||||
} from "./admin/permissions/types";
|
||||
20
src/lib/api/example/users/service.ts
Normal file
20
src/lib/api/example/users/service.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { apiRequest } from "@/lib/api/api-service";
|
||||
import { ApiResponse } from "@/types/api-response";
|
||||
import { UserResponseDto } from "./types";
|
||||
|
||||
/**
|
||||
* Users Service - Example Implementation
|
||||
* Matches Backend: /api/users/*
|
||||
*/
|
||||
|
||||
const getMe = () => {
|
||||
return apiRequest<ApiResponse<UserResponseDto>>({
|
||||
url: "/users/me",
|
||||
client: "core",
|
||||
method: "get",
|
||||
});
|
||||
};
|
||||
|
||||
export const usersService = {
|
||||
getMe,
|
||||
};
|
||||
18
src/lib/api/example/users/types.ts
Normal file
18
src/lib/api/example/users/types.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
// User Response DTO - Matches Backend UserResponseDto
|
||||
export interface UserResponseDto {
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
username?: string;
|
||||
isActive: boolean;
|
||||
avatar?: string | null;
|
||||
roles: RoleInfo[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface RoleInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
20
src/lib/api/example/users/use-hooks.ts
Normal file
20
src/lib/api/example/users/use-hooks.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { ApiResponse } from "@/types/api-response";
|
||||
import { usersService } from "./service";
|
||||
import { UserResponseDto } from "./types";
|
||||
|
||||
export const UsersQueryKeys = {
|
||||
all: ["users"] as const,
|
||||
me: () => [...UsersQueryKeys.all, "me"] as const,
|
||||
};
|
||||
|
||||
export function useGetMe() {
|
||||
const queryKey = UsersQueryKeys.me();
|
||||
|
||||
const { data, ...rest } = useQuery<ApiResponse<UserResponseDto>>({
|
||||
queryKey: queryKey,
|
||||
queryFn: usersService.getMe,
|
||||
});
|
||||
|
||||
return { data: data?.data, ...rest };
|
||||
}
|
||||
11
src/lib/services/pricing-service.ts
Normal file
11
src/lib/services/pricing-service.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
interface Pricing {
|
||||
inputCost: number;
|
||||
outputCost: number;
|
||||
}
|
||||
|
||||
export async function getPricing(): Promise<Pricing> {
|
||||
return {
|
||||
inputCost: 0.3,
|
||||
outputCost: 2.5,
|
||||
};
|
||||
}
|
||||
217
src/lib/utils/ai-helper.ts
Normal file
217
src/lib/utils/ai-helper.ts
Normal file
@@ -0,0 +1,217 @@
|
||||
import { getPricing } from "@/lib/services/pricing-service";
|
||||
import { GoogleGenAI, Modality } from "@google/genai";
|
||||
|
||||
/**
|
||||
* Merkezi yapay zeka istemci örneği
|
||||
* Tüm yapay zeka istekleri bu örneği kullanmalıdır
|
||||
*/
|
||||
export const ai = new GoogleGenAI({
|
||||
vertexai: true,
|
||||
|
||||
apiKey: process.env.GOOGLE_API_KEY,
|
||||
});
|
||||
|
||||
// Kalite artırıcı anahtar kelimeler
|
||||
const QUALITY_BOOSTERS = [
|
||||
"highly detailed",
|
||||
"8k resolution",
|
||||
"professional photography",
|
||||
"studio lighting",
|
||||
"sharp focus",
|
||||
"cinematic composition",
|
||||
"vibrant colors",
|
||||
"masterpiece",
|
||||
"masterpiece",
|
||||
];
|
||||
|
||||
/**
|
||||
* Yapay Zeka Model yapılandırmaları
|
||||
*/
|
||||
export const AI_MODELS = {
|
||||
FLASH_LITE: "gemini-2.5-flash-lite",
|
||||
FLASH: "gemini-2.5-flash",
|
||||
FLASH_IMAGE: "gemini-2.5-flash-image",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Yapay zeka ile içerik oluştur
|
||||
* Tüm yapay zeka içerik oluşturma işlemleri için merkezi fonksiyon
|
||||
*
|
||||
* @param model - Kullanılacak yapay zeka modeli
|
||||
* @param prompt - Gönderilecek istem veya içerikler
|
||||
* @param config - İsteğe bağlı yapılandırma
|
||||
* @returns Yapay zeka yanıtı
|
||||
*/
|
||||
export async function generateAIContent(
|
||||
model: string,
|
||||
prompt: string | any,
|
||||
config?: any
|
||||
) {
|
||||
try {
|
||||
const response = await ai.models.generateContent({
|
||||
model,
|
||||
contents: prompt,
|
||||
...(config && { config }),
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("AI generation error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Yapay zeka ile metin içeriği oluştur
|
||||
* @param model - Kullanılacak yapay zeka modeli
|
||||
* @param prompt - Metin istemi
|
||||
* @returns Oluşturulan metin
|
||||
*/
|
||||
export async function generateText(
|
||||
model: string,
|
||||
prompt: string
|
||||
): Promise<{ text: string; usage?: any }> {
|
||||
const response = await generateAIContent(model, prompt);
|
||||
return {
|
||||
text: (response.text || "").trim(),
|
||||
usage: response.usageMetadata,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Görüntü girdisi ile içerik oluştur
|
||||
* @param model - Kullanılacak yapay zeka modeli
|
||||
* @param imageBase64 - Base64 kodlanmış görüntü
|
||||
* @param textPrompt - Metin istemi
|
||||
* @returns Yapay zeka yanıtı
|
||||
*/
|
||||
export async function generateWithImage(
|
||||
model: string,
|
||||
imageBase64: string,
|
||||
textPrompt: string
|
||||
) {
|
||||
const response = await generateAIContent(model, [
|
||||
{
|
||||
role: "user",
|
||||
parts: [
|
||||
{ inlineData: { mimeType: "image/jpeg", data: imageBase64 } },
|
||||
{ text: textPrompt },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
return {
|
||||
text: (response.text || "").trim(),
|
||||
usage: response.usageMetadata,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* İstemden görüntü oluştur
|
||||
* @param prompt - Görüntü oluşturma için metin istemi
|
||||
* @param imageBase64 - Düzenleme için isteğe bağlı base64 görüntü
|
||||
* @param suggestions - Görsel iyileştirme önerileri (Nano Banana için)
|
||||
* @param model - Kullanılacak model (varsayılan: FLASH_IMAGE)
|
||||
* @returns Oluşturulan görüntü URL'si (base64)
|
||||
*/
|
||||
/**
|
||||
* İstemden görüntü oluştur
|
||||
* @param prompt - Görüntü oluşturma için metin istemi
|
||||
* @param imageBase64 - Düzenleme için isteğe bağlı base64 görüntü
|
||||
* @param suggestions - Görsel iyileştirme önerileri (Nano Banana için)
|
||||
* @param model - Kullanılacak model (varsayılan: FLASH_IMAGE)
|
||||
* @param aspectRatio - Hedef en boy oranı (örn: "16:9", "1:1")
|
||||
* @returns Oluşturulan görüntü URL'si (base64)
|
||||
*/
|
||||
export async function generateImage(
|
||||
prompt: string,
|
||||
imageBase64?: string,
|
||||
suggestions?: string[],
|
||||
model: string = AI_MODELS.FLASH_IMAGE,
|
||||
aspectRatio?: string
|
||||
): Promise<{ imageUrl: string | null; usage?: any }> {
|
||||
let parts: Array<
|
||||
{ text: string } | { inlineData: { mimeType: string; data: string } }
|
||||
> = [];
|
||||
|
||||
// En boy oranı talimatı oluştur
|
||||
const ratioInstruction = aspectRatio
|
||||
? `\n\nTarget Aspect Ratio: ${aspectRatio}\nEnsure the image strictly follows the ${aspectRatio} aspect ratio format.`
|
||||
: "";
|
||||
|
||||
if (imageBase64) {
|
||||
// Görüntü düzenleme modu
|
||||
const cleanBase64 = imageBase64.replace(
|
||||
/^data:image\/(png|jpeg|webp|jpg);base64,/,
|
||||
""
|
||||
);
|
||||
|
||||
// Eğer Nano Banana modeli ve öneriler varsa, prompt'u zenginleştir
|
||||
let finalPrompt = prompt;
|
||||
if (model === "nano-banana") {
|
||||
let improvementInstructions = "";
|
||||
if (suggestions && suggestions.length > 0) {
|
||||
improvementInstructions = `\n\nApply these specific improvements based on platform analysis:\n${suggestions.map((s) => `- ${s}`).join("\n")}`;
|
||||
}
|
||||
|
||||
finalPrompt = `${prompt}${improvementInstructions}\n\nStyle & Quality Instructions:\nRender in ${QUALITY_BOOSTERS.join(", ")}.\n\nCRITICAL OBJECTIVE: The result MUST achieve a perfect 10/10 score.\n- Clarity: 10/10 (Ultra-sharp, no blur)\n- Professionalism: 10/10 (High-end commercial look)\n- Engagement: 10/10 (Eye-catching contrast and lighting)\n- Platform Fit: 10/10 (Perfect aspect ratio and framing)\nEnsure the image is visually stunning and flawless.`;
|
||||
}
|
||||
|
||||
parts.push({ inlineData: { mimeType: "image/png", data: cleanBase64 } });
|
||||
parts.push({ text: finalPrompt + ratioInstruction });
|
||||
} else {
|
||||
parts.push({ text: prompt + ratioInstruction });
|
||||
}
|
||||
|
||||
console.log("[AI-Helper] generateImage calling Gemini model", {
|
||||
model: AI_MODELS.FLASH_IMAGE,
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await ai.models.generateContent({
|
||||
model,
|
||||
contents: [{ role: "user", parts }],
|
||||
config: {
|
||||
responseModalities: [Modality.IMAGE],
|
||||
},
|
||||
});
|
||||
|
||||
const candidate = response.candidates?.[0];
|
||||
const imagePart = candidate?.content?.parts?.find((p: any) =>
|
||||
p.inlineData?.mimeType?.startsWith("image/")
|
||||
);
|
||||
|
||||
if (imagePart?.inlineData?.data) {
|
||||
return {
|
||||
imageUrl: `data:${imagePart.inlineData.mimeType};base64,${imagePart.inlineData.data}`,
|
||||
usage: response.usageMetadata,
|
||||
};
|
||||
}
|
||||
|
||||
console.warn("No image part found in response");
|
||||
return { imageUrl: null, usage: response.usageMetadata };
|
||||
} catch (error) {
|
||||
console.error("generateImage error:", error);
|
||||
return { imageUrl: null };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Token kullanımına dayalı Yapay Zeka Maliyetini hesapla
|
||||
* Giriş Maliyeti = (Giriş Tokenları / 1.000.000) * Giriş Fiyatı
|
||||
* Çıkış Maliyeti = (Çıkış Tokenları / 1.000.000) * Çıkış Fiyatı
|
||||
* @param usage - Yapay zeka yanıtından kullanım meta verileri
|
||||
* @returns Para birimi cinsinden toplam maliyet
|
||||
*/
|
||||
export async function calculateAICost(usage: any): Promise<number> {
|
||||
if (!usage) return 0;
|
||||
|
||||
const pricing = await getPricing();
|
||||
const inputTokens = usage.promptTokenCount || 0;
|
||||
const outputTokens = usage.candidatesTokenCount || 0;
|
||||
|
||||
const inputCost = (inputTokens / 1_000_000) * pricing.inputCost;
|
||||
const outputCost = (outputTokens / 1_000_000) * pricing.outputCost;
|
||||
|
||||
return inputCost + outputCost;
|
||||
}
|
||||
52
src/lib/utils/language.ts
Normal file
52
src/lib/utils/language.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Language mapping utility for AI prompts
|
||||
* Maps locale codes to full language names for AI instructions
|
||||
*/
|
||||
|
||||
export const SUPPORTED_LOCALES = {
|
||||
tr: 'Turkish',
|
||||
en: 'English',
|
||||
de: 'German',
|
||||
ar: 'Arabic',
|
||||
zh: 'Chinese',
|
||||
es: 'Spanish',
|
||||
fr: 'French',
|
||||
it: 'Italian',
|
||||
ja: 'Japanese',
|
||||
ko: 'Korean',
|
||||
pt: 'Portuguese',
|
||||
ru: 'Russian',
|
||||
} as const;
|
||||
|
||||
export type SupportedLocale = keyof typeof SUPPORTED_LOCALES;
|
||||
|
||||
/**
|
||||
* Get the full language name for AI prompts
|
||||
* @param locale - Locale code (e.g., 'tr', 'en')
|
||||
* @returns Full language name (e.g., 'Turkish', 'English')
|
||||
*/
|
||||
export function getLanguageName(locale?: string): string {
|
||||
if (!locale) return SUPPORTED_LOCALES.tr; // Default to Turkish
|
||||
|
||||
const normalizedLocale = locale.toLowerCase() as SupportedLocale;
|
||||
return SUPPORTED_LOCALES[normalizedLocale] || SUPPORTED_LOCALES.en; // Fallback to English
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language instruction for AI prompts
|
||||
* @param locale - Locale code
|
||||
* @returns Formatted instruction for AI (e.g., "Write in Turkish")
|
||||
*/
|
||||
export function getLanguageInstruction(locale?: string): string {
|
||||
const language = getLanguageName(locale);
|
||||
return `Write in ${language}.`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a locale is supported
|
||||
* @param locale - Locale code to check
|
||||
* @returns True if supported, false otherwise
|
||||
*/
|
||||
export function isSupportedLocale(locale: string): locale is SupportedLocale {
|
||||
return locale.toLowerCase() in SUPPORTED_LOCALES;
|
||||
}
|
||||
35
src/lib/utils/prompt-builder.ts
Normal file
35
src/lib/utils/prompt-builder.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Gemini için Fotoğrafçılık Taslağı (JSON Blueprint)
|
||||
* Bu yapı modeli profesyonel bir fotoğrafçı gibi düşünmeye zorlar.
|
||||
* Client-side güvenlidir.
|
||||
*/
|
||||
export const buildPhotorealisticPrompt = (corePrompt: string): string => {
|
||||
const blueprint = {
|
||||
subject_context: corePrompt,
|
||||
style: {
|
||||
direction: 'Professional Lifestyle Photography / Authentic Brand Content',
|
||||
aesthetic: 'Modern, bright, engaging, premium but approachable',
|
||||
lighting: 'Natural ambient daylight mixed with soft studio fill, warm tones',
|
||||
},
|
||||
camera_settings: {
|
||||
sensor: 'Full-frame Digital Sensor (Sony Alpha / Canon R5)',
|
||||
lens: '35mm or 50mm Prime (Standard Social Media View)',
|
||||
depth_of_field: 'Moderate depth of field (sharp subject, slightly softened background)',
|
||||
shutter: '1/125s natural motion freeze',
|
||||
},
|
||||
composition_rules: {
|
||||
framing: 'Rule of thirds, center-weighted for social media engagement',
|
||||
angle: 'Eye-level or 45-degree isometric (depending on subject)',
|
||||
aspect_ratio_fit: 'Optimized for Instagram/LinkedIn',
|
||||
},
|
||||
quality_assurance: {
|
||||
clarity: 'Perfect Focus',
|
||||
noise_level: 'Minimal natural grain allowed for authenticity',
|
||||
texture_detail: 'High fidelity materials',
|
||||
render_engine: 'Photorealistic Photography Style (Not CGI looking)',
|
||||
},
|
||||
prohibited_elements: ['text watermarks', 'blurry', 'distorted', 'ugly', 'low resolution'],
|
||||
};
|
||||
|
||||
return JSON.stringify(blueprint, null, 2);
|
||||
};
|
||||
Reference in New Issue
Block a user