feat: debounce search, tambah filter source & date range di bug-reports, hapus seafile.ts
- Debounce search input (400ms, min 3 karakter) sesuai konvensi - Tambah filter source (QC/SYSTEM/USER) dan date range di bug-reports - Backend /api/bugs support query param source, dateFrom, dateTo - Update API_URLS.getBugs dengan param baru - Hapus seafile.ts (dead code, tidak digunakan)
This commit is contained in:
20
src/app.ts
20
src/app.ts
@@ -805,6 +805,9 @@ export function createApp() {
|
|||||||
const search = query.search || ''
|
const search = query.search || ''
|
||||||
const app = query.app as any
|
const app = query.app as any
|
||||||
const status = query.status as any
|
const status = query.status as any
|
||||||
|
const source = query.source as any
|
||||||
|
const dateFrom = query.dateFrom
|
||||||
|
const dateTo = query.dateTo
|
||||||
|
|
||||||
const where: any = {}
|
const where: any = {}
|
||||||
if (search) {
|
if (search) {
|
||||||
@@ -821,6 +824,18 @@ export function createApp() {
|
|||||||
if (status && status !== 'all') {
|
if (status && status !== 'all') {
|
||||||
where.status = status
|
where.status = status
|
||||||
}
|
}
|
||||||
|
if (source && source !== 'all') {
|
||||||
|
where.source = source
|
||||||
|
}
|
||||||
|
if (dateFrom || dateTo) {
|
||||||
|
where.createdAt = {}
|
||||||
|
if (dateFrom) where.createdAt.gte = new Date(dateFrom)
|
||||||
|
if (dateTo) {
|
||||||
|
const end = new Date(dateTo)
|
||||||
|
end.setHours(23, 59, 59, 999)
|
||||||
|
where.createdAt.lte = end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const [bugs, total] = await Promise.all([
|
const [bugs, total] = await Promise.all([
|
||||||
prisma.bug.findMany({
|
prisma.bug.findMany({
|
||||||
@@ -852,10 +867,13 @@ export function createApp() {
|
|||||||
search: t.Optional(t.String({ description: 'Cari berdasarkan deskripsi, device, OS, atau versi' })),
|
search: t.Optional(t.String({ description: 'Cari berdasarkan deskripsi, device, OS, atau versi' })),
|
||||||
app: t.Optional(t.String({ description: 'Filter berdasarkan ID aplikasi, atau "all"' })),
|
app: t.Optional(t.String({ description: 'Filter berdasarkan ID aplikasi, atau "all"' })),
|
||||||
status: t.Optional(t.String({ description: 'Filter status: OPEN | ON_HOLD | IN_PROGRESS | RESOLVED | RELEASED | CLOSED | all' })),
|
status: t.Optional(t.String({ description: 'Filter status: OPEN | ON_HOLD | IN_PROGRESS | RESOLVED | RELEASED | CLOSED | all' })),
|
||||||
|
source: t.Optional(t.String({ description: 'Filter sumber: QC | SYSTEM | USER | all' })),
|
||||||
|
dateFrom: t.Optional(t.String({ description: 'Filter dari tanggal (YYYY-MM-DD)' })),
|
||||||
|
dateTo: t.Optional(t.String({ description: 'Filter sampai tanggal (YYYY-MM-DD)' })),
|
||||||
}),
|
}),
|
||||||
detail: {
|
detail: {
|
||||||
summary: 'List Bug Reports',
|
summary: 'List Bug Reports',
|
||||||
description: 'Mengembalikan daftar bug report dengan pagination, beserta data pelapor, gambar, dan riwayat status (BugLog). Mendukung filter berdasarkan aplikasi dan status.',
|
description: 'Mengembalikan daftar bug report dengan pagination, beserta data pelapor, gambar, dan riwayat status (BugLog). Mendukung filter berdasarkan aplikasi, status, source, dan tanggal.',
|
||||||
tags: ['Bugs'],
|
tags: ['Bugs'],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -51,8 +51,13 @@ export const API_URLS = {
|
|||||||
createOperator: () => `/api/operators`,
|
createOperator: () => `/api/operators`,
|
||||||
editOperator: (id: string) => `/api/operators/${id}`,
|
editOperator: (id: string) => `/api/operators/${id}`,
|
||||||
deleteOperator: (id: string) => `/api/operators/${id}`,
|
deleteOperator: (id: string) => `/api/operators/${id}`,
|
||||||
getBugs: (page: number, search: string, app: string, status: string) =>
|
getBugs: (page: number, search: string, app: string, status: string, source?: string, dateFrom?: string, dateTo?: string) => {
|
||||||
`/api/bugs?page=${page}&search=${encodeURIComponent(search)}&app=${app}&status=${status}`,
|
const params = new URLSearchParams({ page: String(page), search: encodeURIComponent(search), app, status })
|
||||||
|
if (source && source !== 'all') params.set('source', source)
|
||||||
|
if (dateFrom) params.set('dateFrom', dateFrom)
|
||||||
|
if (dateTo) params.set('dateTo', dateTo)
|
||||||
|
return `/api/bugs?${params}`
|
||||||
|
},
|
||||||
createBug: () => `/api/bugs`,
|
createBug: () => `/api/bugs`,
|
||||||
uploadImage: () => `/api/upload/image`,
|
uploadImage: () => `/api/upload/image`,
|
||||||
updateBugStatus: (id: string) => `/api/bugs/${id}/status`,
|
updateBugStatus: (id: string) => `/api/bugs/${id}/status`,
|
||||||
|
|||||||
@@ -27,12 +27,13 @@ import {
|
|||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@mantine/core'
|
} from '@mantine/core'
|
||||||
import { useDisclosure } from '@mantine/hooks'
|
import { useDebouncedValue, useDisclosure } from '@mantine/hooks'
|
||||||
|
import { DatePickerInput, type DatesRangeValue } from '@mantine/dates'
|
||||||
import { notifications } from '@mantine/notifications'
|
import { notifications } from '@mantine/notifications'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
TbAlertTriangle,
|
TbAlertTriangle,
|
||||||
TbBug,
|
TbBug,
|
||||||
@@ -71,18 +72,35 @@ const STATUS_LABEL: Record<string, string> = {
|
|||||||
function ListErrorsPage() {
|
function ListErrorsPage() {
|
||||||
const [page, setPage] = useState(1)
|
const [page, setPage] = useState(1)
|
||||||
const [search, setSearch] = useState('')
|
const [search, setSearch] = useState('')
|
||||||
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const [app, setApp] = useState('all')
|
const [app, setApp] = useState('all')
|
||||||
const [status, setStatus] = useState('all')
|
const [status, setStatus] = useState('all')
|
||||||
|
const [source, setSource] = useState('all')
|
||||||
|
const [dateRange, setDateRange] = useState<DatesRangeValue>([null, null])
|
||||||
|
|
||||||
|
const [debouncedSearch] = useDebouncedValue(search, 400)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (debouncedSearch.length >= 3 || debouncedSearch.length === 0) {
|
||||||
|
setSearchQuery(debouncedSearch)
|
||||||
|
setPage(1)
|
||||||
|
}
|
||||||
|
}, [debouncedSearch])
|
||||||
|
|
||||||
|
useEffect(() => { setPage(1) }, [app, status, source, dateRange])
|
||||||
|
|
||||||
const [showLogs, setShowLogs] = useState<Record<string, boolean>>({})
|
const [showLogs, setShowLogs] = useState<Record<string, boolean>>({})
|
||||||
const [showStackTrace, setShowStackTrace] = useState<Record<string, boolean>>({})
|
const [showStackTrace, setShowStackTrace] = useState<Record<string, boolean>>({})
|
||||||
|
|
||||||
|
const dateFrom = dateRange[0] ? dayjs(dateRange[0]).format('YYYY-MM-DD') : undefined
|
||||||
|
const dateTo = dateRange[1] ? dayjs(dateRange[1]).format('YYYY-MM-DD') : undefined
|
||||||
|
|
||||||
const toggleLogs = (bugId: string) => setShowLogs((prev) => ({ ...prev, [bugId]: !prev[bugId] }))
|
const toggleLogs = (bugId: string) => setShowLogs((prev) => ({ ...prev, [bugId]: !prev[bugId] }))
|
||||||
const toggleStackTrace = (bugId: string) => setShowStackTrace((prev) => ({ ...prev, [bugId]: !prev[bugId] }))
|
const toggleStackTrace = (bugId: string) => setShowStackTrace((prev) => ({ ...prev, [bugId]: !prev[bugId] }))
|
||||||
|
|
||||||
const { data, isLoading, refetch } = useQuery({
|
const { data, isLoading, refetch } = useQuery({
|
||||||
queryKey: ['bugs', { page, search, app, status }],
|
queryKey: ['bugs', { page, searchQuery, app, status, source, dateFrom, dateTo }],
|
||||||
queryFn: () => fetch(API_URLS.getBugs(page, search, app, status)).then((r) => r.json()),
|
queryFn: () => fetch(API_URLS.getBugs(page, searchQuery, app, status, source, dateFrom, dateTo)).then((r) => r.json()),
|
||||||
})
|
})
|
||||||
|
|
||||||
const { data: appsList } = useQuery({
|
const { data: appsList } = useQuery({
|
||||||
@@ -411,7 +429,7 @@ function ListErrorsPage() {
|
|||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Paper withBorder radius="2xl" className="glass" p="md">
|
<Paper withBorder radius="2xl" className="glass" p="md">
|
||||||
<SimpleGrid cols={{ base: 1, sm: 4 }} mb="lg">
|
<SimpleGrid cols={{ base: 1, sm: 2, md: 3 }} mb="sm">
|
||||||
<TextInput
|
<TextInput
|
||||||
label="Search"
|
label="Search"
|
||||||
placeholder="Description, device, OS..."
|
placeholder="Description, device, OS..."
|
||||||
@@ -444,12 +462,35 @@ function ListErrorsPage() {
|
|||||||
onChange={(val) => setStatus(val || 'all')}
|
onChange={(val) => setStatus(val || 'all')}
|
||||||
radius="md"
|
radius="md"
|
||||||
/>
|
/>
|
||||||
|
<Select
|
||||||
|
label="Source"
|
||||||
|
size="sm"
|
||||||
|
data={[
|
||||||
|
{ value: 'all', label: 'All Sources' },
|
||||||
|
{ value: 'QC', label: 'QC' },
|
||||||
|
{ value: 'SYSTEM', label: 'System' },
|
||||||
|
{ value: 'USER', label: 'User' },
|
||||||
|
]}
|
||||||
|
value={source}
|
||||||
|
onChange={(val) => setSource(val || 'all')}
|
||||||
|
radius="md"
|
||||||
|
/>
|
||||||
|
<DatePickerInput
|
||||||
|
type="range"
|
||||||
|
label="Date Range"
|
||||||
|
placeholder="Pick date range"
|
||||||
|
size="sm"
|
||||||
|
radius="md"
|
||||||
|
value={dateRange}
|
||||||
|
onChange={setDateRange}
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
<Stack justify="flex-end">
|
<Stack justify="flex-end">
|
||||||
<Button
|
<Button
|
||||||
variant="filled"
|
variant="filled"
|
||||||
color="violet"
|
color="violet"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => { setSearch(''); setApp('all'); setStatus('all') }}
|
onClick={() => { setSearch(''); setApp('all'); setStatus('all'); setSource('all'); setDateRange([null, null]) }}
|
||||||
>
|
>
|
||||||
Reset Filters
|
Reset Filters
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,252 +0,0 @@
|
|||||||
#!/usr/bin/env bun
|
|
||||||
import { promises as fs } from 'fs';
|
|
||||||
import * as os from 'os';
|
|
||||||
import * as path from 'path';
|
|
||||||
|
|
||||||
// --- Constants ---
|
|
||||||
const CONFIG_FILE = path.join(os.homedir(), '.note.conf');
|
|
||||||
|
|
||||||
// --- Types ---
|
|
||||||
interface Config {
|
|
||||||
TOKEN?: string;
|
|
||||||
REPO?: string;
|
|
||||||
URL?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultConfigSF: Config = {
|
|
||||||
TOKEN: process.env.SF_TOKEN,
|
|
||||||
REPO: process.env.SF_REPO,
|
|
||||||
URL: process.env.SF_URL,
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loadConfig(): Promise<Config> {
|
|
||||||
if (!(await fs.stat(CONFIG_FILE)).isFile()) {
|
|
||||||
console.error(`⚠️ Config file not found at ${CONFIG_FILE}`);
|
|
||||||
console.error('Run: bun note.ts config to create/edit it.');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const configContent = await fs.readFile(CONFIG_FILE, 'utf8');
|
|
||||||
const config: Config = {};
|
|
||||||
|
|
||||||
configContent.split('\n').forEach((line) => {
|
|
||||||
const trimmed = line.trim();
|
|
||||||
if (!trimmed || trimmed.startsWith('#')) return;
|
|
||||||
|
|
||||||
const [key, ...valueParts] = trimmed.split('=');
|
|
||||||
if (key && valueParts.length > 0) {
|
|
||||||
let value = valueParts.join('=').trim();
|
|
||||||
if (
|
|
||||||
(value.startsWith('"') && value.endsWith('"')) ||
|
|
||||||
(value.startsWith("'") && value.endsWith("'"))
|
|
||||||
) {
|
|
||||||
value = value.slice(1, -1);
|
|
||||||
}
|
|
||||||
config[key as keyof Config] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!config.TOKEN || !config.REPO || !config.URL) {
|
|
||||||
console.error(`❌ Config invalid. Please set TOKEN, REPO, and URL inside ${CONFIG_FILE}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- HTTP Helpers ---
|
|
||||||
export async function fetchWithAuth(config: Config, url: string, options: RequestInit = {}): Promise<Response> {
|
|
||||||
const headers = {
|
|
||||||
Authorization: `Token ${config.TOKEN}`,
|
|
||||||
...options.headers,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await fetch(url, { ...options, headers });
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error(`❌ Request failed: ${response.status} ${response.statusText}`);
|
|
||||||
console.error(`🔍 URL: ${url}`);
|
|
||||||
console.error(`🔍 Headers:`, headers);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const errorText = await response.text();
|
|
||||||
console.error(`🔍 Response body: ${errorText}`);
|
|
||||||
} catch {
|
|
||||||
console.error('🔍 Could not read response body');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Commands ---
|
|
||||||
export async function testConnection(config: Config): Promise<string> {
|
|
||||||
try {
|
|
||||||
const response = await fetchWithAuth(config, `${config.URL}/ping/`);
|
|
||||||
return `✅ API connection successful: ${await response.text()}`
|
|
||||||
} catch {
|
|
||||||
// return '⚠️ API ping failed, trying repo access...'
|
|
||||||
try {
|
|
||||||
await fetchWithAuth(config, `${config.URL}/${config.REPO}/`);
|
|
||||||
return `✅ Repo access successful`
|
|
||||||
} catch {
|
|
||||||
return '❌ Both API ping and repo access failed'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function listFiles(config: Config): Promise<{ name: string }[]> {
|
|
||||||
const url = `${config.URL}/${config.REPO}/dir/?p=/`;
|
|
||||||
const response = await fetchWithAuth(config, url);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const files = (await response.json()) as { name: string }[];
|
|
||||||
return files
|
|
||||||
} catch {
|
|
||||||
console.error('❌ Failed to parse response');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function catFile(config: Config, folder: string, fileName: string): Promise<ArrayBuffer> {
|
|
||||||
const downloadUrlResponse = await fetchWithAuth(config, `${config.URL}/${config.REPO}/file/?p=/${folder}/${fileName}`);
|
|
||||||
const downloadUrl = (await downloadUrlResponse.text()).replace(/"/g, '');
|
|
||||||
|
|
||||||
// Download file sebagai binary, BUKAN text
|
|
||||||
const fileResponse = await fetchWithAuth(config, downloadUrl);
|
|
||||||
const buffer = await fileResponse.arrayBuffer();
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function uploadFile(config: Config, file: File, folder: string): Promise<string> {
|
|
||||||
const remoteName = path.basename(file.name);
|
|
||||||
|
|
||||||
// 1. Dapatkan upload link (pakai Authorization)
|
|
||||||
const uploadUrlResponse = await fetchWithAuth(
|
|
||||||
config,
|
|
||||||
`${config.URL}/${config.REPO}/upload-link/`
|
|
||||||
);
|
|
||||||
const uploadUrl = (await uploadUrlResponse.text()).replace(/"/g, "");
|
|
||||||
|
|
||||||
// 2. Siapkan form-data
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("parent_dir", "/");
|
|
||||||
formData.append("relative_path", folder); // tanpa slash di akhir
|
|
||||||
formData.append("file", file, remoteName); // file langsung, jangan pakai Blob
|
|
||||||
|
|
||||||
// 3. Upload file TANPA Authorization header, token di query param
|
|
||||||
const res = await fetch(`${uploadUrl}?token=${config.TOKEN}`, {
|
|
||||||
method: "POST",
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
|
|
||||||
const text = await res.text();
|
|
||||||
|
|
||||||
if (!res.ok) return 'gagal'
|
|
||||||
return `✅ Uploaded ${file.name} successfully`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function uploadFileBase64(config: Config, base64File: { name: string; data: string; }): Promise<string> {
|
|
||||||
const remoteName = path.basename(base64File.name);
|
|
||||||
|
|
||||||
// 1. Dapatkan upload link (pakai Authorization)
|
|
||||||
const uploadUrlResponse = await fetchWithAuth(
|
|
||||||
config,
|
|
||||||
`${config.URL}/${config.REPO}/upload-link/`
|
|
||||||
);
|
|
||||||
const uploadUrl = (await uploadUrlResponse.text()).replace(/"/g, "");
|
|
||||||
|
|
||||||
// 2. Konversi base64 ke Blob
|
|
||||||
const binary = Buffer.from(base64File.data, "base64");
|
|
||||||
const blob = new Blob([binary]);
|
|
||||||
|
|
||||||
// 3. Siapkan form-data
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("parent_dir", "/");
|
|
||||||
formData.append("relative_path", "syarat-dokumen"); // tanpa slash di akhir
|
|
||||||
formData.append("file", blob, remoteName);
|
|
||||||
|
|
||||||
// 4. Upload file TANPA Authorization header, token di query param
|
|
||||||
const res = await fetch(`${uploadUrl}?token=${config.TOKEN}`, {
|
|
||||||
method: "POST",
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
|
|
||||||
const text = await res.text();
|
|
||||||
|
|
||||||
if (!res.ok) throw new Error(`Upload failed: ${text}`);
|
|
||||||
return `✅ Uploaded ${base64File.name} successfully`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function uploadFileToFolder(config: Config, base64File: { name: string; data: string; }, folder: 'syarat-dokumen' | 'pengaduan'): Promise<string> {
|
|
||||||
const remoteName = path.basename(base64File.name);
|
|
||||||
|
|
||||||
// 1. Dapatkan upload link (pakai Authorization)
|
|
||||||
const uploadUrlResponse = await fetchWithAuth(
|
|
||||||
config,
|
|
||||||
`${config.URL}/${config.REPO}/upload-link/`
|
|
||||||
);
|
|
||||||
const uploadUrl = (await uploadUrlResponse.text()).replace(/"/g, "");
|
|
||||||
|
|
||||||
// 2. Konversi base64 ke Blob
|
|
||||||
const binary = Buffer.from(base64File.data, "base64");
|
|
||||||
const blob = new Blob([binary]);
|
|
||||||
|
|
||||||
// 3. Siapkan form-data
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("parent_dir", "/");
|
|
||||||
formData.append("relative_path", folder); // tanpa slash di akhir
|
|
||||||
formData.append("file", blob, remoteName);
|
|
||||||
|
|
||||||
// 4. Upload file TANPA Authorization header, token di query param
|
|
||||||
const res = await fetch(`${uploadUrl}?token=${config.TOKEN}`, {
|
|
||||||
method: "POST",
|
|
||||||
body: formData,
|
|
||||||
});
|
|
||||||
|
|
||||||
const text = await res.text();
|
|
||||||
|
|
||||||
if (!res.ok) throw new Error(`Upload failed: ${text}`);
|
|
||||||
return `✅ Uploaded ${base64File.name} successfully`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export async function removeFile(config: Config, fileName: string, folder: string): Promise<string> {
|
|
||||||
const res = await fetchWithAuth(config, `${config.URL}/${config.REPO}/file/?p=/${folder}/${fileName}`, { method: 'DELETE' });
|
|
||||||
|
|
||||||
if (!res.ok) return 'gagal menghapus file';
|
|
||||||
return `🗑️ Removed ${fileName}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function moveFile(config: Config, oldName: string, newName: string): Promise<string> {
|
|
||||||
const url = `${config.URL}/${config.REPO}/file/?p=/${oldName}`;
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('operation', 'rename');
|
|
||||||
formData.append('newname', newName);
|
|
||||||
|
|
||||||
await fetchWithAuth(config, url, { method: 'POST', body: formData });
|
|
||||||
return `✏️ Renamed ${oldName} → ${newName}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function downloadFile(config: Config, fileName: string, folder: string, localFile?: string): Promise<string> {
|
|
||||||
const localName = localFile || fileName;
|
|
||||||
// 🔹 gabungkan path folder + file
|
|
||||||
const filePath = `/${folder}/${fileName}`.replace(/\/+/g, "/");
|
|
||||||
|
|
||||||
// 🔹 encode path agar aman (spasi, dll)
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
p: filePath,
|
|
||||||
});
|
|
||||||
|
|
||||||
const downloadUrlResponse = await fetchWithAuth(config, `${config.URL}/${config.REPO}/file/?${params.toString()}`);
|
|
||||||
if (!downloadUrlResponse.ok)
|
|
||||||
return 'gagal'
|
|
||||||
const downloadUrl = (await downloadUrlResponse.text()).replace(/"/g, '');
|
|
||||||
const buffer = Buffer.from(await (await fetchWithAuth(config, downloadUrl)).arrayBuffer());
|
|
||||||
await fs.writeFile(localName, buffer);
|
|
||||||
return `⬇️ Downloaded ${fileName} → ${localName}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getFileLink(config: Config, fileName: string): Promise<string> {
|
|
||||||
const downloadUrlResponse = await fetchWithAuth(config, `${config.URL}/${config.REPO}/file/?p=/${fileName}`);
|
|
||||||
return `🔗 Link for ${fileName}:\n${(await downloadUrlResponse.text()).replace(/"/g, '')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user