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); } } }