feat: delete record and copy message
This commit is contained in:
1 parent
5926eff9c6
commit
1c48a23fab
4 files changed
+113
-17
No files matched your search
@@ -111,3 +111,30 @@ export async function purgeRecordsBeforeId(
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
export async function deleteRecord(
|
||||
db: D1Database,
|
||||
id: number,
|
||||
deleteFiles: (path: string[]) => Promise<void>
|
||||
) {
|
||||
// 1. get record
|
||||
const record = await db
|
||||
.prepare(`SELECT * FROM upload_record WHERE id = ?`)
|
||||
.bind(id)
|
||||
.first<UploadRecord>();
|
||||
if (!record) return;
|
||||
|
||||
// 2. delete files
|
||||
if (!record.files) return;
|
||||
try {
|
||||
const files = JSON.parse(record.files) as {
|
||||
name: string;
|
||||
path: string;
|
||||
size: number;
|
||||
}[];
|
||||
await deleteFiles(files.map((file) => file.path));
|
||||
} catch {}
|
||||
|
||||
// 3. delete record
|
||||
await db.prepare(`DELETE FROM upload_record WHERE id = ?`).bind(id).run();
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Hono } from "hono";
|
||||
import {
|
||||
createUploadRecord,
|
||||
deleteRecord,
|
||||
getUploadRecords,
|
||||
migrateTables,
|
||||
purgeRecordsBeforeId,
|
||||
@@ -92,6 +93,13 @@ app.get("/api/download", async (c) => {
|
||||
return new Response(r.body, { headers });
|
||||
});
|
||||
|
||||
app.post("/api/delete", async (c) => {
|
||||
const body = await c.req.json();
|
||||
const id = +body.id;
|
||||
await deleteRecord(c.env.DB, id, (paths) => c.env.MY_BUCKET.delete(paths));
|
||||
return c.json({ ok: true });
|
||||
});
|
||||
|
||||
app.post("/api/purge", async (c) => {
|
||||
const beforeId = 9999999;
|
||||
await purgeRecordsBeforeId(c.env.DB, beforeId, (paths) =>
|
||||
|
||||
+32
-11
@@ -1,32 +1,53 @@
|
||||
import { useState } from 'react';
|
||||
import './App.css';
|
||||
import { ContentInput } from './components/ContentInput';
|
||||
import { UploadRecords } from './components/UploadRecords';
|
||||
|
||||
const App = () => {
|
||||
const [progress, setProgress] = useState(0);
|
||||
function startUpload(text: string, files: File[]) {
|
||||
const body = new FormData();
|
||||
body.append('message', text);
|
||||
files.forEach((file) => body.append('files', file));
|
||||
|
||||
fetch('/api/upload', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-uploader': 'yon',
|
||||
},
|
||||
body,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
console.log('data', data);
|
||||
});
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/api/upload');
|
||||
xhr.setRequestHeader('x-uploader', 'yon');
|
||||
|
||||
xhr.upload.onprogress = (e) => {
|
||||
if (e.lengthComputable) {
|
||||
const percentComplete = (e.loaded / e.total) * 100;
|
||||
setProgress(percentComplete);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status === 200) {
|
||||
const data = JSON.parse(xhr.responseText);
|
||||
console.log('response', data);
|
||||
setProgress(0);
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send(body);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-slate-2 h-vh">
|
||||
<ContentInput onSend={startUpload} />
|
||||
<SimpleProgressBar progress={progress} />
|
||||
<UploadRecords />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function SimpleProgressBar(props: { progress: number }) {
|
||||
return (
|
||||
<div className="bg-slate-4 h-2 rounded overflow-hidden">
|
||||
<div className="bg-slate-6 h-full" style={{ width: `${props.progress}%` }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -33,9 +33,6 @@ export const UploadRecords = memo<Props>((props) => {
|
||||
});
|
||||
|
||||
const UploadRecordItem = memo((props: { record: UploadRecord }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const toggleOpen = () => setOpen(!open);
|
||||
|
||||
const files = useMemo(() => {
|
||||
if (!props.record.files) return [];
|
||||
return JSON.parse(props.record.files) as { name: string; path: string; size: number }[];
|
||||
@@ -58,12 +55,26 @@ const UploadRecordItem = memo((props: { record: UploadRecord }) => {
|
||||
{toReadableSize(props.record.size)}
|
||||
</span>
|
||||
)}
|
||||
|
||||
<div className="mr-a">
|
||||
<br />
|
||||
</div>
|
||||
|
||||
{!!props.record.message && (
|
||||
<span className="cursor-pointer hover:text-black" onClick={() => copyToClipboard(props.record.message)}>
|
||||
<i className="i-mdi-clipboard mr-1"></i>
|
||||
Copy Message
|
||||
</span>
|
||||
)}
|
||||
|
||||
<span className="cursor-pointer hover:text-red" onClick={() => deleteRecord(props.record.id)}>
|
||||
<i className="i-mdi-trash mr-1"></i>
|
||||
Delete
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{props.record.thumbnail}
|
||||
<pre className="max-h-md overflow-auto ws-pre-wrap" onClick={toggleOpen}>
|
||||
{props.record.message}
|
||||
</pre>
|
||||
<pre className="max-h-md overflow-auto ws-pre-wrap">{props.record.message}</pre>
|
||||
|
||||
{files.length > 0 && (
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -94,3 +105,32 @@ function toReadableSize(size: number) {
|
||||
}
|
||||
return `${size.toFixed(1)} ${units[unit]}`;
|
||||
}
|
||||
|
||||
function copyToClipboard(text: string) {
|
||||
try {
|
||||
navigator.clipboard.writeText(text);
|
||||
} catch (err) {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = text;
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteRecord(id: number) {
|
||||
if (!confirm('Are you sure you want to delete this record?')) return;
|
||||
fetch('/api/delete', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ id }),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
console.log('deleted data', data);
|
||||
window.document.dispatchEvent(new FocusEvent('focus'));
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user