310 lines
12 KiB
TypeScript
310 lines
12 KiB
TypeScript
import { Response, NextFunction } from 'express';
|
|
import path from 'path';
|
|
import { PutObjectCommand } from '@aws-sdk/client-s3';
|
|
import { s3Client, BUCKET_NAME } from '../utils/s3';
|
|
import { AssetService } from '../services/asset.service';
|
|
import { ScraperService } from '../services/scraper.service';
|
|
import { AuthRequest } from '../middleware/auth.middleware';
|
|
|
|
export class AssetController {
|
|
private assetService = new AssetService();
|
|
private scraperService = new ScraperService();
|
|
|
|
public scrapeCaseStudy = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const url = req.query.url as string;
|
|
if (!url) {
|
|
return res.status(400).json({ error: 'URL parameter is required' });
|
|
}
|
|
const data = await this.scraperService.scrapeCaseStudy(url);
|
|
res.status(200).json(data);
|
|
} catch (err) { next(err); }
|
|
}
|
|
|
|
public uploadAsset = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const files = req.files as { [fieldname: string]: Express.Multer.File[] } | undefined;
|
|
const assetFile = files?.file?.[0] || req.file;
|
|
const thumbnailFile = files?.thumbnail?.[0];
|
|
|
|
const isUrlAsset = req.body.isUrlAsset === 'true' || req.body.isUrlAsset === true || req.body.type === 'url' || req.body.type === 'case_study';
|
|
|
|
if (!isUrlAsset && !assetFile) {
|
|
throw new Error('No file uploaded');
|
|
}
|
|
|
|
const uploaderId = req.user?.userId || 'system';
|
|
|
|
const description = req.body.description;
|
|
if (!description || !description.trim()) {
|
|
return res.status(400).json({ error: 'Description is required' });
|
|
}
|
|
|
|
// Parse shares if present
|
|
let shares = req.body.shares;
|
|
if (typeof shares === 'string' && shares.trim()) {
|
|
try { shares = JSON.parse(shares); } catch { shares = undefined; }
|
|
}
|
|
|
|
let fileUrl = '';
|
|
if (!isUrlAsset && assetFile) {
|
|
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
|
|
const filename = uniqueSuffix + path.extname(assetFile.originalname);
|
|
|
|
await s3Client.send(new PutObjectCommand({
|
|
Bucket: BUCKET_NAME,
|
|
Key: filename,
|
|
Body: assetFile.buffer,
|
|
ContentType: assetFile.mimetype,
|
|
}));
|
|
|
|
fileUrl = `/uploads/${filename}`;
|
|
} else {
|
|
fileUrl = req.body.url;
|
|
}
|
|
|
|
// Handle thumbnail file upload if present
|
|
let thumbnailUrl: string | null = req.body.thumbnailUrl || null;
|
|
if (thumbnailFile) {
|
|
if (!thumbnailFile.mimetype.startsWith('image/')) {
|
|
return res.status(400).json({ error: 'Thumbnail must be an image file' });
|
|
}
|
|
const thumbSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
|
|
const thumbFilename = 'thumb-' + thumbSuffix + path.extname(thumbnailFile.originalname);
|
|
|
|
await s3Client.send(new PutObjectCommand({
|
|
Bucket: BUCKET_NAME,
|
|
Key: thumbFilename,
|
|
Body: thumbnailFile.buffer,
|
|
ContentType: thumbnailFile.mimetype,
|
|
}));
|
|
|
|
thumbnailUrl = `/uploads/${thumbFilename}`;
|
|
}
|
|
|
|
const parseJsonOrArray = (val: any) => {
|
|
if (!val) return undefined;
|
|
if (typeof val === 'string' && val.trim()) {
|
|
try { return JSON.parse(val); } catch { return val.split(',').map((id: string) => id.trim()).filter(Boolean); }
|
|
}
|
|
return val;
|
|
};
|
|
|
|
let verticalIds = parseJsonOrArray(req.body.verticalIds);
|
|
let techStackIds = parseJsonOrArray(req.body.techStackIds);
|
|
let engagementTypeIds = parseJsonOrArray(req.body.engagementTypeIds);
|
|
let complianceIds = parseJsonOrArray(req.body.complianceIds);
|
|
|
|
const assetData = {
|
|
title: req.body.title || (assetFile ? assetFile.originalname : 'URL Asset'),
|
|
type: req.body.type || (isUrlAsset ? 'url' : assetFile!.mimetype),
|
|
size: isUrlAsset ? 0 : assetFile!.size,
|
|
url: fileUrl,
|
|
uploadedBy: uploaderId,
|
|
description: req.body.description || null,
|
|
categoryId: req.body.categoryId || null,
|
|
subcategory: req.body.subcategory || null,
|
|
tags: req.body.tags || [],
|
|
githubUrl: req.body.githubUrl || null,
|
|
status: req.body.status || 'published',
|
|
isDownloadable: req.body.isDownloadable === 'true' || req.body.isDownloadable === true,
|
|
thumbnailUrl: thumbnailUrl,
|
|
problemStatement: req.body.problemStatement || null,
|
|
solution: req.body.solution || null,
|
|
verticalIds,
|
|
techStackIds,
|
|
engagementTypeIds,
|
|
complianceIds,
|
|
shares,
|
|
sharedOrgIds: req.body.sharedOrgIds || null,
|
|
};
|
|
|
|
const asset = await this.assetService.createAsset(assetData);
|
|
res.status(201).json(asset);
|
|
} catch (err) { next(err); }
|
|
}
|
|
|
|
public uploadThumbnail = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const files = req.files as { [fieldname: string]: Express.Multer.File[] } | undefined;
|
|
const file = req.file || files?.thumbnail?.[0];
|
|
if (!file) {
|
|
return res.status(400).json({ error: 'No thumbnail file uploaded' });
|
|
}
|
|
|
|
if (!file.mimetype.startsWith('image/')) {
|
|
return res.status(400).json({ error: 'Thumbnail must be an image file' });
|
|
}
|
|
|
|
const thumbSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
|
|
const thumbFilename = 'thumb-' + thumbSuffix + path.extname(file.originalname);
|
|
|
|
await s3Client.send(new PutObjectCommand({
|
|
Bucket: BUCKET_NAME,
|
|
Key: thumbFilename,
|
|
Body: file.buffer,
|
|
ContentType: file.mimetype,
|
|
}));
|
|
|
|
const thumbnailUrl = `/uploads/${thumbFilename}`;
|
|
res.status(200).json({ thumbnailUrl });
|
|
} catch (err) { next(err); }
|
|
}
|
|
|
|
public listAssets = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const userContext = req.user ? { role: req.user.role, userId: req.user.userId } : undefined;
|
|
const filters = {
|
|
search: req.query.search as string,
|
|
verticalIds: req.query.verticalIds ? (req.query.verticalIds as string).split(',') : undefined,
|
|
techStackIds: req.query.techStackIds ? (req.query.techStackIds as string).split(',') : undefined,
|
|
engagementTypeIds: req.query.engagementTypeIds ? (req.query.engagementTypeIds as string).split(',') : undefined,
|
|
complianceIds: req.query.complianceIds ? (req.query.complianceIds as string).split(',') : undefined,
|
|
contentTypes: req.query.contentTypes ? (req.query.contentTypes as string).split(',') : undefined,
|
|
subcategories: req.query.subcategories ? (req.query.subcategories as string).split(',') : undefined,
|
|
tags: req.query.tags ? (req.query.tags as string).split(',') : undefined,
|
|
sortBy: req.query.sortBy as any,
|
|
};
|
|
const assets = await this.assetService.getAssets(userContext, filters);
|
|
res.status(200).json(assets);
|
|
} catch(err) { next(err); }
|
|
}
|
|
|
|
public getAsset = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const asset = await this.assetService.getAssetById(req.params.id);
|
|
if (!asset) {
|
|
return res.status(404).json({ error: 'Asset not found' });
|
|
}
|
|
res.status(200).json(asset);
|
|
} catch (err) { next(err); }
|
|
}
|
|
|
|
public updateAsset = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const asset = await this.assetService.updateAsset(req.params.id, req.body);
|
|
res.status(200).json(asset);
|
|
} catch (err) { next(err); }
|
|
}
|
|
|
|
public bulkShareAssets = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const { assetIds, shares } = req.body;
|
|
if (!Array.isArray(assetIds)) {
|
|
return res.status(400).json({ error: 'assetIds must be an array' });
|
|
}
|
|
if (!Array.isArray(shares)) {
|
|
return res.status(400).json({ error: 'shares must be an array' });
|
|
}
|
|
await this.assetService.bulkShareAssets(assetIds, shares);
|
|
res.status(200).json({ message: 'Assets shared successfully' });
|
|
} catch (err) { next(err); }
|
|
}
|
|
|
|
public shareAsset = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const { organizationIds } = req.body;
|
|
if (!Array.isArray(organizationIds)) {
|
|
return res.status(400).json({ error: 'organizationIds must be an array' });
|
|
}
|
|
const asset = await this.assetService.shareAsset(req.params.id, organizationIds);
|
|
res.status(200).json(asset);
|
|
} catch (err) { next(err); }
|
|
}
|
|
|
|
public unshareAsset = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const { organizationIds } = req.body;
|
|
if (!Array.isArray(organizationIds)) {
|
|
return res.status(400).json({ error: 'organizationIds must be an array' });
|
|
}
|
|
const asset = await this.assetService.unshareAsset(req.params.id, organizationIds);
|
|
res.status(200).json(asset);
|
|
} catch (err) { next(err); }
|
|
}
|
|
|
|
public incrementDownload = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const asset = await this.assetService.incrementDownloadCount(req.params.id);
|
|
res.status(200).json(asset);
|
|
} catch (err) { next(err); }
|
|
}
|
|
|
|
public deleteAsset = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
await this.assetService.deleteAsset(req.params.id);
|
|
res.status(204).send();
|
|
} catch(err) { next(err); }
|
|
}
|
|
|
|
public requestDownload = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const userId = req.user?.userId;
|
|
if (!userId) return res.status(401).json({ error: 'Unauthorized' });
|
|
const request = await this.assetService.requestDownload(req.params.id, userId);
|
|
res.status(200).json(request);
|
|
} catch (err) { next(err); }
|
|
}
|
|
|
|
public approveDownload = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const request = await this.assetService.approveDownloadRequest(req.params.requestId);
|
|
res.status(200).json(request);
|
|
} catch (err) { next(err); }
|
|
}
|
|
|
|
public rejectDownload = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const request = await this.assetService.rejectDownloadRequest(req.params.requestId);
|
|
res.status(200).json(request);
|
|
} catch (err) { next(err); }
|
|
}
|
|
|
|
public createAssetGroup = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const { name, description, assetIds } = req.body;
|
|
if (!name) {
|
|
return res.status(400).json({ error: 'Group name is required' });
|
|
}
|
|
const group = await this.assetService.createAssetGroup(name, description, assetIds || []);
|
|
res.status(201).json(group);
|
|
} catch (err) { next(err); }
|
|
}
|
|
|
|
public listAssetGroups = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const groups = await this.assetService.getAssetGroups();
|
|
res.status(200).json(groups);
|
|
} catch (err) { next(err); }
|
|
}
|
|
|
|
public deleteAssetGroup = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
await this.assetService.deleteAssetGroup(req.params.id);
|
|
res.status(204).send();
|
|
} catch (err) { next(err); }
|
|
}
|
|
|
|
public addAssetsToGroup = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const { assetIds } = req.body;
|
|
if (!Array.isArray(assetIds)) {
|
|
return res.status(400).json({ error: 'assetIds must be an array' });
|
|
}
|
|
const group = await this.assetService.addAssetsToGroup(req.params.id, assetIds);
|
|
res.status(200).json(group);
|
|
} catch (err) { next(err); }
|
|
}
|
|
|
|
public removeAssetsFromGroup = async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
try {
|
|
const { assetIds } = req.body;
|
|
if (!Array.isArray(assetIds)) {
|
|
return res.status(400).json({ error: 'assetIds must be an array' });
|
|
}
|
|
const group = await this.assetService.removeAssetsFromGroup(req.params.id, assetIds);
|
|
res.status(200).json(group);
|
|
} catch (err) { next(err); }
|
|
}
|
|
}
|