feat: Implement is_deleted flag for items, add new utility scripts, and refine UI styles.
This commit is contained in:
40
debug/fix_deleted.mjs
Normal file
40
debug/fix_deleted.mjs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
import db from "../src/inc/sql.mjs";
|
||||||
|
import { promises as fs } from "fs";
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
console.log("Starting migration...");
|
||||||
|
const items = await db`select id, dest from items where active = false`;
|
||||||
|
console.log(`Found ${items.length} inactive items.`);
|
||||||
|
|
||||||
|
let trashCount = 0;
|
||||||
|
let pendingCount = 0;
|
||||||
|
let brokenCount = 0;
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
try {
|
||||||
|
await fs.access(`./deleted/b/${item.dest}`);
|
||||||
|
// File exists in deleted, mark as is_deleted = true
|
||||||
|
await db`update items set is_deleted = true where id = ${item.id}`;
|
||||||
|
trashCount++;
|
||||||
|
} catch {
|
||||||
|
// Not in deleted, check public
|
||||||
|
try {
|
||||||
|
await fs.access(`./public/b/${item.dest}`);
|
||||||
|
// In public, is_deleted = false (default)
|
||||||
|
pendingCount++;
|
||||||
|
} catch {
|
||||||
|
// Not in either? Broken.
|
||||||
|
console.log(`Item ${item.id} (${item.dest}) missing from both locations.`);
|
||||||
|
brokenCount++;
|
||||||
|
// Default is false, which effectively puts it in pending queue as 'broken'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Migration complete.`);
|
||||||
|
console.log(`Trash (soft-deleted): ${trashCount}`);
|
||||||
|
console.log(`Pending: ${pendingCount}`);
|
||||||
|
console.log(`Broken: ${brokenCount}`);
|
||||||
|
process.exit(0);
|
||||||
|
})();
|
||||||
@@ -10,7 +10,8 @@
|
|||||||
"autotagger": "node debug/autotagger.mjs",
|
"autotagger": "node debug/autotagger.mjs",
|
||||||
"thumbnailer": "node debug/thumbnailer.mjs",
|
"thumbnailer": "node debug/thumbnailer.mjs",
|
||||||
"test": "node debug/test.mjs",
|
"test": "node debug/test.mjs",
|
||||||
"clean": "node debug/clean.mjs"
|
"clean": "node debug/clean.mjs",
|
||||||
|
"fix:deleted": "node debug/fix_deleted.mjs"
|
||||||
},
|
},
|
||||||
"author": "Flummi",
|
"author": "Flummi",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -20,4 +21,4 @@
|
|||||||
"flummpress": "^2.0.5",
|
"flummpress": "^2.0.5",
|
||||||
"postgres": "^3.3.4"
|
"postgres": "^3.3.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3386,7 +3386,7 @@ input#s_avatar {
|
|||||||
.login-modal-content {
|
.login-modal-content {
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
border: 1px solid var(--accent);
|
border: 1px solid var(--accent);
|
||||||
border-radius: 8px;
|
border-radius: 0;
|
||||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
||||||
max-width: 400px;
|
max-width: 400px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -3405,7 +3405,18 @@ input#s_avatar {
|
|||||||
.login-modal-content .login-image {
|
.login-modal-content .login-image {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
border-radius: 4px;
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: #39ff14;
|
||||||
|
/* Matrix neon green */
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 5px;
|
||||||
|
box-shadow: 0 0 5px #39ff14;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-modal-content input[type="text"],
|
.login-modal-content input[type="text"],
|
||||||
|
|||||||
@@ -30,22 +30,21 @@ export default async bot => {
|
|||||||
f: async e => {
|
f: async e => {
|
||||||
logger.info(`${e.network} -> ${e.channel} -> ${e.user.nick}: ${e.message}`);
|
logger.info(`${e.network} -> ${e.channel} -> ${e.user.nick}: ${e.message}`);
|
||||||
|
|
||||||
let [ cmd, id ] = e.opt.data.split(':');
|
let [cmd, id] = e.opt.data.split(':');
|
||||||
let f0ck;
|
let f0ck;
|
||||||
id = +id;
|
id = +id;
|
||||||
|
|
||||||
if(cmd.startsWith('b_settag_')) {
|
if (cmd.startsWith('b_settag_')) {
|
||||||
const tagid = +cmd.replace('b_settag_', '');
|
const tagid = +cmd.replace('b_settag_', '');
|
||||||
|
|
||||||
if(!(await lib.getTags(id)).filter(tag => tag.id == tagid).length) {
|
if (!(await lib.getTags(id)).filter(tag => tag.id == tagid).length) {
|
||||||
// insert
|
// insert
|
||||||
await db`
|
await db`
|
||||||
insert into "tags_assign" ${
|
insert into "tags_assign" ${db({
|
||||||
db({
|
item_id: id,
|
||||||
item_id: id,
|
tag_id: tagid,
|
||||||
tag_id: tagid,
|
user_id: 1
|
||||||
user_id: 1
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -71,9 +70,9 @@ export default async bot => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(cmd) {
|
switch (cmd) {
|
||||||
case "b_tags":
|
case "b_tags":
|
||||||
if(!id)
|
if (!id)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const keyboard = await tagkeyboard(id);
|
const keyboard = await tagkeyboard(id);
|
||||||
@@ -87,9 +86,9 @@ export default async bot => {
|
|||||||
]]
|
]]
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "b_back":
|
case "b_back":
|
||||||
if(!id)
|
if (!id)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await e.editMessageText(e.raw.chat.id, e.raw.message_id, e.message, {
|
await e.editMessageText(e.raw.chat.id, e.raw.message_id, e.message, {
|
||||||
@@ -104,24 +103,23 @@ export default async bot => {
|
|||||||
]]
|
]]
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "b_sfw":
|
case "b_sfw":
|
||||||
|
|
||||||
if(!id)
|
if (!id)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(!await lib.hasTag(id, 1)) {
|
if (!await lib.hasTag(id, 1)) {
|
||||||
// insert
|
// insert
|
||||||
await db`
|
await db`
|
||||||
insert into "tags_assign" ${
|
insert into "tags_assign" ${db({
|
||||||
db({
|
item_id: id,
|
||||||
item_id: id,
|
tag_id: 1, // sfw
|
||||||
tag_id: 1, // sfw
|
user_id: 1
|
||||||
user_id: 1
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
if(await lib.hasTag(id, 2)) {
|
if (await lib.hasTag(id, 2)) {
|
||||||
await db`
|
await db`
|
||||||
delete from "tags_assign"
|
delete from "tags_assign"
|
||||||
where tag_id = 2
|
where tag_id = 2
|
||||||
@@ -151,23 +149,22 @@ export default async bot => {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "b_nsfw":
|
case "b_nsfw":
|
||||||
if(!id)
|
if (!id)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if(!await lib.hasTag(id, 2)) {
|
if (!await lib.hasTag(id, 2)) {
|
||||||
// insert
|
// insert
|
||||||
await db`
|
await db`
|
||||||
insert into "tags_assign" ${
|
insert into "tags_assign" ${db({
|
||||||
db({
|
item_id: id,
|
||||||
item_id: id,
|
tag_id: 2, // nsfw
|
||||||
tag_id: 2, // nsfw
|
user_id: 1
|
||||||
user_id: 1
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
if(await lib.hasTag(id, 1)) {
|
if (await lib.hasTag(id, 1)) {
|
||||||
await db`
|
await db`
|
||||||
delete from "tags_assign"
|
delete from "tags_assign"
|
||||||
where tag_id = 1
|
where tag_id = 1
|
||||||
@@ -196,9 +193,9 @@ export default async bot => {
|
|||||||
]]
|
]]
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "b_delete":
|
case "b_delete":
|
||||||
if(id <= 1)
|
if (id <= 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
e.user = {
|
e.user = {
|
||||||
@@ -218,33 +215,33 @@ export default async bot => {
|
|||||||
`;
|
`;
|
||||||
const level = getLevel(e.user).level;
|
const level = getLevel(e.user).level;
|
||||||
|
|
||||||
if(f0ck.length === 0) {
|
if (f0ck.length === 0) {
|
||||||
return await e.reply(`f0ck ${id}: f0ck not found`);
|
return await e.reply(`f0ck ${id}: f0ck not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(
|
if (
|
||||||
(f0ck[0].username !== (e.user.nick || e.user.username) ||
|
(f0ck[0].username !== (e.user.nick || e.user.username) ||
|
||||||
f0ck[0].userchannel !== e.channel ||
|
f0ck[0].userchannel !== e.channel ||
|
||||||
f0ck[0].usernetwork !== e.network) &&
|
f0ck[0].usernetwork !== e.network) &&
|
||||||
level < 100
|
level < 100
|
||||||
) {
|
) {
|
||||||
return await e.reply(`f0ck ${id}: insufficient permissions`);
|
return await e.reply(`f0ck ${id}: insufficient permissions`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(~~(new Date() / 1e3) >= (f0ck[0].stamp + 600) && level < 100) {
|
if (~~(new Date() / 1e3) >= (f0ck[0].stamp + 600) && level < 100) {
|
||||||
return await e.reply(`f0ck ${id}: too late lol`);
|
return await e.reply(`f0ck ${id}: too late lol`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await db`update "items" set active = 'false' where id = ${id}`;
|
await db`update "items" set active = 'false', is_deleted = true where id = ${id}`;
|
||||||
|
|
||||||
await fs.copyFile(`./public/b/${f0ck[0].dest}`, `./deleted/b/${f0ck[0].dest}`).catch(_=>{});
|
await fs.copyFile(`./public/b/${f0ck[0].dest}`, `./deleted/b/${f0ck[0].dest}`).catch(_ => { });
|
||||||
await fs.copyFile(`./public/t/${id}.webp`, `./deleted/t/${id}.webp`).catch(_=>{});
|
await fs.copyFile(`./public/t/${id}.webp`, `./deleted/t/${id}.webp`).catch(_ => { });
|
||||||
await fs.unlink(`./public/b/${f0ck[0].dest}`).catch(_=>{});
|
await fs.unlink(`./public/b/${f0ck[0].dest}`).catch(_ => { });
|
||||||
await fs.unlink(`./public/t/${id}.webp`).catch(_=>{});
|
await fs.unlink(`./public/t/${id}.webp`).catch(_ => { });
|
||||||
|
|
||||||
if(f0ck[0].mime.startsWith('audio')) {
|
if (f0ck[0].mime.startsWith('audio')) {
|
||||||
await fs.copyFile(`./public/ca/${id}.webp`, `./deleted/ca/${id}.webp`).catch(_=>{});
|
await fs.copyFile(`./public/ca/${id}.webp`, `./deleted/ca/${id}.webp`).catch(_ => { });
|
||||||
await fs.unlink(`./public/ca/${id}.webp`).catch(_=>{});
|
await fs.unlink(`./public/ca/${id}.webp`).catch(_ => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
await e.editMessageText(e.raw.chat.id, e.raw.message_id, e.message, {
|
await e.editMessageText(e.raw.chat.id, e.raw.message_id, e.message, {
|
||||||
@@ -259,9 +256,9 @@ export default async bot => {
|
|||||||
]]
|
]]
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "b_recover":
|
case "b_recover":
|
||||||
if(id <= 1)
|
if (id <= 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
e.user = {
|
e.user = {
|
||||||
@@ -270,7 +267,7 @@ export default async bot => {
|
|||||||
username: e.raw.reply_to_message.from.username,
|
username: e.raw.reply_to_message.from.username,
|
||||||
account: e.raw.reply_to_message.from.id.toString()
|
account: e.raw.reply_to_message.from.id.toString()
|
||||||
};
|
};
|
||||||
|
|
||||||
f0ck = await db`
|
f0ck = await db`
|
||||||
select dest, mime
|
select dest, mime
|
||||||
from "items"
|
from "items"
|
||||||
@@ -279,23 +276,23 @@ export default async bot => {
|
|||||||
active = 'false'
|
active = 'false'
|
||||||
limit 1
|
limit 1
|
||||||
`;
|
`;
|
||||||
|
|
||||||
if(f0ck.length === 0) {
|
if (f0ck.length === 0) {
|
||||||
return await e.reply(`f0ck ${id}: f0ck not found`);
|
return await e.reply(`f0ck ${id}: f0ck not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.copyFile(`./deleted/b/${f0ck[0].dest}`, `./public/b/${f0ck[0].dest}`).catch(_=>{});
|
await fs.copyFile(`./deleted/b/${f0ck[0].dest}`, `./public/b/${f0ck[0].dest}`).catch(_ => { });
|
||||||
await fs.copyFile(`./deleted/t/${id}.webp`, `./public/t/${id}.webp`).catch(_=>{});
|
await fs.copyFile(`./deleted/t/${id}.webp`, `./public/t/${id}.webp`).catch(_ => { });
|
||||||
await fs.unlink(`./deleted/b/${f0ck[0].dest}`).catch(_=>{});
|
await fs.unlink(`./deleted/b/${f0ck[0].dest}`).catch(_ => { });
|
||||||
await fs.unlink(`./deleted/t/${id}.webp`).catch(_=>{});
|
await fs.unlink(`./deleted/t/${id}.webp`).catch(_ => { });
|
||||||
|
|
||||||
if(f0ck[0].mime.startsWith('audio')) {
|
if (f0ck[0].mime.startsWith('audio')) {
|
||||||
await fs.copyFile(`./deleted/ca/${id}.webp`, `./public/ca/${id}.webp`).catch(_=>{});
|
await fs.copyFile(`./deleted/ca/${id}.webp`, `./public/ca/${id}.webp`).catch(_ => { });
|
||||||
await fs.unlink(`./deleted/ca/${id}.webp`).catch(_=>{});
|
await fs.unlink(`./deleted/ca/${id}.webp`).catch(_ => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
await db`update "items" set active = 'true' where id = ${id}`;
|
await db`update "items" set active = 'true' where id = ${id}`;
|
||||||
|
|
||||||
await e.editMessageText(e.raw.chat.id, e.raw.message_id, e.message, {
|
await e.editMessageText(e.raw.chat.id, e.raw.message_id, e.message, {
|
||||||
reply_markup: JSON.stringify({
|
reply_markup: JSON.stringify({
|
||||||
inline_keyboard: [[
|
inline_keyboard: [[
|
||||||
@@ -308,7 +305,7 @@ export default async bot => {
|
|||||||
]]
|
]]
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
await e.reply('lol');
|
await e.reply('lol');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ export default (router, tpl) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await db`update "items" set active = 'true' where id = ${id}`;
|
await db`update "items" set active = 'true', is_deleted = false where id = ${id}`;
|
||||||
|
|
||||||
// Check if files need moving (if they are in deleted/)
|
// Check if files need moving (if they are in deleted/)
|
||||||
try {
|
try {
|
||||||
@@ -169,51 +169,56 @@ export default (router, tpl) => {
|
|||||||
const total = (await db`select count(*) as c from "items" where active = 'false'`)[0].c;
|
const total = (await db`select count(*) as c from "items" where active = 'false'`)[0].c;
|
||||||
const pages = Math.ceil(total / limit);
|
const pages = Math.ceil(total / limit);
|
||||||
|
|
||||||
const _posts = await db`
|
// Fetch Pending (not deleted)
|
||||||
select id, mime, username, dest
|
const pending = await db`
|
||||||
from "items"
|
select i.id, i.mime, i.username, i.dest, array_agg(t.tag) as tags
|
||||||
|
from "items" i
|
||||||
|
left join "tags_assign" ta on ta.item_id = i.id
|
||||||
|
left join "tags" t on t.id = ta.tag_id
|
||||||
where
|
where
|
||||||
active = 'false'
|
i.active = 'false' and i.is_deleted = false
|
||||||
order by id desc
|
group by i.id
|
||||||
limit ${limit} offset ${offset}
|
order by i.id desc
|
||||||
`;
|
`;
|
||||||
|
|
||||||
if (_posts.length === 0 && page > 1) {
|
// Fetch Trash (deleted)
|
||||||
// if page empty, maybe redirect to last page or page 1?
|
const trash = await db`
|
||||||
// Just render empty for now
|
select i.id, i.mime, i.username, i.dest, array_agg(t.tag) as tags
|
||||||
}
|
from "items" i
|
||||||
|
left join "tags_assign" ta on ta.item_id = i.id
|
||||||
|
left join "tags" t on t.id = ta.tag_id
|
||||||
|
where
|
||||||
|
i.active = 'false' and i.is_deleted = true
|
||||||
|
group by i.id
|
||||||
|
order by i.id desc
|
||||||
|
`;
|
||||||
|
|
||||||
if (_posts.length === 0) {
|
// Helper to process thumbnails
|
||||||
return res.reply({
|
const processItems = async (items, isInTrash) => {
|
||||||
body: tpl.render('admin/approve', { posts: [], pages: 0, page: 1, tmp: null }, req)
|
return Promise.all(items.map(async p => {
|
||||||
});
|
let thumb = '';
|
||||||
}
|
const path = isInTrash ? 'deleted' : 'public';
|
||||||
|
|
||||||
const posts = await Promise.all(_posts.map(async p => {
|
|
||||||
// Try to get thumbnail from public or deleted
|
|
||||||
let thumb;
|
|
||||||
try {
|
|
||||||
// Try public first
|
|
||||||
thumb = (await fs.readFile(`./public/t/${p.id}.webp`)).toString('base64');
|
|
||||||
} catch {
|
|
||||||
try {
|
try {
|
||||||
thumb = (await fs.readFile(`./deleted/t/${p.id}.webp`)).toString('base64');
|
thumb = (await fs.readFile(`./${path}/t/${p.id}.webp`)).toString('base64');
|
||||||
} catch {
|
} catch { }
|
||||||
thumb = ""; // No thumbnail?
|
return {
|
||||||
}
|
...p,
|
||||||
}
|
thumbnail: thumb,
|
||||||
return {
|
tags: p.tags.filter(t => t !== null)
|
||||||
...p,
|
};
|
||||||
thumbnail: thumb
|
}));
|
||||||
};
|
};
|
||||||
}));
|
|
||||||
|
const pendingProcessed = await processItems(pending, false);
|
||||||
|
const trashProcessed = await processItems(trash, true);
|
||||||
|
|
||||||
res.reply({
|
res.reply({
|
||||||
body: tpl.render('admin/approve', {
|
body: tpl.render('admin/approve', {
|
||||||
posts,
|
pending: pendingProcessed,
|
||||||
|
trash: trashProcessed,
|
||||||
page,
|
page,
|
||||||
pages,
|
pages,
|
||||||
stats: { total: posts.length },
|
stats: { total: pending.length + trash.length },
|
||||||
tmp: null
|
tmp: null
|
||||||
}, req)
|
}, req)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ export default async bot => {
|
|||||||
f: async e => {
|
f: async e => {
|
||||||
let deleted = [];
|
let deleted = [];
|
||||||
|
|
||||||
for(let id of e.args) {
|
for (let id of e.args) {
|
||||||
id = +id;
|
id = +id;
|
||||||
if(id <= 1)
|
if (id <= 1)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const f0ck = await db`
|
const f0ck = await db`
|
||||||
select dest, mime, username, userchannel, usernetwork
|
select dest, mime, username, userchannel, usernetwork
|
||||||
from "items"
|
from "items"
|
||||||
@@ -26,36 +26,36 @@ export default async bot => {
|
|||||||
`;
|
`;
|
||||||
const level = getLevel(e.user).level;
|
const level = getLevel(e.user).level;
|
||||||
|
|
||||||
if(f0ck.length === 0) {
|
if (f0ck.length === 0) {
|
||||||
await e.reply(`f0ck ${id}: f0ck not found`);
|
await e.reply(`f0ck ${id}: f0ck not found`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(
|
if (
|
||||||
(f0ck[0].username !== (e.user.nick || e.user.username) ||
|
(f0ck[0].username !== (e.user.nick || e.user.username) ||
|
||||||
f0ck[0].userchannel !== e.channel ||
|
f0ck[0].userchannel !== e.channel ||
|
||||||
f0ck[0].usernetwork !== e.network) &&
|
f0ck[0].usernetwork !== e.network) &&
|
||||||
level < 100
|
level < 100
|
||||||
) {
|
) {
|
||||||
await e.reply(`f0ck ${id}: insufficient permissions`);
|
await e.reply(`f0ck ${id}: insufficient permissions`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(~~(new Date() / 1e3) >= (f0ck[0].stamp + 600) && level < 100) {
|
if (~~(new Date() / 1e3) >= (f0ck[0].stamp + 600) && level < 100) {
|
||||||
await e.reply(`f0ck ${id}: too late lol`);
|
await e.reply(`f0ck ${id}: too late lol`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await db`update "items" set active = 'false' where id = ${id}`;
|
await db`update "items" set active = 'false', is_deleted = true where id = ${id}`;
|
||||||
|
|
||||||
await fs.copyFile(`./public/b/${f0ck[0].dest}`, `./deleted/b/${f0ck[0].dest}`).catch(_=>{});
|
await fs.copyFile(`./public/b/${f0ck[0].dest}`, `./deleted/b/${f0ck[0].dest}`).catch(_ => { });
|
||||||
await fs.copyFile(`./public/t/${id}.webp`, `./deleted/t/${id}.webp`).catch(_=>{});
|
await fs.copyFile(`./public/t/${id}.webp`, `./deleted/t/${id}.webp`).catch(_ => { });
|
||||||
await fs.unlink(`./public/b/${f0ck[0].dest}`).catch(_=>{});
|
await fs.unlink(`./public/b/${f0ck[0].dest}`).catch(_ => { });
|
||||||
await fs.unlink(`./public/t/${id}.webp`).catch(_=>{});
|
await fs.unlink(`./public/t/${id}.webp`).catch(_ => { });
|
||||||
|
|
||||||
if(f0ck[0].mime.startsWith('audio')) {
|
if (f0ck[0].mime.startsWith('audio')) {
|
||||||
await fs.copyFile(`./public/ca/${id}.webp`, `./deleted/ca/${id}.webp`).catch(_=>{});
|
await fs.copyFile(`./public/ca/${id}.webp`, `./deleted/ca/${id}.webp`).catch(_ => { });
|
||||||
await fs.unlink(`./public/ca/${id}.webp`).catch(_=>{});
|
await fs.unlink(`./public/ca/${id}.webp`).catch(_ => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
deleted.push(id);
|
deleted.push(id);
|
||||||
@@ -71,11 +71,11 @@ export default async bot => {
|
|||||||
f: async e => {
|
f: async e => {
|
||||||
let recovered = [];
|
let recovered = [];
|
||||||
|
|
||||||
for(let id of e.args) {
|
for (let id of e.args) {
|
||||||
id = +id;
|
id = +id;
|
||||||
if(id <= 1)
|
if (id <= 1)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const f0ck = await db`
|
const f0ck = await db`
|
||||||
select dest, mime
|
select dest, mime
|
||||||
from "items"
|
from "items"
|
||||||
@@ -85,19 +85,19 @@ export default async bot => {
|
|||||||
limit 1
|
limit 1
|
||||||
`;
|
`;
|
||||||
|
|
||||||
if(f0ck.length === 0) {
|
if (f0ck.length === 0) {
|
||||||
await e.reply(`f0ck ${id}: f0ck not found`);
|
await e.reply(`f0ck ${id}: f0ck not found`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.copyFile(`./deleted/b/${f0ck[0].dest}`, `./public/b/${f0ck[0].dest}`).catch(_=>{});
|
await fs.copyFile(`./deleted/b/${f0ck[0].dest}`, `./public/b/${f0ck[0].dest}`).catch(_ => { });
|
||||||
await fs.copyFile(`./deleted/t/${id}.webp`, `./public/t/${id}.webp`).catch(_=>{});
|
await fs.copyFile(`./deleted/t/${id}.webp`, `./public/t/${id}.webp`).catch(_ => { });
|
||||||
await fs.unlink(`./deleted/b/${f0ck[0].dest}`).catch(_=>{});
|
await fs.unlink(`./deleted/b/${f0ck[0].dest}`).catch(_ => { });
|
||||||
await fs.unlink(`./deleted/t/${id}.webp`).catch(_=>{});
|
await fs.unlink(`./deleted/t/${id}.webp`).catch(_ => { });
|
||||||
|
|
||||||
if(f0ck[0].mime.startsWith('audio')) {
|
if (f0ck[0].mime.startsWith('audio')) {
|
||||||
await fs.copyFile(`./deleted/ca/${id}.webp`, `./public/ca/${id}.webp`).catch(_=>{});
|
await fs.copyFile(`./deleted/ca/${id}.webp`, `./public/ca/${id}.webp`).catch(_ => { });
|
||||||
await fs.unlink(`./deleted/ca/${id}.webp`).catch(_=>{});
|
await fs.unlink(`./deleted/ca/${id}.webp`).catch(_ => { });
|
||||||
}
|
}
|
||||||
|
|
||||||
await db`update "items" set active = 'true' where id = ${id}`;
|
await db`update "items" set active = 'true' where id = ${id}`;
|
||||||
|
|||||||
@@ -101,7 +101,12 @@ process.on('unhandledRejection', err => {
|
|||||||
}, 'last_used', 'last_action', 'browser')
|
}, 'last_used', 'last_action', 'browser')
|
||||||
}
|
}
|
||||||
where id = ${+user[0].sess_id}
|
where id = ${+user[0].sess_id}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
if (req.session.admin) {
|
||||||
|
const pending = await db`select count(*) as c from "items" where active = false and is_deleted = false`;
|
||||||
|
req.session.pending_count = pending[0].c;
|
||||||
|
}
|
||||||
|
|
||||||
req.session.theme = req.cookies.theme;
|
req.session.theme = req.cookies.theme;
|
||||||
req.session.fullscreen = req.cookies.fullscreen;
|
req.session.fullscreen = req.cookies.fullscreen;
|
||||||
|
|||||||
@@ -3,18 +3,22 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>APPROVAL QUEUE</h1>
|
<h1>APPROVAL QUEUE</h1>
|
||||||
<p>Items here are pending approval.</p>
|
<p>Items here are pending approval.</p>
|
||||||
<table class="table" style="width: 100%">
|
|
||||||
|
@if(pending.length > 0)
|
||||||
|
<h2>Pending Uploads</h2>
|
||||||
|
<table class="table" style="width: 100%; margin-bottom: 30px;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Preview</td>
|
<td>Preview</td>
|
||||||
<td>ID</td>
|
<td>ID</td>
|
||||||
<td>Uploader</td>
|
<td>Uploader</td>
|
||||||
<td>Type</td>
|
<td>Type</td>
|
||||||
|
<td>Tags</td>
|
||||||
<td>Action</td>
|
<td>Action</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@each(posts as post)
|
@each(pending as post)
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<video controls loop muted preload="metadata" style="max-height: 200px; max-width: 300px;">
|
<video controls loop muted preload="metadata" style="max-height: 200px; max-width: 300px;">
|
||||||
@@ -24,6 +28,11 @@
|
|||||||
<td>{{ post.id }}</td>
|
<td>{{ post.id }}</td>
|
||||||
<td>{{ post.username }}</td>
|
<td>{{ post.username }}</td>
|
||||||
<td>{{ post.mime }}</td>
|
<td>{{ post.mime }}</td>
|
||||||
|
<td>
|
||||||
|
@each(post.tags as tag)
|
||||||
|
<span class="badge badge-secondary" style="margin-right: 5px;">{{ tag }}</span>
|
||||||
|
@endeach
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="/admin/approve/?id={{ post.id }}" class="badge badge-success">Approve</a>
|
<a href="/admin/approve/?id={{ post.id }}" class="badge badge-success">Approve</a>
|
||||||
<a href="/admin/deny/?id={{ post.id }}" class="badge badge-danger btn-deny-async">Deny /
|
<a href="/admin/deny/?id={{ post.id }}" class="badge badge-danger btn-deny-async">Deny /
|
||||||
@@ -31,13 +40,60 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endeach
|
@endeach
|
||||||
@if(posts.length === 0)
|
|
||||||
<tr>
|
|
||||||
<td colspan="5">No pending items.</td>
|
|
||||||
</tr>
|
|
||||||
@endif
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(trash.length > 0)
|
||||||
|
<h2 style="color: #ff6b6b; margin-top: 40px;">Reference / Soft Deleted</h2>
|
||||||
|
<p class="text-muted">These items are in the deleted folder but not purged from DB. Approving them will restore
|
||||||
|
them.</p>
|
||||||
|
<table class="table" style="width: 100%; opacity: 0.8;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Preview</td>
|
||||||
|
<td>ID</td>
|
||||||
|
<td>Uploader</td>
|
||||||
|
<td>Type</td>
|
||||||
|
<td>Tags</td>
|
||||||
|
<td>Action</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@each(trash as post)
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
@if(post.thumbnail)
|
||||||
|
<img src="data:image/webp;base64,{{ post.thumbnail }}" style="max-height: 150px; opacity: 0.6;">
|
||||||
|
@else
|
||||||
|
<span style="color:red;">[File Missing]</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td>{{ post.id }}</td>
|
||||||
|
<td>{{ post.username }}</td>
|
||||||
|
<td>{{ post.mime }}</td>
|
||||||
|
<td>
|
||||||
|
@each(post.tags as tag)
|
||||||
|
<span class="badge badge-secondary" style="margin-right: 5px;">{{ tag }}</span>
|
||||||
|
@endeach
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="/admin/approve/?id={{ post.id }}" class="badge badge-warning">Restore</a>
|
||||||
|
<a href="/admin/deny/?id={{ post.id }}" class="badge badge-danger btn-deny-async">Purge</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if(pending.length === 0 && trash.length === 0)
|
||||||
|
<div style="text-align: center; padding: 50px;">
|
||||||
|
<h3>No pending items.</h3>
|
||||||
|
<p>Go touch grass?</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
@if(typeof pages !== 'undefined' && pages > 1)
|
@if(typeof pages !== 'undefined' && pages > 1)
|
||||||
<div class="pagination" style="display: flex; gap: 10px; align-items: center; justify-content: center;">
|
<div class="pagination" style="display: flex; gap: 10px; align-items: center; justify-content: center;">
|
||||||
@@ -54,7 +110,7 @@
|
|||||||
|
|
||||||
<div style="text-align: center; margin-bottom: 20px;">
|
<div style="text-align: center; margin-bottom: 20px;">
|
||||||
<button id="btn-deny-all" class="badge badge-danger" onclick="window.handleDenyAll(event)"
|
<button id="btn-deny-all" class="badge badge-danger" onclick="window.handleDenyAll(event)"
|
||||||
style="font-size: 1.2em; padding: 10px 20px; border: none; cursor: pointer;">Deny All</button>
|
style="font-size: 1.2em; padding: 10px 20px; border: none; cursor: pointer;">Deny All Visible</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href="/admin">Back to Admin</a>
|
<a href="/admin">Back to Admin</a>
|
||||||
|
|||||||
@@ -12,7 +12,12 @@
|
|||||||
<a href="/user/{{ session.user.toLowerCase() }}/favs">favs</a>
|
<a href="/user/{{ session.user.toLowerCase() }}/favs">favs</a>
|
||||||
<a href="/upload">upload</a>
|
<a href="/upload">upload</a>
|
||||||
@if(session.admin)
|
@if(session.admin)
|
||||||
<a href="/admin">admin</a>
|
<a href="/admin">Admin
|
||||||
|
@if(typeof session.pending_count !== 'undefined' && session.pending_count > 0)
|
||||||
|
<span class="notification-dot" title="{{ session.pending_count }} Pending"
|
||||||
|
onclick="event.preventDefault(); window.location.href='/admin/approve';"></span>
|
||||||
|
@endif
|
||||||
|
</a>
|
||||||
@endif
|
@endif
|
||||||
<a href="/settings">settings</a>
|
<a href="/settings">settings</a>
|
||||||
<a href="/about">about</a>
|
<a href="/about">about</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user