Feature logs

Deskripsi:
- Fitur baru log untuk melihat error pada server upload
- Baru di terapkan di create profile
This commit is contained in:
2024-12-16 15:41:02 +08:00
parent 4dd98c6183
commit 0bdc25b1e6
19 changed files with 815 additions and 349 deletions

3
.gitignore vendored
View File

@@ -34,6 +34,9 @@ yarn-error.log*
# vercel
.vercel
# logs
logs
# typescript
*.tsbuildinfo
next-env.d.ts

BIN
bun.lockb

Binary file not shown.

View File

@@ -0,0 +1,53 @@
import { funGetDirectoryNameByValue } from "@/app_modules/_global/fun/get";
import backendLogger from "@/util/backendLogger";
import { NextResponse } from "next/server";
export async function DELETE(req: Request) {
const data = await req.json();
const id = data.fileId;
const dirId = data.dirId;
const keyOfDirectory = await funGetDirectoryNameByValue({
value: dirId,
});
if (req.method === "DELETE") {
try {
const res = await fetch(
`https://wibu-storage.wibudev.com/api/files/${id}/delete`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.WS_APIKEY}`,
},
}
);
backendLogger.info("Server status code: " + res.status);
const data = await res.json();
if (res.ok) {
backendLogger.info(
`Success delete ${keyOfDirectory}`
);
return NextResponse.json({ success: true });
} else {
const errorText = await res.json();
backendLogger.error(
`Failed delete ${keyOfDirectory}: ` + errorText.message
);
return NextResponse.json({
success: false,
message: errorText.message,
});
}
} catch (error) {
backendLogger.error(`Delete error ${keyOfDirectory}:`, error);
return NextResponse.json({
success: false,
message: "An unexpected error occurred",
});
}
} else {
backendLogger.error(`Error upload ${keyOfDirectory}: Method not allowed`);
return NextResponse.json({ success: false, message: "Method not allowed" });
}
}

View File

@@ -0,0 +1,58 @@
import { funGetDirectoryNameByValue } from "@/app_modules/_global/fun/get";
import backendLogger from "@/util/backendLogger";
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const formData = await request.formData();
const valueOfDir = formData.get("dirId");
const keyOfDirectory = await funGetDirectoryNameByValue({
value: valueOfDir as string,
});
if (request.method === "POST") {
try {
const res = await fetch("https://wibu-storage.wibudev.com/api/upload", {
method: "POST",
body: formData,
headers: {
Authorization: `Bearer ${process.env.WS_APIKEY}`,
},
});
backendLogger.info("Server status code: " + res.status);
const dataRes = await res.json();
if (res.ok) {
backendLogger.info(
`Success upload ${keyOfDirectory}: ${JSON.stringify(dataRes.data)}`
);
return NextResponse.json(
{ success: true, data: dataRes.data },
{ status: 200 }
);
} else {
const errorText = await res.text();
backendLogger.error(`Failed upload ${keyOfDirectory}: ${errorText}`);
return NextResponse.json(
{ success: false, message: errorText },
{ status: 400 }
);
}
} catch (error) {
backendLogger.error(`Error upload ${keyOfDirectory}: ${error}`);
return NextResponse.json(
{
success: false,
message: "An unexpected error occurred",
},
{ status: 500 }
);
}
} else {
backendLogger.error(`Error upload ${keyOfDirectory}: Method not allowed`);
return NextResponse.json(
{ success: false, message: "Method not allowed" },
{ status: 405 }
);
}
}

View File

@@ -1,51 +0,0 @@
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const WS_APIKEY = process.env.WS_APIKEY;
console.log(WS_APIKEY);
try {
const formData = await request.formData();
const res = await fetch("https://wibu-storage.wibudev.com/api/upload", {
method: "POST",
body: formData,
headers: {
Authorization: `Bearer ${process.env.WS_APIKEY}`,
},
});
// if (res.ok) {
// console.log("Berhasil");
// const hasil = await res.json();
// return { success: true, data: hasil.data };
// } else {
// const errorText = await res.text();
// return { success: false, data: {} };
// }
} catch (error) {
console.log(error);
}
// try {
// const res = await fetch("https://wibu-storage.wibudev.com/api/upload", {
// method: "POST",
// body: formData,
// headers: {
// Authorization: `Bearer ${process.env.WS_APIKEY}`,
// },
// });
// if (res.ok) {
// const hasil = await res.json();
// return { success: true, data: hasil.data };
// } else {
// const errorText = await res.text();
// return { success: false, data: {} };
// }
// } catch (error) {
// console.error("Upload error:", error);
// return { success: false, data: {} };
// }
return NextResponse.json({ success: true });
}

View File

@@ -33,7 +33,7 @@ export default function Page() {
const formData = new FormData();
formData.append("file", filePP as any);
const res = await fetch("/api/upload", {
const res = await fetch("/api/image/upload", {
method: "POST",
body: formData,
});

View File

@@ -1,24 +1,50 @@
export async function funGlobal_DeleteFileById({ fileId }: { fileId: string }) {
try {
const res = await fetch(
`https://wibu-storage.wibudev.com/api/files/${fileId}/delete`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${process.env.WS_APIKEY}`,
},
}
);
import { clientLogger } from "@/util/clientLogger";
if (res.ok) {
const hasil = await res.json();
return { success: true };
export async function funGlobal_DeleteFileById({
fileId,
dirId,
}: {
fileId: string;
dirId?: string;
}) {
try {
const res = await fetch("/api/image/delete", {
method: "DELETE",
body: JSON.stringify({ fileId, dirId }),
});
const data = await res.json();
if (data.success) {
clientLogger.info(`File ${fileId} deleted successfully`);
return { success: true, message: "File berhasil dihapus" };
} else {
const errorText = await res.json();
return { success: false };
return { success: false, message: data.message };
}
} catch (error) {
return { success: false };
console.error("Upload error:", error);
return { success: false, message: "An unexpected error occurred" };
}
// try {
// const res = await fetch(
// `https://wibu-storage.wibudev.com/api/files/${fileId}/delete`,
// {
// method: "DELETE",
// headers: {
// Authorization: `Bearer ${process.env.WS_APIKEY}`,
// },
// }
// );
// if (res.ok) {
// const hasil = await res.json();
// return { success: true, message: "File berhasil dihapus" };
// } else {
// const errorText = await res.json();
// return { success: false, message: errorText.message };
// }
// } catch (error) {
// console.error("Upload error:", error);
// return { success: false, message: "An unexpected error occurred" };
// }
}

View File

@@ -0,0 +1,11 @@
import { DIRECTORY_ID } from "@/app/lib";
export async function funGetDirectoryNameByValue({
value,
}: {
value?: string | null;
}) {
if (!value) return null;
const object: any = DIRECTORY_ID;
return Object.keys(object).find((key) => object[key] === value);
}

View File

@@ -1,5 +1,5 @@
import { funGlobal_CheckProfile } from "./fun_check_profile";
import { funGetDirectoryNameByValue } from "./fun_get_directory_name";
import { funGlobal_getNomorAdmin } from "./fun_get_nomor_admin";
import { funGetUserIdByToken } from "./fun_get_user_id_by_token";
import { funGlobal_getMasterKategoriApp } from "./fun_master_kategori_app";
@@ -8,3 +8,4 @@ export { funGlobal_getMasterKategoriApp };
export { funGlobal_getNomorAdmin };
export { funGetUserIdByToken };
export { funGlobal_CheckProfile };
export { funGetDirectoryNameByValue };

View File

@@ -32,40 +32,20 @@ export async function funGlobal_UploadToStorage({
console.error("File terlalu besar");
return { success: false, message: "File size exceeds limit" };
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000); // Timeout 30 detik
const formData = new FormData();
formData.append("file", file);
formData.append("dirId", dirId);
try {
const res = await fetch("https://wibu-storage.wibudev.com/api/upload", {
method: "POST",
body: formData,
headers: {
Authorization: `Bearer ${Env_WS_APIKEY}`,
},
signal: controller.signal,
});
const upload = await fetch("/api/image/upload", {
method: "POST",
body: formData,
});
clearTimeout(timeoutId); // Bersihkan timeout jika selesai tepat waktu
const res = await upload.json();
if (res.ok) {
const dataRes = await res.json();
// const cekLog = await res.text();
// console.log(cekLog);
return { success: true, data: dataRes.data };
} else {
const errorText = await res.text();
console.error("Error:", errorText);
return { success: false, message: errorText };
}
} catch (error) {
clearTimeout(timeoutId); //
console.error("Error:", error);
return { success: false, message: "An unexpected error occurred" };
if (upload.ok) {
return { success: true, data: res.data, message: res.message };
} else {
return { success: false, data: {}, message: res.message };
}
}

View File

@@ -1,42 +1,16 @@
"use client";
import { DIRECTORY_ID } from "@/app/lib";
import { MainColor } from "@/app_modules/_global/color";
import {
ComponentGlobal_BoxInformation,
ComponentGlobal_BoxUploadImage,
ComponentGlobal_ErrorInput,
} from "@/app_modules/_global/component";
import {
funGlobal_DeleteFileById,
funGlobal_UploadToStorage,
} from "@/app_modules/_global/fun";
import { MAX_SIZE } from "@/app_modules/_global/lib";
import { PemberitahuanMaksimalFile } from "@/app_modules/_global/lib/max_size";
import { ComponentGlobal_NotifikasiPeringatan } from "@/app_modules/_global/notif_global";
import {
AspectRatio,
Avatar,
Box,
Button,
Center,
FileButton,
Image,
Paper,
Select,
Stack,
Text,
TextInput,
} from "@mantine/core";
import { IconAt, IconCamera, IconUpload } from "@tabler/icons-react";
import { ComponentGlobal_ErrorInput } from "@/app_modules/_global/component";
import { Select, Stack, TextInput } from "@mantine/core";
import { IconAt } from "@tabler/icons-react";
import { useState } from "react";
import { gmailRegex } from "../../component/regular_expressions";
import { Profile_ComponentCreateNewProfile } from "../_component";
import Profile_ViewUploadBackground from "./view_upload_background";
import Profile_ViewUploadFoto from "./view_upload_foto";
export default function CreateProfile() {
const [filePP, setFilePP] = useState<File | null>(null);
const [imgPP, setImgPP] = useState<any | null>();
const [fileBG, setFileBG] = useState<File | null>(null);
const [imgBG, setImgBG] = useState<any | null>();
const [fotoProfileId, setFotoProfileId] = useState("");
const [backgroundProfileId, setBackgroundProfileId] = useState("");
@@ -51,223 +25,19 @@ export default function CreateProfile() {
return (
<>
<Stack px={"sm"} spacing={40}>
<Box>
<Stack spacing={"lg"}>
<ComponentGlobal_BoxInformation informasi="Upload foto profile anda." />
<Center>
{imgPP ? (
<Paper shadow="lg" radius={"100%"}>
<Avatar
color={"cyan"}
sx={{
borderStyle: "solid",
borderColor: "gray",
borderWidth: "0.5px",
}}
src={imgPP ? imgPP : "/aset/global/avatar.png"}
size={150}
radius={"100%"}
/>
</Paper>
) : (
<Paper shadow="lg" radius={"100%"}>
<Avatar
variant="light"
color="blue"
size={150}
radius={"100%"}
sx={{
borderStyle: "solid",
borderColor: MainColor.darkblue,
borderWidth: "0.5px",
}}
/>
</Paper>
)}
</Center>
<Profile_ViewUploadFoto
imgPP={imgPP}
onSetImgPP={setImgPP}
fotoProfileId={fotoProfileId}
onSetFotoProfileId={setFotoProfileId}
/>
<Center>
<FileButton
onChange={async (files: any | null) => {
try {
const buffer = URL.createObjectURL(
new Blob([new Uint8Array(await files.arrayBuffer())])
);
if (files.size > MAX_SIZE) {
ComponentGlobal_NotifikasiPeringatan(
PemberitahuanMaksimalFile
);
setImgPP(null);
setFilePP(null);
return;
}
if (fotoProfileId != "") {
const deleteFotoProfile = await funGlobal_DeleteFileById({
fileId: fotoProfileId,
});
if (deleteFotoProfile.success) {
setFotoProfileId("");
const uploadPhoto = await funGlobal_UploadToStorage({
file: files,
dirId: DIRECTORY_ID.profile_foto,
});
if (uploadPhoto.success) {
setFotoProfileId(uploadPhoto.data.id);
setImgPP(buffer);
setFilePP(files);
} else {
ComponentGlobal_NotifikasiPeringatan(
"Gagal upload foto profile"
);
}
}
} else {
const uploadPhoto = await funGlobal_UploadToStorage({
file: files,
dirId: DIRECTORY_ID.profile_foto,
});
if (uploadPhoto.success) {
setFotoProfileId(uploadPhoto.data.id);
setImgPP(buffer);
setFilePP(files);
} else {
ComponentGlobal_NotifikasiPeringatan(
"Gagal upload foto profile"
);
}
}
} catch (error) {
console.log(error);
}
}}
accept="image/png,image/jpeg"
>
{(props) => (
<Button
{...props}
radius={"xl"}
leftIcon={<IconCamera />}
bg={MainColor.yellow}
color="yellow"
c={"black"}
>
Upload
</Button>
)}
</FileButton>
</Center>
</Stack>
</Box>
<Box>
<Stack spacing={"lg"}>
<ComponentGlobal_BoxInformation informasi="Upload foto latar belakang profile anda." />
<ComponentGlobal_BoxUploadImage>
{imgBG ? (
<AspectRatio ratio={1 / 1} mah={265} mx={"auto"}>
<Image
style={{ maxHeight: 250, margin: "auto", padding: "5px" }}
alt="Foto"
height={250}
src={imgBG ? imgBG : "/aset/no-img.png"}
/>
</AspectRatio>
) : (
<Stack justify="center" align="center" h={"100%"}>
<IconUpload color="white" />
<Text fz={"xs"} c={"white"}>
Upload Background
</Text>
</Stack>
)}
</ComponentGlobal_BoxUploadImage>
<Center>
<FileButton
onChange={async (files: any | null) => {
try {
const buffer = URL.createObjectURL(
new Blob([new Uint8Array(await files.arrayBuffer())])
);
if (files.size > MAX_SIZE) {
ComponentGlobal_NotifikasiPeringatan(
PemberitahuanMaksimalFile
);
setImgBG(null);
setFileBG(null);
return;
}
if (backgroundProfileId != "") {
const deleteFotoBg = await funGlobal_DeleteFileById({
fileId: backgroundProfileId,
});
if (deleteFotoBg.success) {
setBackgroundProfileId("");
const uploadBackground =
await funGlobal_UploadToStorage({
file: files,
dirId: DIRECTORY_ID.profile_background,
});
if (uploadBackground.success) {
setBackgroundProfileId(uploadBackground.data.id);
setImgBG(buffer);
setFileBG(files);
} else {
ComponentGlobal_NotifikasiPeringatan(
"Gagal upload background profile"
);
}
}
} else {
const uploadBackground = await funGlobal_UploadToStorage({
file: files,
dirId: DIRECTORY_ID.profile_background,
});
if (uploadBackground.success) {
setBackgroundProfileId(uploadBackground.data.id);
setImgBG(buffer);
setFileBG(files);
} else {
ComponentGlobal_NotifikasiPeringatan(
"Gagal upload background profile"
);
}
}
} catch (error) {
console.log(error);
}
}}
accept="image/png,image/jpeg"
>
{(props) => (
<Button
{...props}
radius={"xl"}
leftIcon={<IconCamera />}
bg={MainColor.yellow}
color="yellow"
c={"black"}
>
Upload
</Button>
)}
</FileButton>
</Center>
</Stack>
</Box>
<Profile_ViewUploadBackground
imgBG={imgBG}
backgroundProfileId={backgroundProfileId}
onSetImgBG={setImgBG}
onSetBackgroundProfileId={setBackgroundProfileId}
/>
<Stack mb={"lg"}>
<TextInput

View File

@@ -0,0 +1,170 @@
import { DIRECTORY_ID } from "@/app/lib";
import { MainColor } from "@/app_modules/_global/color";
import {
ComponentGlobal_BoxInformation,
ComponentGlobal_BoxUploadImage,
} from "@/app_modules/_global/component";
import {
funGlobal_DeleteFileById,
funGlobal_UploadToStorage,
} from "@/app_modules/_global/fun";
import { MAX_SIZE } from "@/app_modules/_global/lib";
import { PemberitahuanMaksimalFile } from "@/app_modules/_global/lib/max_size";
import { ComponentGlobal_NotifikasiPeringatan } from "@/app_modules/_global/notif_global";
import { clientLogger } from "@/util/clientLogger";
import {
AspectRatio,
Box,
Button,
Center,
FileButton,
Image,
Stack,
Text,
Loader,
} from "@mantine/core";
import { IconCamera, IconUpload } from "@tabler/icons-react";
import { useState } from "react";
export default function Profile_ViewUploadBackground({
imgBG,
onSetImgBG,
backgroundProfileId,
onSetBackgroundProfileId,
}: {
imgBG: string;
onSetImgBG: (img: string | null) => void;
backgroundProfileId: string;
onSetBackgroundProfileId: (id: string) => void;
}) {
const [isLoading, setLoading] = useState(false);
return (
<>
<Box>
<Stack spacing={"lg"}>
<ComponentGlobal_BoxInformation informasi="Upload foto latar belakang profile anda." />
<ComponentGlobal_BoxUploadImage>
{isLoading ? (
<Center h={"100%"}>
<Loader variant="oval" size={50} color="cyan" />
</Center>
) : imgBG ? (
<AspectRatio ratio={1 / 1} mah={265} mx={"auto"}>
<Image
style={{ maxHeight: 250, margin: "auto", padding: "5px" }}
alt="Foto"
height={250}
src={imgBG}
/>
</AspectRatio>
) : (
<Stack justify="center" align="center" h={"100%"}>
<IconUpload color="white" />
<Text fz={"xs"} c={"white"}>
Upload Background
</Text>
</Stack>
)}
</ComponentGlobal_BoxUploadImage>
<Center>
<FileButton
onChange={async (files: any | null) => {
try {
setLoading(true);
const buffer = URL.createObjectURL(
new Blob([new Uint8Array(await files.arrayBuffer())])
);
if (files.size > MAX_SIZE) {
ComponentGlobal_NotifikasiPeringatan(
PemberitahuanMaksimalFile
);
onSetImgBG(null);
return;
}
if (backgroundProfileId != "") {
const deleteFotoBg = await funGlobal_DeleteFileById({
fileId: backgroundProfileId,
dirId: DIRECTORY_ID.profile_background,
});
if (!deleteFotoBg.success) {
clientLogger.error(
"Client failed delete background:" +
deleteFotoBg.message
);
return;
}
if (deleteFotoBg.success) {
onSetBackgroundProfileId("");
onSetImgBG(null);
const uploadBackground = await funGlobal_UploadToStorage({
file: files,
dirId: DIRECTORY_ID.profile_background,
});
if (!uploadBackground.success) {
clientLogger.error(
"Client failed upload background:" +
uploadBackground.message
);
return;
}
if (uploadBackground.success) {
onSetBackgroundProfileId(uploadBackground.data.id);
onSetImgBG(buffer);
} else {
ComponentGlobal_NotifikasiPeringatan(
"Gagal upload background profile"
);
}
}
} else {
const uploadBackground = await funGlobal_UploadToStorage({
file: files,
dirId: DIRECTORY_ID.profile_background,
});
if (uploadBackground.success) {
onSetBackgroundProfileId(uploadBackground.data.id);
onSetImgBG(buffer);
} else {
ComponentGlobal_NotifikasiPeringatan(
"Gagal upload background profile"
);
}
}
} catch (error) {
clientLogger.error("Client error upload background:", error);
} finally {
setLoading(false);
}
}}
accept="image/png,image/jpeg"
>
{(props) => (
<Button
{...props}
radius={"xl"}
leftIcon={<IconCamera />}
bg={MainColor.yellow}
color="yellow"
c={"black"}
>
Upload
</Button>
)}
</FileButton>
</Center>
</Stack>
</Box>
</>
);
}

View File

@@ -0,0 +1,216 @@
import { DIRECTORY_ID } from "@/app/lib";
import { MainColor } from "@/app_modules/_global/color";
import { ComponentGlobal_BoxInformation } from "@/app_modules/_global/component";
import {
funGlobal_DeleteFileById,
funGlobal_UploadToStorage,
} from "@/app_modules/_global/fun";
import { MAX_SIZE } from "@/app_modules/_global/lib";
import { PemberitahuanMaksimalFile } from "@/app_modules/_global/lib/max_size";
import { ComponentGlobal_NotifikasiPeringatan } from "@/app_modules/_global/notif_global";
import { clientLogger } from "@/util/clientLogger";
import {
Avatar,
Box,
Button,
Center,
FileButton,
Loader,
Paper,
Stack,
} from "@mantine/core";
import { IconCamera } from "@tabler/icons-react";
import { useState } from "react";
export default function Profile_ViewUploadFoto({
imgPP,
onSetImgPP,
fotoProfileId,
onSetFotoProfileId,
}: {
imgPP: string | null | undefined;
onSetImgPP: (img: string | null) => void;
fotoProfileId: string;
onSetFotoProfileId: (id: string) => void;
}) {
const [isLoading, setLoading] = useState(false);
return (
<>
<Box>
<Stack spacing={"lg"}>
<ComponentGlobal_BoxInformation informasi="Upload foto profile anda dengan ukuran maksimal file 3 MB." />
<Center>
{isLoading ? (
<Paper shadow="lg" radius={"100%"}>
<Avatar
variant="light"
color="blue"
size={150}
radius={"100%"}
sx={{
borderStyle: "solid",
borderColor: MainColor.darkblue,
borderWidth: "0.5px",
}}
>
<Center>
<Loader color="cyan" size="xl" />
</Center>
</Avatar>
</Paper>
) : imgPP != undefined || imgPP != null ? (
<Paper shadow="lg" radius={"100%"}>
<Avatar
color={"cyan"}
sx={{
borderStyle: "solid",
borderColor: "gray",
borderWidth: "0.5px",
}}
src={imgPP}
size={150}
radius={"100%"}
/>
</Paper>
) : (
<Paper shadow="lg" radius={"100%"}>
<Avatar
variant="light"
color="blue"
size={150}
radius={"100%"}
sx={{
borderStyle: "solid",
borderColor: MainColor.darkblue,
borderWidth: "0.5px",
}}
/>
</Paper>
)}
</Center>
<Center>
<FileButton
onChange={async (files: any | null) => {
try {
const buffer = URL.createObjectURL(
new Blob([new Uint8Array(await files.arrayBuffer())])
);
if (files.size > MAX_SIZE) {
ComponentGlobal_NotifikasiPeringatan(
PemberitahuanMaksimalFile
);
onSetImgPP(null);
return;
}
if (fotoProfileId != "") {
try {
setLoading(true);
const deleteFotoProfile = await funGlobal_DeleteFileById({
fileId: fotoProfileId,
dirId: DIRECTORY_ID.profile_foto,
});
if (!deleteFotoProfile.success) {
clientLogger.error(
"Client failed delete photo profile:" +
deleteFotoProfile.message
);
return;
}
if (deleteFotoProfile.success) {
onSetFotoProfileId("");
onSetImgPP(null);
const uploadPhoto = await funGlobal_UploadToStorage({
file: files,
dirId: DIRECTORY_ID.profile_foto,
});
if (!uploadPhoto.success) {
clientLogger.error(
"Client failed upload photo profile::" +
uploadPhoto.message
);
return;
}
if (uploadPhoto.success) {
clientLogger.info(
"Client success upload foto profile"
);
onSetFotoProfileId(uploadPhoto.data.id);
onSetImgPP(buffer);
} else {
clientLogger.error(
"Client failed upload foto:",
uploadPhoto.message
);
ComponentGlobal_NotifikasiPeringatan(
"Gagal upload foto profile"
);
}
}
} catch (error) {
clientLogger.error("Client error upload foto:", error);
} finally {
setLoading(false);
}
} else {
try {
setLoading(true);
const uploadPhoto = await funGlobal_UploadToStorage({
file: files,
dirId: DIRECTORY_ID.profile_foto,
});
if (uploadPhoto.success) {
clientLogger.info("Client success upload foto profile");
onSetFotoProfileId(uploadPhoto.data.id);
onSetImgPP(buffer);
} else {
clientLogger.error(
"Client failed upload foto:",
uploadPhoto.message
);
ComponentGlobal_NotifikasiPeringatan(
"Gagal upload foto profile"
);
}
} catch (error) {
clientLogger.error("Client error upload foto:", error);
} finally {
setLoading(false);
}
}
} catch (error) {
clientLogger.error("Client error upload foto:", error);
}
}}
accept="image/png,image/jpeg"
>
{(props) => (
<Button
{...props}
radius={"xl"}
leftIcon={<IconCamera />}
bg={MainColor.yellow}
color="yellow"
c={"black"}
>
Upload
</Button>
)}
</FileButton>
</Center>
</Stack>
</Box>
</>
);
}

View File

@@ -3,6 +3,7 @@
import prisma from "@/app/lib/prisma";
import { RouterHome } from "@/app/lib/router_hipmi/router_home";
import { funGetUserIdByToken } from "@/app_modules/_global/fun/get";
import backendLogger from "@/util/backendLogger";
import { Prisma } from "@prisma/client";
import { revalidatePath } from "next/cache";
@@ -19,6 +20,7 @@ export default async function funCreateNewProfile({
const userLoginId = await funGetUserIdByToken();
if (!userLoginId) {
backendLogger.error("User tidak terautentikasi");
return { status: 400, message: "User tidak terautentikasi" }; // Validasi user login
}
@@ -45,6 +47,7 @@ export default async function funCreateNewProfile({
});
if (!createProfile) {
backendLogger.error("Gagal membuat profile");
return { status: 400, message: "Gagal membuat profile" };
}
@@ -61,7 +64,7 @@ export default async function funCreateNewProfile({
message: "Berhasil",
};
} catch (error) {
console.error("Error creating profile:", error);
backendLogger.error("Terjadi kesalahan pada server", error);
return { status: 500, message: "Terjadi kesalahan pada server" };
}
}

View File

@@ -189,7 +189,7 @@ async function coba_ButtonFileUpload({
formData.append("dirId", dirId);
try {
const res = await fetch("https://wibu-storage.wibudev.com/api/upload", {
const res = await fetch("https://wibu-storage.wibudev.com/api/image/upload", {
method: "POST",
body: formData,
headers: {

View File

@@ -19,7 +19,8 @@ const middlewareConfig: MiddlewareConfig = {
userPath: "/dev/home",
publicRoutes: [
"/",
"/api/upload",
"/api/logs/*",
"/api/image/*",
"/api/job/*",
"/api/validation",
"/api/auth/*",

37
src/util/backendLogger.ts Normal file
View File

@@ -0,0 +1,37 @@
// src/utils/backendLogger.ts
import winston from "winston";
import DailyRotateFile from "winston-daily-rotate-file";
import path from "path";
const backendLogger = winston.createLogger({
level: "info",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
// Error logs
new DailyRotateFile({
filename: path.join("logs/backend/error-%DATE%.log"),
datePattern: "YYYY-MM-DD",
level: "error",
zippedArchive: true,
maxSize: "20m",
maxFiles: "14d",
}),
// Combined logs
new DailyRotateFile({
filename: path.join("logs/backend/combined-%DATE%.log"),
datePattern: "YYYY-MM-DD",
zippedArchive: true,
maxSize: "20m",
maxFiles: "14d",
}),
// Console output in development
...(process.env.NODE_ENV !== "production"
? [new winston.transports.Console()]
: []),
],
});
export default backendLogger;

87
src/util/clientLogger.ts Normal file
View File

@@ -0,0 +1,87 @@
// src/utils/clientLogger.ts
interface LogEntry {
level: "info" | "warn" | "error";
message: string;
data?: any;
timestamp?: string;
}
class ClientLogger {
private queue: LogEntry[] = [];
private readonly maxQueueSize: number = 10;
private readonly apiEndpoint: string = "/api/logs";
private isSending: boolean = false;
private async sendLogs(): Promise<void> {
if (this.isSending || this.queue.length === 0) return;
this.isSending = true;
const logsToSend = [...this.queue];
this.queue = [];
try {
const response = await fetch(this.apiEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(logsToSend),
});
if (!response.ok) {
console.error("Failed to send logs:", response.statusText);
// Restore logs to queue if send failed
this.queue = [...logsToSend, ...this.queue];
}
} catch (error) {
console.error("Error sending logs:", error);
// Restore logs to queue if send failed
this.queue = [...logsToSend, ...this.queue];
} finally {
this.isSending = false;
}
}
private addToQueue(entry: LogEntry): void {
this.queue.push({
...entry,
timestamp: new Date().toISOString(),
});
if (this.queue.length >= this.maxQueueSize) {
this.sendLogs();
}
}
public info(message: string, data?: any): void {
this.addToQueue({ level: "info", message, data });
}
public warn(message: string, data?: any): void {
this.addToQueue({ level: "warn", message, data });
// Send immediately for warnings
this.sendLogs();
}
public error(message: string, error?: Error | any): void {
const errorData =
error instanceof Error
? {
name: error.name,
message: error.message,
stack: error.stack,
}
: error;
this.addToQueue({ level: "error", message, data: errorData });
// Send immediately for errors
this.sendLogs();
}
// Flush remaining logs (useful when page is about to unload)
public flush(): void {
this.sendLogs();
}
}
export const clientLogger = new ClientLogger();

101
src/util/frontend-logger.ts Normal file
View File

@@ -0,0 +1,101 @@
// utils/frontend-logger.ts
import winston from "winston";
import DailyRotateFile from "winston-daily-rotate-file";
import path from "path";
// Define log levels and their priorities
const levels = {
error: 0,
warn: 1,
info: 2,
debug: 3,
};
// Define interface for log entries
export interface LogEntry {
level: keyof typeof levels;
message: string;
data?: any;
timestamp?: string;
userAgent?: string;
ip?: string;
url?: string;
}
// Custom format for log entries
const logFormat = winston.format.combine(
winston.format.timestamp({
format: "YYYY-MM-DD HH:mm:ss",
}),
winston.format.metadata({ fillExcept: ["message", "level", "timestamp"] }),
winston.format.json()
);
// Create the logger instance
const frontendLogger: winston.Logger = winston.createLogger({
levels,
level: process.env.LOG_LEVEL || "info",
format: logFormat,
transports: [
// Daily Rotate File for errors
new DailyRotateFile({
filename: path.join(process.cwd(), "logs/frontend/error-%DATE%.log"),
datePattern: "YYYY-MM-DD",
zippedArchive: true,
maxSize: "20m",
maxFiles: "14d",
level: "error",
format: logFormat,
}),
// Daily Rotate File for all logs
new DailyRotateFile({
filename: path.join(process.cwd(), "logs/frontend/combined-%DATE%.log"),
datePattern: "YYYY-MM-DD",
zippedArchive: true,
maxSize: "20m",
maxFiles: "14d",
format: logFormat,
}),
],
// Handle errors from the logger itself
exitOnError: false,
});
// Add console transport in development
if (process.env.NODE_ENV !== "production") {
frontendLogger.add(
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
),
})
);
}
// Helper functions for type-safe logging
export function logError(message: string, data?: any) {
frontendLogger.error(message, { data });
}
export function logWarn(message: string, data?: any) {
frontendLogger.warn(message, { data });
}
export function logInfo(message: string, data?: any) {
frontendLogger.info(message, { data });
}
export function logDebug(message: string, data?: any) {
frontendLogger.debug(message, { data });
}
// Helper function for dynamic logging
export function log(entry: LogEntry) {
const { level, message, ...metadata } = entry;
frontendLogger[level](message, metadata);
}
// Export the logger instance as default
export default frontendLogger;