mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -122,7 +122,7 @@ async function applyAdvertisementFields(advertisement, body) {
|
||||
} else {
|
||||
const asset = await mdl_Assets.findOne({ where: { asset_id: body.image_asset_id, deletedAt: null } });
|
||||
if (!asset) {
|
||||
const err = new Error("Selected image asset was not found.");
|
||||
const err = new Error("Selected image file was not found.");
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
|
||||
@@ -245,10 +245,10 @@ exports.getAssets = async (req, res) => {
|
||||
}
|
||||
|
||||
const data = await attachStreamTokens(result.data, req);
|
||||
return R.success(res, "Assets retrieved.", { ...result, data });
|
||||
return R.success(res, "Files retrieved.", { ...result, data });
|
||||
} catch (err) {
|
||||
console.error("[ASSET][GET ALL]", err);
|
||||
return R.error(res, "Could not retrieve assets.", 500);
|
||||
return R.error(res, "Could not retrieve files.", 500);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -257,7 +257,7 @@ exports.getAssets = async (req, res) => {
|
||||
exports.getAsset = async (req, res) => {
|
||||
try {
|
||||
const { assetId } = req.params;
|
||||
if (!assetId || assetId === "undefined") return R.error(res, "Invalid asset ID.", 400);
|
||||
if (!assetId || assetId === "undefined") return R.error(res, "Invalid file ID.", 400);
|
||||
|
||||
const asset = await Asset.findOne({
|
||||
where: { asset_id: assetId, ...notDeleted },
|
||||
@@ -270,7 +270,7 @@ exports.getAsset = async (req, res) => {
|
||||
],
|
||||
});
|
||||
|
||||
if (!asset) return R.error(res, "Asset not found.", 404);
|
||||
if (!asset) return R.error(res, "File not found.", 404);
|
||||
|
||||
const json = asset.toJSON();
|
||||
|
||||
@@ -298,7 +298,7 @@ exports.getAsset = async (req, res) => {
|
||||
delete json.storage_key;
|
||||
|
||||
redactS3Url(json);
|
||||
return R.success(res, "Asset retrieved.", { data: json });
|
||||
return R.success(res, "File retrieved.", { data: json });
|
||||
} catch (err) {
|
||||
console.error("[ASSET][GET ONE]", err);
|
||||
return R.error(res, "Internal server error.", 500);
|
||||
@@ -578,7 +578,7 @@ exports.uploadAsset = async (req, res) => {
|
||||
const { storage_key, thumbnail_storage_key, original_name } = req.body;
|
||||
const asset = await finalizeAssetFromStorage({ storage_key, thumbnail_storage_key, original_name, body: req.body, user: req.user });
|
||||
invalidateListCache();
|
||||
return R.success(res, "Asset uploaded.", { data: asset }, 201);
|
||||
return R.success(res, "File uploaded.", { data: asset }, 201);
|
||||
|
||||
} catch (err) {
|
||||
console.error("[ASSET][UPLOAD]", err);
|
||||
@@ -594,10 +594,10 @@ exports.updateAsset = async (req, res) => {
|
||||
|
||||
try {
|
||||
const { assetId } = req.params;
|
||||
if (!assetId || assetId === "undefined") return R.error(res, "Invalid asset ID.", 400);
|
||||
if (!assetId || assetId === "undefined") return R.error(res, "Invalid file ID.", 400);
|
||||
|
||||
const asset = await Asset.findOne({ where: { asset_id: assetId, ...notDeleted } });
|
||||
if (!asset) return R.error(res, "Asset not found.", 404);
|
||||
if (!asset) return R.error(res, "File not found.", 404);
|
||||
|
||||
// Browser already PUT the replacement file straight to storage via a
|
||||
// presigned URL (see presignAssetUpload) — this is plain JSON, no
|
||||
@@ -607,7 +607,7 @@ exports.updateAsset = async (req, res) => {
|
||||
const isDocument = asset.file_type === "document";
|
||||
|
||||
if (isDocument && storage_key) return R.error(res, "Document files cannot be replaced.", 400);
|
||||
if (isVideo && storage_key && !req.body.is_thumbnail) return R.error(res, "Video files cannot be replaced. Upload a new asset instead.", 400);
|
||||
if (isVideo && storage_key && !req.body.is_thumbnail) return R.error(res, "Video files cannot be replaced. Upload a new file instead.", 400);
|
||||
|
||||
const storageProvider = asset.storage_provider;
|
||||
const usesProvider = ["chibisafe", "s3"].includes(storageProvider);
|
||||
@@ -641,7 +641,7 @@ exports.updateAsset = async (req, res) => {
|
||||
|
||||
invalidateListCache();
|
||||
logActivity(req.user?.user_id, 'update_asset', { entityType: 'asset', entityId: Number(assetId) });
|
||||
return R.success(res, "Asset updated.", { data: asset });
|
||||
return R.success(res, "File updated.", { data: asset });
|
||||
|
||||
} catch (err) {
|
||||
if (newUpload) await rollbackUploads([newUpload]);
|
||||
@@ -656,16 +656,16 @@ exports.updateAsset = async (req, res) => {
|
||||
exports.archiveAsset = async (req, res) => {
|
||||
try {
|
||||
const { assetId } = req.params;
|
||||
if (!assetId || assetId === "undefined") return R.error(res, "Invalid asset ID.", 400);
|
||||
if (!assetId || assetId === "undefined") return R.error(res, "Invalid file ID.", 400);
|
||||
|
||||
const asset = await Asset.findOne({ where: { asset_id: assetId, ...notDeleted } });
|
||||
if (!asset) return R.error(res, "Asset not found.", 404);
|
||||
if (!asset) return R.error(res, "File not found.", 404);
|
||||
|
||||
await asset.update({ deletedBy: req.body.deletedBy ?? null });
|
||||
await asset.destroy();
|
||||
invalidateListCache();
|
||||
logActivity(req.user?.user_id, 'archive_asset', { entityType: 'asset', entityId: Number(assetId) });
|
||||
return R.success(res, "Asset archived.");
|
||||
return R.success(res, "File archived.");
|
||||
} catch (err) {
|
||||
console.error("[ASSET][ARCHIVE]", err);
|
||||
return R.error(res, "Internal server error.", 500);
|
||||
@@ -680,7 +680,7 @@ exports.archiveAssets = async (req, res) => {
|
||||
if (!Array.isArray(ids) || !ids.length) return R.error(res, "ids must be a non-empty array.", 400);
|
||||
|
||||
const assets = await Asset.findAll({ where: { asset_id: { [Op.in]: ids }, ...notDeleted } });
|
||||
if (!assets.length) return R.error(res, "No assets found.", 404);
|
||||
if (!assets.length) return R.error(res, "No files found.", 404);
|
||||
|
||||
const activeIds = assets.map((a) => a.asset_id);
|
||||
|
||||
@@ -689,7 +689,7 @@ exports.archiveAssets = async (req, res) => {
|
||||
|
||||
invalidateListCache();
|
||||
logActivity(req.user?.user_id, 'bulk_archive_assets', { entityType: 'asset', details: { ids: activeIds, count: activeIds.length } });
|
||||
return R.success(res, `${activeIds.length} asset(s) archived.`, {
|
||||
return R.success(res, `${activeIds.length} file(s) archived.`, {
|
||||
archived_ids: activeIds,
|
||||
skipped_ids: ids.filter((id) => !activeIds.includes(id)),
|
||||
});
|
||||
@@ -706,14 +706,14 @@ exports.restoreAsset = async (req, res) => {
|
||||
const { assetId } = req.params;
|
||||
|
||||
const asset = await Asset.findOne({ where: { asset_id: assetId }, paranoid: false });
|
||||
if (!asset) return R.error(res, "Asset not found.", 404);
|
||||
if (!asset.deletedAt) return R.error(res, "Asset is not archived.", 400);
|
||||
if (!asset) return R.error(res, "File not found.", 404);
|
||||
if (!asset.deletedAt) return R.error(res, "File is not archived.", 400);
|
||||
|
||||
await asset.restore();
|
||||
await asset.update({ deletedBy: null });
|
||||
invalidateListCache();
|
||||
logActivity(req.user?.user_id, 'restore_asset', { entityType: 'asset', entityId: Number(assetId) });
|
||||
return R.success(res, "Asset restored.", { data: asset });
|
||||
return R.success(res, "File restored.", { data: asset });
|
||||
} catch (err) {
|
||||
console.error("[ASSET][RESTORE]", err);
|
||||
return R.error(res, "Internal server error.", 500);
|
||||
@@ -728,10 +728,10 @@ exports.restoreAssets = async (req, res) => {
|
||||
if (!Array.isArray(ids) || !ids.length) return R.error(res, "ids must be a non-empty array.", 400);
|
||||
|
||||
const assets = await Asset.findAll({ where: { asset_id: { [Op.in]: ids } }, paranoid: false });
|
||||
if (!assets.length) return R.error(res, "No assets found.", 404);
|
||||
if (!assets.length) return R.error(res, "No files found.", 404);
|
||||
|
||||
const archivedAssets = assets.filter((a) => a.deletedAt);
|
||||
if (!archivedAssets.length) return R.error(res, "All selected assets are already active.", 400);
|
||||
if (!archivedAssets.length) return R.error(res, "All selected files are already active.", 400);
|
||||
|
||||
const archivedIds = archivedAssets.map((a) => a.asset_id);
|
||||
|
||||
@@ -740,7 +740,7 @@ exports.restoreAssets = async (req, res) => {
|
||||
|
||||
invalidateListCache();
|
||||
logActivity(req.user?.user_id, 'bulk_restore_assets', { entityType: 'asset', details: { ids: archivedIds, count: archivedIds.length } });
|
||||
return R.success(res, `${archivedIds.length} asset(s) restored.`, {
|
||||
return R.success(res, `${archivedIds.length} file(s) restored.`, {
|
||||
restored_ids: archivedIds,
|
||||
skipped_ids: ids.filter((id) => !archivedIds.includes(id)),
|
||||
});
|
||||
@@ -757,8 +757,8 @@ exports.permanentlyDeleteAsset = async (req, res) => {
|
||||
const { assetId } = req.params;
|
||||
|
||||
const asset = await Asset.findOne({ where: { asset_id: assetId }, paranoid: false });
|
||||
if (!asset) return R.error(res, "Asset not found.", 404);
|
||||
if (!asset.deletedAt) return R.error(res, "Asset must be archived before it can be permanently deleted.", 400);
|
||||
if (!asset) return R.error(res, "File not found.", 404);
|
||||
if (!asset.deletedAt) return R.error(res, "File must be archived before it can be permanently deleted.", 400);
|
||||
|
||||
const { storage_provider, storage_key, thumbnail_storage_key } = asset;
|
||||
|
||||
@@ -778,7 +778,7 @@ exports.permanentlyDeleteAsset = async (req, res) => {
|
||||
|
||||
invalidateListCache();
|
||||
logActivity(req.user?.user_id, 'permanently_delete_asset', { entityType: 'asset', entityId: Number(assetId) });
|
||||
return R.success(res, "Asset permanently deleted.");
|
||||
return R.success(res, "File permanently deleted.");
|
||||
} catch (err) {
|
||||
console.error("[ASSET][PERMANENT DELETE]", err);
|
||||
return R.error(res, "Internal server error.", 500);
|
||||
@@ -793,10 +793,10 @@ exports.permanentlyDeleteAssets = async (req, res) => {
|
||||
if (!Array.isArray(ids) || !ids.length) return R.error(res, "ids must be a non-empty array.", 400);
|
||||
|
||||
const assets = await Asset.findAll({ where: { asset_id: { [Op.in]: ids } }, paranoid: false });
|
||||
if (!assets.length) return R.error(res, "No assets found.", 404);
|
||||
if (!assets.length) return R.error(res, "No files found.", 404);
|
||||
|
||||
const archivedAssets = assets.filter((a) => a.deletedAt);
|
||||
if (!archivedAssets.length) return R.error(res, "All selected assets must be archived before they can be permanently deleted.", 400);
|
||||
if (!archivedAssets.length) return R.error(res, "All selected files must be archived before they can be permanently deleted.", 400);
|
||||
|
||||
const archivedIds = archivedAssets.map((a) => a.asset_id);
|
||||
|
||||
@@ -817,7 +817,7 @@ exports.permanentlyDeleteAssets = async (req, res) => {
|
||||
|
||||
invalidateListCache();
|
||||
logActivity(req.user?.user_id, 'bulk_permanently_delete_assets', { entityType: 'asset', details: { ids: archivedIds, count: archivedIds.length } });
|
||||
return R.success(res, `${archivedIds.length} asset(s) permanently deleted.`, {
|
||||
return R.success(res, `${archivedIds.length} file(s) permanently deleted.`, {
|
||||
deleted_ids: archivedIds,
|
||||
skipped_ids: ids.filter((id) => !archivedIds.includes(id)),
|
||||
});
|
||||
@@ -840,10 +840,10 @@ exports.getArchivedAssets = async (req, res) => {
|
||||
findOptions: { paranoid: false, where: { deletedAt: { [Op.ne]: null } } },
|
||||
});
|
||||
result.data = result.data.map(redactS3Url);
|
||||
return R.success(res, "Archived assets retrieved.", result);
|
||||
return R.success(res, "Archived files retrieved.", result);
|
||||
} catch (err) {
|
||||
console.error("[ASSET][GET ARCHIVED]", err);
|
||||
return R.error(res, "Could not retrieve archived assets.", 500);
|
||||
return R.error(res, "Could not retrieve archived files.", 500);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -30,14 +30,14 @@ exports.issueToken = async (req, res) => {
|
||||
attributes: ["asset_id", "file_type", "storage_provider", "storage_key", "mime_type", "thumbnail_storage_key"],
|
||||
});
|
||||
|
||||
if (!asset) return R.error(res, "Asset not found.", 404);
|
||||
if (!asset) return R.error(res, "File not found.", 404);
|
||||
|
||||
if (!mediaToken.SUPPORTED_TYPES.includes(asset.file_type)) {
|
||||
return R.error(res, `Asset type "${asset.file_type}" is not supported.`, 400);
|
||||
return R.error(res, `File type "${asset.file_type}" is not supported.`, 400);
|
||||
}
|
||||
|
||||
if (asset.storage_provider !== "s3") {
|
||||
return R.error(res, "Token flow is for S3 assets only. Use the raw file_url for other providers.", 400);
|
||||
return R.error(res, "Token flow is for S3 files only. Use the raw file_url for other providers.", 400);
|
||||
}
|
||||
|
||||
const ip = mediaToken.resolveIp(req);
|
||||
|
||||
@@ -73,7 +73,7 @@ async function validateImageAssetId(image_asset_id) {
|
||||
if (!image_asset_id) return null;
|
||||
const asset = await mdl_Assets.findOne({ where: { asset_id: image_asset_id, deletedAt: null } });
|
||||
if (!asset) {
|
||||
const err = new Error("Selected image asset was not found.");
|
||||
const err = new Error("Selected image file was not found.");
|
||||
err.status = 400;
|
||||
throw err;
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@ exports.getCategories = async (req, res) => {
|
||||
include: withBadge,
|
||||
order: [['rank', 'ASC']],
|
||||
});
|
||||
return R.success(res, 'Tier categories retrieved.', categories);
|
||||
return R.success(res, 'Subscription categories retrieved.', categories);
|
||||
} catch (err) {
|
||||
console.error('[ADMIN][GET TIER CATEGORIES]', err);
|
||||
return R.error(res, 'Could not retrieve tier categories.', 500);
|
||||
return R.error(res, 'Could not retrieve subscription categories.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -32,11 +32,11 @@ exports.getCategories = async (req, res) => {
|
||||
exports.getCategory = async (req, res) => {
|
||||
try {
|
||||
const cat = await mdl_TierCategories.findByPk(req.params.id, { include: withBadge });
|
||||
if (!cat) return R.error(res, 'Tier category not found.', 404);
|
||||
return R.success(res, 'Tier category retrieved.', cat);
|
||||
if (!cat) return R.error(res, 'Subscription category not found.', 404);
|
||||
return R.success(res, 'Subscription category retrieved.', cat);
|
||||
} catch (err) {
|
||||
console.error('[ADMIN][GET TIER CATEGORY]', err);
|
||||
return R.error(res, 'Could not retrieve tier category.', 500);
|
||||
return R.error(res, 'Could not retrieve subscription category.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,10 +48,10 @@ exports.createCategory = async (req, res) => {
|
||||
if (!slug || !name) return R.error(res, 'slug and name are required.', 400);
|
||||
|
||||
const parsedRank = Number(rank ?? 1);
|
||||
if (parsedRank <= 0) return R.error(res, 'Non-default tier categories must have rank greater than 0.', 400);
|
||||
if (parsedRank <= 0) return R.error(res, 'Non-default subscription categories must have rank greater than 0.', 400);
|
||||
|
||||
const exists = await mdl_TierCategories.findOne({ where: { slug } });
|
||||
if (exists) return R.error(res, `A tier category with slug "${slug}" already exists.`, 409);
|
||||
if (exists) return R.error(res, `A subscription category with slug "${slug}" already exists.`, 409);
|
||||
|
||||
const cat = await mdl_TierCategories.create({
|
||||
slug, name,
|
||||
@@ -69,10 +69,10 @@ exports.createCategory = async (req, res) => {
|
||||
logActivity(req.user?.user_id, 'create_tier_category', { entityType: 'tier_category', details: { slug, name } });
|
||||
|
||||
const result = await mdl_TierCategories.findByPk(cat.tier_category_id, { include: withBadge });
|
||||
return R.success(res, 'Tier category created.', result, 201);
|
||||
return R.success(res, 'Subscription category created.', result, 201);
|
||||
} catch (err) {
|
||||
console.error('[ADMIN][CREATE TIER CATEGORY]', err);
|
||||
return R.error(res, 'Could not create tier category.', 500);
|
||||
return R.error(res, 'Could not create subscription category.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -81,13 +81,13 @@ exports.createCategory = async (req, res) => {
|
||||
exports.updateCategory = async (req, res) => {
|
||||
try {
|
||||
const cat = await mdl_TierCategories.findByPk(req.params.id);
|
||||
if (!cat) return R.error(res, 'Tier category not found.', 404);
|
||||
if (!cat) return R.error(res, 'Subscription category not found.', 404);
|
||||
|
||||
const { name, description, rank, color, badge_asset_id, badge_icon, badge_label, is_active, is_special } = req.body;
|
||||
|
||||
if (!cat.is_default && rank !== undefined) {
|
||||
const parsedRank = Number(rank);
|
||||
if (parsedRank <= 0) return R.error(res, 'Non-default tier categories must have rank greater than 0.', 400);
|
||||
if (parsedRank <= 0) return R.error(res, 'Non-default subscription categories must have rank greater than 0.', 400);
|
||||
}
|
||||
|
||||
await cat.update({
|
||||
@@ -106,10 +106,10 @@ exports.updateCategory = async (req, res) => {
|
||||
logActivity(req.user?.user_id, 'update_tier_category', { entityType: 'tier_category', details: { id: cat.tier_category_id, slug: cat.slug } });
|
||||
|
||||
const result = await mdl_TierCategories.findByPk(cat.tier_category_id, { include: withBadge });
|
||||
return R.success(res, 'Tier category updated.', result);
|
||||
return R.success(res, 'Subscription category updated.', result);
|
||||
} catch (err) {
|
||||
console.error('[ADMIN][UPDATE TIER CATEGORY]', err);
|
||||
return R.error(res, 'Could not update tier category.', 500);
|
||||
return R.error(res, 'Could not update subscription category.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -118,8 +118,8 @@ exports.updateCategory = async (req, res) => {
|
||||
exports.deleteCategory = async (req, res) => {
|
||||
try {
|
||||
const cat = await mdl_TierCategories.findByPk(req.params.id);
|
||||
if (!cat) return R.error(res, 'Tier category not found.', 404);
|
||||
if (cat.is_default) return R.error(res, 'The default (Free) tier category cannot be deleted.', 400);
|
||||
if (!cat) return R.error(res, 'Subscription category not found.', 404);
|
||||
if (cat.is_default) return R.error(res, 'The default (Free) subscription category cannot be deleted.', 400);
|
||||
|
||||
// Block deletion if active plans still reference this category
|
||||
const activePlans = await mdl_TierPlans.count({
|
||||
@@ -130,9 +130,9 @@ exports.deleteCategory = async (req, res) => {
|
||||
|
||||
await cat.destroy();
|
||||
logActivity(req.user?.user_id, 'delete_tier_category', { entityType: 'tier_category', details: { slug: cat.slug } });
|
||||
return R.success(res, 'Tier category deleted.');
|
||||
return R.success(res, 'Subscription category deleted.');
|
||||
} catch (err) {
|
||||
console.error('[ADMIN][DELETE TIER CATEGORY]', err);
|
||||
return R.error(res, 'Could not delete tier category.', 500);
|
||||
return R.error(res, 'Could not delete subscription category.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -117,10 +117,10 @@ exports.createPlan = async (req, res) => {
|
||||
|
||||
const category = await mdl_TierCategories.findByPk(tier_category_id);
|
||||
if (!category || !category.is_active)
|
||||
return R.error(res, 'Tier category not found or inactive.', 404);
|
||||
return R.error(res, 'Subscription category not found or inactive.', 404);
|
||||
|
||||
if (category.is_default)
|
||||
return R.error(res, 'Plans cannot be created under the default (Free) tier. Free access is automatic.', 400);
|
||||
return R.error(res, 'Plans cannot be created under the default (Free) subscription. Free access is automatic.', 400);
|
||||
|
||||
const duration_days = computeDurationDays(duration_value, duration_unit);
|
||||
|
||||
@@ -166,9 +166,9 @@ exports.updatePlan = async (req, res) => {
|
||||
if (updates.tier_category_id) {
|
||||
const category = await mdl_TierCategories.findByPk(updates.tier_category_id);
|
||||
if (!category || !category.is_active)
|
||||
return R.error(res, 'Tier category not found or inactive.', 404);
|
||||
return R.error(res, 'Subscription category not found or inactive.', 404);
|
||||
if (category.is_default)
|
||||
return R.error(res, 'Plans cannot be moved to the Free tier category.', 400);
|
||||
return R.error(res, 'Plans cannot be moved to the Free subscription category.', 400);
|
||||
updates.tier = category.slug;
|
||||
}
|
||||
|
||||
@@ -428,10 +428,10 @@ exports.getUserTiers = async (req, res) => {
|
||||
],
|
||||
order: [['createdAt', 'DESC']],
|
||||
});
|
||||
return R.success(res, 'User tiers retrieved.', tiers);
|
||||
return R.success(res, 'User subscriptions retrieved.', tiers);
|
||||
} catch (err) {
|
||||
console.error('[ADMIN][GET USER TIERS]', err);
|
||||
return R.error(res, 'Could not retrieve user tiers.', 500);
|
||||
return R.error(res, 'Could not retrieve user subscriptions.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -472,18 +472,18 @@ exports.grantTier = async (req, res) => {
|
||||
await snapshotPlanGrants(newTier, plan_id);
|
||||
|
||||
logActivity(req.user.user_id, 'grant_tier', { entityType: 'tier', details: { user_id, tier, plan_id } });
|
||||
return R.success(res, 'Tier granted.', newTier, 201);
|
||||
return R.success(res, 'Subscription granted.', newTier, 201);
|
||||
} catch (err) {
|
||||
console.error('[ADMIN][GRANT TIER]', err);
|
||||
return R.error(res, 'Could not grant tier.', 500);
|
||||
return R.error(res, 'Could not grant subscription.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
exports.revokeTier = async (req, res) => {
|
||||
try {
|
||||
const tierRecord = await mdl_UserTiers.findByPk(req.params.tid);
|
||||
if (!tierRecord) return R.error(res, 'Tier record not found.', 404);
|
||||
if (tierRecord.status !== 'active') return R.error(res, 'Tier is not active.', 400);
|
||||
if (!tierRecord) return R.error(res, 'Subscription record not found.', 404);
|
||||
if (tierRecord.status !== 'active') return R.error(res, 'Subscription is not active.', 400);
|
||||
|
||||
await tierRecord.update({
|
||||
status: 'revoked',
|
||||
@@ -510,10 +510,10 @@ exports.revokeTier = async (req, res) => {
|
||||
}
|
||||
|
||||
logActivity(req.user.user_id, 'revoke_tier', { entityType: 'tier', details: { user_id: tierRecord.user_id, tier: tierRecord.tier } });
|
||||
return R.success(res, remainingActive === 0 ? 'Tier revoked. User downgraded to free.' : 'Tier revoked.');
|
||||
return R.success(res, remainingActive === 0 ? 'Subscription revoked. User downgraded to free.' : 'Subscription revoked.');
|
||||
} catch (err) {
|
||||
console.error('[ADMIN][REVOKE TIER]', err);
|
||||
return R.error(res, 'Could not revoke tier.', 500);
|
||||
return R.error(res, 'Could not revoke subscription.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -160,14 +160,14 @@ exports.issueToken = async (req, res) => {
|
||||
attributes: ["asset_id", "file_type", "storage_provider", "storage_key", "mime_type", "thumbnail_storage_key"],
|
||||
});
|
||||
|
||||
if (!asset) return R.error(res, "Asset not found.", 404);
|
||||
if (!asset) return R.error(res, "File not found.", 404);
|
||||
|
||||
if (!SUPPORTED_TYPES.includes(asset.file_type)) {
|
||||
return R.error(res, `Asset type "${asset.file_type}" is not supported.`, 400);
|
||||
return R.error(res, `File type "${asset.file_type}" is not supported.`, 400);
|
||||
}
|
||||
|
||||
if (asset.storage_provider !== "s3") {
|
||||
return R.error(res, "Token flow is for S3 assets only. Use the raw file_url for other providers.", 400);
|
||||
return R.error(res, "Token flow is for S3 files only. Use the raw file_url for other providers.", 400);
|
||||
}
|
||||
|
||||
// ── Bind token to the requester's IP ──────────────────────────────────────
|
||||
|
||||
@@ -77,7 +77,7 @@ exports.getMyTier = async (req, res) => {
|
||||
const freeTier = { tier: 'free', status: 'active', category: freeCategory ?? null };
|
||||
// Spread freeTier at top level too — keeps `myTier.tier`/`myTier.status`/`myTier.category`
|
||||
// working for existing frontend code that predates the active_tiers/top_tier shape.
|
||||
return R.success(res, 'Active tier retrieved.', {
|
||||
return R.success(res, 'Active subscription retrieved.', {
|
||||
...freeTier,
|
||||
active_tiers: [freeTier],
|
||||
top_tier: 'free',
|
||||
@@ -126,10 +126,10 @@ exports.getMyTier = async (req, res) => {
|
||||
// Spread topTierObj at top level too — keeps `myTier.tier`/`myTier.status`/
|
||||
// `myTier.category`/`myTier.expires_at` working for existing frontend code
|
||||
// that predates the active_tiers/top_tier shape (it'll just see the best tier).
|
||||
return R.success(res, 'Active tier retrieved.', { ...topTierObj, active_tiers, top_tier, just_expired, my_grants });
|
||||
return R.success(res, 'Active subscription retrieved.', { ...topTierObj, active_tiers, top_tier, just_expired, my_grants });
|
||||
} catch (err) {
|
||||
console.error('[CLIENT][GET MY TIER]', err);
|
||||
return R.error(res, 'Could not retrieve tier.', 500);
|
||||
return R.error(res, 'Could not retrieve subscription.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -139,10 +139,10 @@ exports.getMyTierHistory = async (req, res) => {
|
||||
where: { user_id: req.user.user_id },
|
||||
order: [['createdAt', 'DESC']],
|
||||
});
|
||||
return R.success(res, 'Tier history retrieved.', history);
|
||||
return R.success(res, 'Subscription history retrieved.', history);
|
||||
} catch (err) {
|
||||
console.error('[CLIENT][GET MY TIER HISTORY]', err);
|
||||
return R.error(res, 'Could not retrieve tier history.', 500);
|
||||
return R.error(res, 'Could not retrieve subscription history.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -392,7 +392,7 @@ exports.captureOrder = async (req, res) => {
|
||||
expires_at: expiresAt,
|
||||
granted_by: null,
|
||||
});
|
||||
successMessage = 'Payment successful. Tier activated.';
|
||||
successMessage = 'Payment successful. Subscription activated.';
|
||||
}
|
||||
|
||||
// Snapshot the plan's current bundle contents into user_tier_grants —
|
||||
@@ -471,7 +471,7 @@ exports.refundOrder = async (req, res) => {
|
||||
where: activeTierWhere,
|
||||
order: [['createdAt', 'DESC']],
|
||||
});
|
||||
if (!activeTierCandidates.length) return R.error(res, 'No active tier to refund.', 404);
|
||||
if (!activeTierCandidates.length) return R.error(res, 'No active subscription to refund.', 404);
|
||||
if (activeTierCandidates.length > 1) {
|
||||
return R.error(res, 'You have more than one active subscription — specify plan_id to refund a specific one.', 400);
|
||||
}
|
||||
@@ -481,7 +481,7 @@ exports.refundOrder = async (req, res) => {
|
||||
where: { user_id, tier_id: activeTier.tier_id, status: 'completed' },
|
||||
order: [['paid_at', 'DESC']],
|
||||
});
|
||||
if (!payment) return R.error(res, 'No completed payment found for this tier.', 404);
|
||||
if (!payment) return R.error(res, 'No completed payment found for this subscription.', 404);
|
||||
|
||||
// Load plan's payment policy to get the configured refund window
|
||||
const policy = await paymentSvc.getPolicyForPlan(payment.plan_id);
|
||||
@@ -608,10 +608,10 @@ exports.getCategories = async (req, res) => {
|
||||
include: [{ model: Asset, as: 'badgeAsset', attributes: ['asset_id', 'storage_provider', 'file_url', 'display_name'], required: false }],
|
||||
order: [['rank', 'ASC']],
|
||||
});
|
||||
return R.success(res, 'Tier categories retrieved.', categories);
|
||||
return R.success(res, 'Subscription categories retrieved.', categories);
|
||||
} catch (err) {
|
||||
console.error('[CLIENT][GET TIER CATEGORIES]', err);
|
||||
return R.error(res, 'Could not retrieve tier categories.', 500);
|
||||
return R.error(res, 'Could not retrieve subscription categories.', 500);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -44,17 +44,17 @@ exports.issueToken = async (req, res) => {
|
||||
attributes: ["asset_id", "file_type", "storage_provider", "storage_key", "mime_type", "is_public"],
|
||||
});
|
||||
|
||||
if (!asset) return R.error(res, "Asset not found.", 404);
|
||||
if (!asset) return R.error(res, "File not found.", 404);
|
||||
|
||||
// Private assets are never served through the public endpoint
|
||||
if (!asset.is_public) return R.error(res, "Forbidden.", 403);
|
||||
|
||||
if (!SUPPORTED_TYPES.includes(asset.file_type)) {
|
||||
return R.error(res, `Asset type "${asset.file_type}" is not supported.`, 400);
|
||||
return R.error(res, `File type "${asset.file_type}" is not supported.`, 400);
|
||||
}
|
||||
|
||||
if (asset.storage_provider !== "s3") {
|
||||
return R.error(res, "Token flow is for S3 assets only. Use the raw file_url for other providers.", 400);
|
||||
return R.error(res, "Token flow is for S3 files only. Use the raw file_url for other providers.", 400);
|
||||
}
|
||||
|
||||
const ip = resolveIp(req);
|
||||
|
||||
Reference in New Issue
Block a user