Skip to content
Open
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
3 changes: 2 additions & 1 deletion Backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .routes.chat import router as chat_router
from .routes.match import router as match_router
from sqlalchemy.exc import SQLAlchemyError
from .routes.collaboration import router as collaboration_router
import logging
import os
from dotenv import load_dotenv
Expand Down Expand Up @@ -56,7 +57,7 @@ async def lifespan(app: FastAPI):
app.include_router(match_router)
app.include_router(ai.router)
app.include_router(ai.youtube_router)

app.include_router(collaboration_router)

@app.get("/")
async def home():
Expand Down
31 changes: 31 additions & 0 deletions Backend/app/routes/collaboration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import update
from ..db.db import get_db
from ..models.models import Collaboration

router = APIRouter(prefix="/collaboration", tags=["Collaboration"])

@router.put("/update-status/{collab_id}")
async def update_collab_status(
collab_id: str,
status: str,
db: Annotated[AsyncSession, Depends(get_db)]
):
"""
Updates the status of a collaboration request.
Includes validation to ensure the record exists.
"""
if status not in ["accepted", "denied"]:
raise HTTPException(status_code=400, detail="Invalid status")

query = update(Collaboration).where(Collaboration.id == collab_id).values(status=status)
result = await db.execute(query)
await db.commit()

# Major fix: Check if the row actually existed
if result.rowcount == 0:
raise HTTPException(status_code=404, detail=f"Collaboration {collab_id} not found")

return {"message": f"Collaboration {status} successfully"}
23 changes: 23 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Use a stable Python base image
FROM python:3.10-slim
Comment on lines +1 to +2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Container runs as root — add a non-root USER.

Running the application as root inside the container is a security risk. If the process is compromised, the attacker has full root privileges within the container. This was also flagged by Trivy (DS-0002).

Proposed fix — add a non-root user at the end
+# Create a non-root user
+RUN useradd --create-home appuser
+USER appuser
+
 # Change to the Backend directory to run the application
 WORKDIR /app/Backend
🧰 Tools
🪛 Trivy (0.69.1)

[error] 1-1: Image user should not be 'root'

Specify at least 1 USER command in Dockerfile with non-root user as argument

Rule: DS-0002

Learn more

(IaC/Dockerfile)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 1 - 2, The container currently runs as root; add a
non-root user and switch to it at the end of the Dockerfile: create a user and
group (e.g., appuser/appgroup) with non-root UID/GID, ensure application
files/directories are owned by that user (chown relevant paths), set appropriate
permissions, and add a USER appuser (or UID:GID) instruction as the final step
so the process runs unprivileged; reference the Dockerfile and the USER
directive when making the changes.


# Set the working directory
WORKDIR /app

# Install system-level dependencies for building Python packages
RUN apt-get update && apt-get install -y \
build-essential \
&& rm -rf /var/lib/apt/lists/*
Comment on lines +8 to +10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add --no-install-recommends to reduce image bloat.

Without this flag, apt-get install pulls in recommended (but unnecessary) packages, increasing image size. Also flagged by Trivy (DS-0029).

Proposed fix
-RUN apt-get update && apt-get install -y \
+RUN apt-get update && apt-get install -y --no-install-recommends \
     build-essential \
     && rm -rf /var/lib/apt/lists/*
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RUN apt-get update && apt-get install -y \
build-essential \
&& rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
🧰 Tools
🪛 Trivy (0.69.1)

[error] 8-10: 'apt-get' missing '--no-install-recommends'

'--no-install-recommends' flag is missed: 'apt-get update && apt-get install -y build-essential && rm -rf /var/lib/apt/lists/*'

Rule: DS-0029

Learn more

(IaC/Dockerfile)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 8 - 10, The apt-get install invocation in the
Dockerfile (the RUN line that performs "apt-get update && apt-get install -y
build-essential && rm -rf /var/lib/apt/lists/*") should include
--no-install-recommends to avoid installing unnecessary recommended packages;
update that RUN command to use "apt-get install -y --no-install-recommends
build-essential" (keeping the existing apt-get update and the rm -rf
/var/lib/apt/lists/* cleanup) so the image size is reduced and Trivy DS-0029 is
addressed.


# Copy requirements from the Backend folder and install
COPY Backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy the entire project into the container
COPY . .

# Set environment variables (standard for AI platforms)
ENV PYTHONUNBUFFERED=1

# Change to the Backend directory to run the application
WORKDIR /app/Backend
Comment on lines +1 to +23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

No CMD or ENTRYPOINT — the container has no default run command.

The Dockerfile sets up the environment but never declares what to run. Without a CMD or ENTRYPOINT, docker run on this image will drop into a shell (inherited from the base image) rather than starting the backend service. This makes the image unusable out of the box.

Add an appropriate entrypoint, e.g.:

Proposed fix
 # Change to the Backend directory to run the application
 WORKDIR /app/Backend
+
+# Run the application
+CMD ["python", "main.py"]

Adjust main.py to whatever the actual entry point script is.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Use a stable Python base image
FROM python:3.10-slim
# Set the working directory
WORKDIR /app
# Install system-level dependencies for building Python packages
RUN apt-get update && apt-get install -y \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements from the Backend folder and install
COPY Backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy the entire project into the container
COPY . .
# Set environment variables (standard for AI platforms)
ENV PYTHONUNBUFFERED=1
# Change to the Backend directory to run the application
WORKDIR /app/Backend
# Use a stable Python base image
FROM python:3.10-slim
# Set the working directory
WORKDIR /app
# Install system-level dependencies for building Python packages
RUN apt-get update && apt-get install -y \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements from the Backend folder and install
COPY Backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy the entire project into the container
COPY . .
# Set environment variables (standard for AI platforms)
ENV PYTHONUNBUFFERED=1
# Change to the Backend directory to run the application
WORKDIR /app/Backend
# Run the application
CMD ["python", "main.py"]
🧰 Tools
🪛 Trivy (0.69.1)

[error] 1-1: Image user should not be 'root'

Specify at least 1 USER command in Dockerfile with non-root user as argument

Rule: DS-0002

Learn more

(IaC/Dockerfile)


[error] 8-10: 'apt-get' missing '--no-install-recommends'

'--no-install-recommends' flag is missed: 'apt-get update && apt-get install -y build-essential && rm -rf /var/lib/apt/lists/*'

Rule: DS-0029

Learn more

(IaC/Dockerfile)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 1 - 23, The Dockerfile currently misses a default
runtime command; add an ENTRYPOINT or CMD in the Dockerfile to start the backend
service (e.g., invoke Python on your app entrypoint or start Uvicorn for an ASGI
app). Update the Dockerfile to include a final line that runs the correct
entrypoint (reference the project's actual startup target such as main.py or the
ASGI app module) and ensure the referenced script/module (e.g., main.py or
app:app) is present in Backend and executable; adjust the entrypoint string to
match that symbol.

75 changes: 55 additions & 20 deletions Frontend/src/components/collaboration-hub/CollabRequests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { Badge } from "../ui/badge";
import { Separator } from "../ui/separator";
import { MessageSquare, CheckCircle, XCircle, Lightbulb, TrendingUp, Users, Star, Mail } from "lucide-react";

// Mock data for incoming requests
const BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8000";

const mockRequests = [
{
id: 1,
id: "1",
sender: {
name: "TechSavvy",
avatar: "https://randomuser.me/api/portraits/men/32.jpg",
Expand Down Expand Up @@ -51,7 +52,7 @@ const mockRequests = [
}
},
{
id: 2,
id: "2",
sender: {
name: "EcoChic",
avatar: "https://randomuser.me/api/portraits/women/44.jpg",
Expand Down Expand Up @@ -96,22 +97,48 @@ const mockRequests = [

const CollabRequests: React.FC = () => {
const [requests, setRequests] = useState(mockRequests);
// Switch to Set to track multiple in-flight requests
const [loadingIds, setLoadingIds] = useState<Set<string>>(new Set());

const handleAccept = (id: number) => {
setRequests(prev => prev.filter(req => req.id !== id));
// TODO: Integrate with backend to accept request
// Shared helper to keep code DRY
const handleStatusUpdate = async (id: string, status: 'accepted' | 'denied', verb: string) => {
if (loadingIds.has(id)) return;

setLoadingIds(prev => new Set(prev).add(id));
try {
const response = await fetch(`${BASE_URL}/collaboration/update-status/${id}?status=${status}`, {
method: 'PUT',
});
if (response.ok) {
setRequests(prev => prev.filter(req => req.id !== id));
} else {
const err = await response.json().catch(() => ({}));
alert(`Failed to ${verb}: ${err.detail || "Server error"}`);
}
} catch (error) {
console.error(`Failed to ${verb} request:`, error);
alert(`Network error: Could not ${verb} request. Please check your connection.`);
} finally {
setLoadingIds(prev => {
const next = new Set(prev);
next.delete(id);
return next;
});
}
};

const handleDeny = (id: number) => {
setRequests(prev => prev.filter(req => req.id !== id));
// TODO: Integrate with backend to deny request
};
const handleAccept = (id: string) => handleStatusUpdate(id, 'accepted', 'accept');
const handleDeny = (id: string) => handleStatusUpdate(id, 'denied', 'deny');

const handleMessage = (id: number) => {
// TODO: Open message modal or redirect to chat
const handleMessage = (_id: string) => {
alert("Open chat with sender (not implemented)");
};

// Wired up the Email button
const handleEmail = (_id: string) => {
alert("Email functionality is coming soon!");
};

return (
<div className="space-y-6">
{requests.length === 0 ? (
Expand Down Expand Up @@ -140,7 +167,6 @@ const CollabRequests: React.FC = () => {
<div className="mb-3">
<span className="font-medium text-gray-800">Request:</span> {req.summary}
</div>
{/* Initial Collaboration Proposal Section */}
<div className="bg-purple-50 border border-purple-100 rounded-lg p-3 mb-4">
<div className="flex items-center gap-2 mb-1 text-purple-700 font-semibold text-sm">
<span role="img" aria-label="proposal">📝</span> Initial Collaboration Proposal
Expand All @@ -154,21 +180,18 @@ const CollabRequests: React.FC = () => {
</ul>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
{/* AI Advantages */}
<div className="bg-blue-50 border border-blue-100 rounded-lg p-3">
<div className="flex items-center gap-2 mb-1 text-blue-700 font-semibold text-sm"><Lightbulb className="h-4 w-4" /> Advantages</div>
<ul className="list-disc ml-5 text-xs text-blue-900">
{req.ai.advantages.map((adv, i) => <li key={i}>{adv}</li>)}
</ul>
</div>
{/* AI Ideas */}
<div className="bg-green-50 border border-green-100 rounded-lg p-3">
<div className="flex items-center gap-2 mb-1 text-green-700 font-semibold text-sm"><Lightbulb className="h-4 w-4" /> Collaboration Ideas</div>
<ul className="list-disc ml-5 text-xs text-green-900">
{req.ai.ideas.map((idea, i) => <li key={i}>{idea}</li>)}
</ul>
</div>
{/* AI Recommendations */}
<div className="bg-yellow-50 border border-yellow-100 rounded-lg p-3">
<div className="flex items-center gap-2 mb-1 text-yellow-700 font-semibold text-sm"><Lightbulb className="h-4 w-4" /> Recommendations</div>
<ul className="list-disc ml-5 text-xs text-yellow-900">
Expand All @@ -183,16 +206,28 @@ const CollabRequests: React.FC = () => {
<span>Avg Views: <b>{req.stats.avgViews}</b></span>
</div>
<div className="flex gap-3 mt-4">
<Button size="sm" className="flex items-center gap-1 bg-green-100 text-green-800 hover:bg-green-200 border border-green-200" onClick={() => handleAccept(req.id)} aria-label="Accept collaboration request">
<Button
size="sm"
className="flex items-center gap-1 bg-green-100 text-green-800 hover:bg-green-200 border border-green-200"
onClick={() => handleAccept(req.id)}
disabled={loadingIds.has(req.id)}
aria-label="Accept collaboration request"
>
<CheckCircle className="h-4 w-4" /> Accept
</Button>
<Button size="sm" className="flex items-center gap-1 bg-red-100 text-red-800 hover:bg-red-200 border border-red-200" onClick={() => handleDeny(req.id)} aria-label="Deny collaboration request">
<Button
size="sm"
className="flex items-center gap-1 bg-red-100 text-red-800 hover:bg-red-200 border border-red-200"
onClick={() => handleDeny(req.id)}
disabled={loadingIds.has(req.id)}
aria-label="Deny collaboration request"
>
<XCircle className="h-4 w-4" /> Deny
</Button>
<Button size="sm" variant="outline" className="flex items-center gap-1" onClick={() => handleMessage(req.id)} aria-label="Message sender">
<MessageSquare className="h-4 w-4" /> Message
</Button>
<Button size="sm" variant="outline" className="flex items-center gap-1" aria-label="Email sender">
<Button size="sm" variant="outline" className="flex items-center gap-1" onClick={() => handleEmail(req.id)} aria-label="Email sender">
<Mail className="h-4 w-4" /> Email
</Button>
</div>
Expand All @@ -204,4 +239,4 @@ const CollabRequests: React.FC = () => {
);
};

export default CollabRequests;
export default CollabRequests;