Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,7 @@ backend/node_modules
**/node_modules


package-lock.json
package-lock.json


local_uploads
4 changes: 3 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@types/compression": "^1.7.5",
"@types/cookie-parser": "^1.4.3",
"@types/cors": "^2.8.18",
"@types/express": "^4.17.1",
"@types/express": "^4.17.22",
"@types/formidable": "^3.4.5",
"@types/jest": "^29.5.14",
"@types/ms": "^2.1.0",
Expand All @@ -34,6 +34,8 @@
},
"dependencies": {
"@algolia/client-search": "^5.20.4",
"@aws-sdk/client-s3": "^3.816.0",
"@aws-sdk/s3-request-presigner": "^3.816.0",
"@google/generative-ai": "^0.24.1",
"@langchain/core": "^0.3.57",
"@types/jsonwebtoken": "^9.0.7",
Expand Down
10 changes: 9 additions & 1 deletion backend/src/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@ export const appConfig = {
// Lower default body-parser limits to mitigate DoS risk; adjust per-route as needed
json: json({ limit: "2mb" }),
urlencoded: urlencoded({ extended: true, limit: "2mb" }),
helmet: helmet(),
helmet: helmet({
contentSecurityPolicy: {
directives: {
...helmet.contentSecurityPolicy.getDefaultDirectives(),
"img-src": ["'self'", "http://localhost:3000", "data:"],
},
},
crossOriginResourcePolicy: { policy: "cross-origin" },
}),
compression: compression(),
cookieParser: cookieParser(),
},
Expand Down
52 changes: 28 additions & 24 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import express from 'express';
import rateLimit from 'express-rate-limit';
import { appConfig } from './app.config';
import { errorHandler } from './shared/middleware/error.middleware';
import { stripeWebhookMiddleware } from './shared/middleware/stripe.middleware';
import { logger } from './shared/utils/logger';
import { createErrorResponse } from './shared/utils/response';
import express from "express";
import rateLimit from "express-rate-limit";
import { appConfig } from "./app.config";
import { errorHandler } from "./shared/middleware/error.middleware";
import { stripeWebhookMiddleware } from "./shared/middleware/stripe.middleware";
import { logger } from "./shared/utils/logger";
import { createErrorResponse } from "./shared/utils/response";
import path from "path";

// Import routes
import userRoutes from './modules/user/routes/user.routes';
Expand All @@ -17,7 +18,6 @@ import webhookRoutes from './modules/subscription/routes/webhook.routes';
import searchRoutes from './modules/search/routes/search.routes';
import summarizationRoutes from './modules/ai/routes/summarization.routes';


const app = express();

// Apply basic middleware
Expand All @@ -39,37 +39,41 @@ app.use((req, res, next) => {
const limiter = rateLimit({
...appConfig.rateLimiting,
handler: (req, res) => {
res.status(429).json(
createErrorResponse('Too many requests, please try again later')
);
res
.status(429)
.json(createErrorResponse("Too many requests, please try again later"));
},
});
app.use(limiter);

// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
app.get("/health", (req, res) => {
res.json({ status: "ok", timestamp: new Date().toISOString() });
});

// API routes
app.use('/user', userRoutes);
app.use('/comment', commentRoutes);
app.use('/content', contentRoutes);
app.use('/subscription', subscriptionRoutes);
app.use('/notification', notificationRoutes);
app.use('/oauth', oauthRoutes);
app.use('/webhook', webhookRoutes);
app.use('/search', searchRoutes);
app.use("/user", userRoutes);
app.use("/comment", commentRoutes);
app.use("/content", contentRoutes);
app.use("/subscription", subscriptionRoutes);
app.use("/notification", notificationRoutes);
app.use("/oauth", oauthRoutes);
app.use("/webhook", webhookRoutes);
app.use("/search", searchRoutes);
app.use('/ai', summarizationRoutes);
app.use(
"/local_uploads",
express.static(path.join(process.cwd(), "local_uploads"))
);

app.get('/', (_, res) => {
res.send('Server is Listening!');
app.get("/", (_, res) => {
res.send("Server is Listening!");
});

// 404 handler
app.use((req, res) => {
res.status(404).json({
error: `Cannot ${req.method} ${req.path}`
error: `Cannot ${req.method} ${req.path}`,
});
});

Expand Down
37 changes: 26 additions & 11 deletions backend/src/modules/content/controllers/content.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,40 +20,51 @@ export class ContentController {
console.log(error);
res
.status(500)
.json({ error: error as string || "Failed to create content" });
.json({ error: (error as string) || "Failed to create content" });
}
}

static async uploadThumbnail(req: Request, res: Response) {
console.log("Uploading Thumbnail...");
console.log(req.body);
console.log(req.params);
const form = new IncomingForm();
form.parse(req, async (err, fields, files: any) => {
if (err) {
console.error("Error parsing form: ", err);
return res.status(500).json({ error: "Failed to upload thumbnail." });
}

// Check if files.thumbnail exists and is an array
if (
!files.thumbnail ||
!Array.isArray(files.thumbnail) ||
!files.thumbnail[0]
) {
return res.status(400).json({ error: "No file uploaded." });
}

const file = files.thumbnail[0];
const fileName = file.newFilename;
const fileType = file.mimetype;
const filePath = file.filepath; // Use 'filepath' for formidable v2+

// Upload thumbnail to storage
try {
// Upload thumbnail
if (!filePath) {
return res.status(400).json({ error: "File path is missing." });
}

// Pass the file object with the correct path to StorageService
const response = await StorageService.uploadFile(
file,
{ ...file, path: filePath }, // Ensure 'path' is set if your StorageService expects it
"thumbnails",
fileName,
fileType
fileType,
);
res.status(201).json(response);
} catch (error: any) {
console.log(error);
res
.status(500)
.json({ error: error as string || "Failed to upload thumbnail" });
.json({ error: error.message || "Failed to upload thumbnail" });
}
});
}
Expand Down Expand Up @@ -82,7 +93,7 @@ export class ContentController {
// const confirmation = await axios.get(`${apiURL}/content/${contentId}`)
const confirmation = await ContentService.getContent(contentId);
const owner_id = confirmation?.creatorUID;

if (userId == owner_id) {
//check whether they are allowed to edit the content
const response = await ContentService.editContent(contentId, data);
Expand Down Expand Up @@ -137,11 +148,15 @@ export class ContentController {
const fileType = file.mimetype;

try {
if (!file) {
return res.status(400).json({ error: "No file uploaded." });
}

const response = await StorageService.uploadFile(
file,
"thumbnails",
fileName,
fileType
fileType,
);

const updateData = JSON.parse(fields.data);
Expand Down Expand Up @@ -433,7 +448,7 @@ export class ContentController {
static async getRelatedContent(req: Request, res: Response) {
console.log("Fetching Related Content...");
const { contentId } = req.params;
const userId = req.query.userId as string || undefined;
const userId = (req.query.userId as string) || undefined;
const limit = req.query.limit ? parseInt(req.query.limit as string) : 5;

try {
Expand Down
Loading