mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
Adjusted
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
// controllers/admin/assets.controller.js
|
||||
|
||||
const path = require("path");
|
||||
const crypto = require("crypto");
|
||||
const { Op } = require("sequelize");
|
||||
const Asset = require("../../models/assets/assets.mdl");
|
||||
|
||||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
const notDeleted = { deletedAt: null };
|
||||
|
||||
function resolveFileType(mimeType = "") {
|
||||
if (mimeType.startsWith("image/")) return "image";
|
||||
if (mimeType.startsWith("video/")) return "video";
|
||||
if (mimeType.startsWith("application/") || mimeType.startsWith("text/"))
|
||||
return "document";
|
||||
return "other";
|
||||
}
|
||||
|
||||
function resolveExtension(originalName = "") {
|
||||
return path.extname(originalName).replace(".", "").toLowerCase() || null;
|
||||
}
|
||||
|
||||
function resolveChecksum(buffer) {
|
||||
return crypto.createHash("sha256").update(buffer).digest("hex");
|
||||
}
|
||||
|
||||
function resolveResolution(width, height) {
|
||||
if (!width || !height) return null;
|
||||
const h = Math.min(width, height);
|
||||
if (h >= 2160) return "4K";
|
||||
if (h >= 1440) return "1440p";
|
||||
if (h >= 1080) return "1080p";
|
||||
if (h >= 720) return "720p";
|
||||
if (h >= 480) return "480p";
|
||||
if (h >= 360) return "360p";
|
||||
if (h >= 240) return "240p";
|
||||
return `${width}x${height}`;
|
||||
}
|
||||
|
||||
// ─── GET ALL ──────────────────────────────────────────────────────────────────
|
||||
|
||||
exports.getAssets = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
limit = 20,
|
||||
file_type,
|
||||
owner_type,
|
||||
owner_id,
|
||||
uploadedBy,
|
||||
is_public,
|
||||
resolution,
|
||||
search,
|
||||
sort_by = "createdAt",
|
||||
sort_dir = "DESC",
|
||||
} = req.query;
|
||||
|
||||
const where = { ...notDeleted };
|
||||
|
||||
if (file_type) where.file_type = file_type;
|
||||
if (owner_type) where.owner_type = owner_type;
|
||||
if (owner_id) where.owner_id = owner_id;
|
||||
if (uploadedBy) where.uploadedBy = uploadedBy;
|
||||
if (resolution) where.resolution = resolution;
|
||||
if (is_public !== undefined) where.is_public = is_public === "true";
|
||||
|
||||
if (search) {
|
||||
where[Op.or] = [
|
||||
{ display_name: { [Op.iLike]: `%${search}%` } },
|
||||
{ original_name: { [Op.iLike]: `%${search}%` } },
|
||||
{ description: { [Op.iLike]: `%${search}%` } },
|
||||
];
|
||||
}
|
||||
|
||||
const offset = (parseInt(page) - 1) * parseInt(limit);
|
||||
|
||||
const { count, rows } = await Asset.findAndCountAll({
|
||||
where,
|
||||
order: [[sort_by, sort_dir.toUpperCase()]],
|
||||
limit: parseInt(limit),
|
||||
offset,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
data: rows,
|
||||
pagination: {
|
||||
total: count,
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
totalPages: Math.ceil(count / parseInt(limit)),
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("[ASSET][GET ALL]", err);
|
||||
return res.status(500).json({ message: "Internal server error." });
|
||||
}
|
||||
};
|
||||
|
||||
// ─── GET ONE ──────────────────────────────────────────────────────────────────
|
||||
|
||||
exports.getAsset = async (req, res) => {
|
||||
try {
|
||||
const { assetId } = req.params;
|
||||
if (!assetId || assetId === "undefined") {
|
||||
return res.status(400).json({ message: "Invalid asset ID." });
|
||||
}
|
||||
|
||||
const asset = await Asset.findOne({
|
||||
where: { asset_id: assetId, ...notDeleted },
|
||||
});
|
||||
|
||||
if (!asset) return res.status(404).json({ message: "Asset not found." });
|
||||
|
||||
return res.status(200).json({ data: asset });
|
||||
} catch (err) {
|
||||
console.error("[ASSET][GET ONE]", err);
|
||||
return res.status(500).json({ message: "Internal server error." });
|
||||
}
|
||||
};
|
||||
|
||||
// ─── UPLOAD ───────────────────────────────────────────────────────────────────
|
||||
|
||||
exports.uploadAsset = async (req, res) => {
|
||||
try {
|
||||
const file = req.file;
|
||||
if (!file) return res.status(400).json({ message: "No file uploaded." });
|
||||
|
||||
const {
|
||||
display_name,
|
||||
description,
|
||||
owner_type,
|
||||
owner_id,
|
||||
is_public = false,
|
||||
access_level = "private",
|
||||
storage_provider = "local",
|
||||
storage_bucket,
|
||||
storage_key,
|
||||
uploadedBy,
|
||||
file_url: bodyFileUrl,
|
||||
|
||||
// video metadata — from ffprobe pipeline or client
|
||||
width,
|
||||
height,
|
||||
duration,
|
||||
frame_rate,
|
||||
bitrate,
|
||||
video_codec,
|
||||
audio_codec,
|
||||
thumbnail_url,
|
||||
} = req.body;
|
||||
|
||||
if (!uploadedBy) {
|
||||
return res.status(400).json({ message: "uploadedBy is required." });
|
||||
}
|
||||
|
||||
const mime_type = file.mimetype;
|
||||
const file_type = resolveFileType(mime_type);
|
||||
const extension = resolveExtension(file.originalname);
|
||||
const checksum = file.buffer ? resolveChecksum(file.buffer) : null;
|
||||
|
||||
const parsedWidth = width ? parseInt(width) : null;
|
||||
const parsedHeight = height ? parseInt(height) : null;
|
||||
|
||||
const resolution = file_type === "video"
|
||||
? resolveResolution(parsedWidth, parsedHeight)
|
||||
: null;
|
||||
|
||||
const file_url = storage_provider === "local"
|
||||
? `/uploads/${file.filename}`
|
||||
: bodyFileUrl;
|
||||
|
||||
if (!file_url) {
|
||||
return res.status(400).json({ message: "file_url is required for non-local storage." });
|
||||
}
|
||||
|
||||
const asset = await Asset.create({
|
||||
original_name: file.originalname,
|
||||
display_name: display_name || file.originalname,
|
||||
file_url,
|
||||
file_size: file.size,
|
||||
mime_type,
|
||||
extension,
|
||||
checksum,
|
||||
file_type,
|
||||
width: parsedWidth,
|
||||
height: parsedHeight,
|
||||
duration: duration ? parseFloat(duration) : null,
|
||||
frame_rate: frame_rate ? parseFloat(frame_rate) : null,
|
||||
bitrate: bitrate ? parseInt(bitrate) : null,
|
||||
video_codec: video_codec || null,
|
||||
audio_codec: audio_codec || null,
|
||||
thumbnail_url: thumbnail_url || null,
|
||||
resolution,
|
||||
description,
|
||||
storage_provider,
|
||||
storage_bucket: storage_bucket || null,
|
||||
storage_key: storage_key || file.filename,
|
||||
is_public,
|
||||
access_level,
|
||||
owner_type: owner_type || null,
|
||||
owner_id: owner_id || null,
|
||||
uploadedBy,
|
||||
});
|
||||
|
||||
return res.status(201).json({ data: asset });
|
||||
} catch (err) {
|
||||
console.error("[ASSET][UPLOAD]", err);
|
||||
return res.status(500).json({ message: "Internal server error." });
|
||||
}
|
||||
};
|
||||
|
||||
// ─── UPDATE THUMBNAIL ─────────────────────────────────────────────────────────
|
||||
|
||||
exports.updateThumbnail = async (req, res) => {
|
||||
try {
|
||||
const { assetId } = req.params;
|
||||
const { thumbnail_url } = req.body;
|
||||
|
||||
if (!thumbnail_url) {
|
||||
return res.status(400).json({ message: "thumbnail_url is required." });
|
||||
}
|
||||
|
||||
const asset = await Asset.findOne({
|
||||
where: { asset_id: assetId, ...notDeleted },
|
||||
});
|
||||
if (!asset) return res.status(404).json({ message: "Asset not found." });
|
||||
|
||||
asset.thumbnail_url = thumbnail_url;
|
||||
await asset.save();
|
||||
|
||||
return res.status(200).json({ data: asset });
|
||||
} catch (err) {
|
||||
console.error("[ASSET][UPDATE THUMBNAIL]", err);
|
||||
return res.status(500).json({ message: "Internal server error." });
|
||||
}
|
||||
};
|
||||
|
||||
// ─── UPDATE METADATA ──────────────────────────────────────────────────────────
|
||||
|
||||
exports.updateAsset = async (req, res) => {
|
||||
try {
|
||||
const { assetId } = req.params;
|
||||
if (!assetId || assetId === "undefined") {
|
||||
return res.status(400).json({ message: "Invalid asset ID." });
|
||||
}
|
||||
|
||||
const asset = await Asset.findOne({
|
||||
where: { asset_id: assetId, ...notDeleted },
|
||||
});
|
||||
if (!asset) return res.status(404).json({ message: "Asset not found." });
|
||||
|
||||
const allowed = [
|
||||
"display_name", "description",
|
||||
"owner_type", "owner_id",
|
||||
"is_public", "access_level",
|
||||
"thumbnail_url",
|
||||
"width", "height", "duration",
|
||||
"frame_rate", "bitrate",
|
||||
"video_codec", "audio_codec",
|
||||
];
|
||||
|
||||
allowed.forEach((field) => {
|
||||
if (req.body[field] !== undefined) asset[field] = req.body[field];
|
||||
});
|
||||
|
||||
// Re-derive resolution if dimensions were updated
|
||||
if (req.body.width || req.body.height) {
|
||||
asset.resolution = resolveResolution(asset.width, asset.height);
|
||||
}
|
||||
|
||||
await asset.save();
|
||||
|
||||
return res.status(200).json({ data: asset });
|
||||
} catch (err) {
|
||||
console.error("[ASSET][UPDATE]", err);
|
||||
return res.status(500).json({ message: "Internal server error." });
|
||||
}
|
||||
};
|
||||
|
||||
// ─── SOFT DELETE (single) ─────────────────────────────────────────────────────
|
||||
|
||||
exports.deleteAsset = async (req, res) => {
|
||||
try {
|
||||
const { assetId } = req.params;
|
||||
if (!assetId || assetId === "undefined") {
|
||||
return res.status(400).json({ message: "Invalid asset ID." });
|
||||
}
|
||||
|
||||
const asset = await Asset.findOne({
|
||||
where: { asset_id: assetId, ...notDeleted },
|
||||
});
|
||||
if (!asset) return res.status(404).json({ message: "Asset not found." });
|
||||
|
||||
asset.deletedAt = new Date();
|
||||
asset.deletedBy = req.body.deletedBy ?? null;
|
||||
await asset.save();
|
||||
|
||||
return res.status(200).json({ message: "Asset deleted." });
|
||||
} catch (err) {
|
||||
console.error("[ASSET][DELETE]", err);
|
||||
return res.status(500).json({ message: "Internal server error." });
|
||||
}
|
||||
};
|
||||
|
||||
// ─── SOFT DELETE (bulk) ───────────────────────────────────────────────────────
|
||||
|
||||
exports.deleteAssets = async (req, res) => {
|
||||
try {
|
||||
const { ids, deletedBy } = req.body;
|
||||
|
||||
if (!Array.isArray(ids) || !ids.length) {
|
||||
return res.status(400).json({ message: "ids must be a non-empty array." });
|
||||
}
|
||||
|
||||
const [count] = await Asset.update(
|
||||
{ deletedAt: new Date(), deletedBy: deletedBy ?? null },
|
||||
{ where: { asset_id: { [Op.in]: ids }, ...notDeleted } },
|
||||
);
|
||||
|
||||
return res.status(200).json({ message: `${count} asset(s) deleted.` });
|
||||
} catch (err) {
|
||||
console.error("[ASSET][BULK DELETE]", err);
|
||||
return res.status(500).json({ message: "Internal server error." });
|
||||
}
|
||||
};
|
||||
|
||||
// ─── RESTORE (single) ─────────────────────────────────────────────────────────
|
||||
|
||||
exports.restoreAsset = async (req, res) => {
|
||||
try {
|
||||
const { assetId } = req.params;
|
||||
|
||||
const asset = await Asset.findOne({
|
||||
where: { asset_id: assetId, deletedAt: { [Op.not]: null } },
|
||||
});
|
||||
if (!asset) {
|
||||
return res.status(404).json({ message: "Asset not found or not deleted." });
|
||||
}
|
||||
|
||||
asset.deletedAt = null;
|
||||
asset.deletedBy = null;
|
||||
await asset.save();
|
||||
|
||||
return res.status(200).json({ data: asset, message: "Asset restored." });
|
||||
} catch (err) {
|
||||
console.error("[ASSET][RESTORE]", err);
|
||||
return res.status(500).json({ message: "Internal server error." });
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user