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
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.pyc
*.pyo
*.pyd

# Virtual Environment
backend/venv/
venv/

# Logs
backend/logs/
*.log

# OS files
.DS_Store
Thumbs.db
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
{
"DockerRun.DisableDockerrc": true
"DockerRun.DisableDockerrc": true,
"python.defaultInterpreterPath": "${workspaceFolder}/devlink/backend/venv/Scripts/python.exe",
"python.analysis.extraPaths": [
"${workspaceFolder}/devlink/backend"
]
}
45 changes: 21 additions & 24 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,23 @@
from slowapi.middleware import SlowAPIMiddleware
from slowapi import _rate_limit_exceeded_handler

from app.routers import (
activities,
applications,
auth,
bookmarks,
builder_flares,
conversations,
followers,
messages,
notifications,
organizations,
projects,
repositories,
skills,
users,
)


@asynccontextmanager
async def lifespan(app: FastAPI):
Expand Down Expand Up @@ -125,43 +142,23 @@ async def global_exception_handler(request, exc):
# API Routers
# ------------------------------------------------------------------

# Uncomment as each router is created.

from app.routers import auth
from app.routers import users
from app.routers import projects
from app.routers import builders
from app.routers import builder_flare
from app.routers import messages
from app.routers import notifications
from app.routers import ai
from app.routers import followers
from app.routers import bookmarks
from app.routers import activities
from app.routers import notifications
from app.routers import conversations
from app.routers import repositories
from app.routers import organizations
from app.routers import applications
from app.routers import skills
from app.routers import users
# Router inclusions

app.include_router(auth.router, prefix="/api/auth", tags=["Authentication"])
app.include_router(users.router, prefix="/api/users", tags=["Users"])
app.include_router(projects.router, prefix="/api/projects", tags=["Projects"])
app.include_router(builder_flare.router, prefix="/api/flare", tags=["Builder's Flare"])
app.include_router(builder_flares.router, prefix="/api/flare", tags=["Builder's Flare"])
app.include_router(messages.router, prefix="/api/messages", tags=["Messages"])
app.include_router(
notifications.router, prefix="/api/notifications", tags=["Notifications"]
)
app.include_router(ai.router, prefix="/api/ai", tags=["AI"])
# app.include_router(ai.router, prefix="/api/ai", tags=["AI"])
app.include_router(followers.router)
app.include_router(bookmarks.router)
app.include_router(activities.router)
app.include_router(notifications.router)
app.include_router(conversations.router)
app.include_router(repositories.router)
app.include_router(organizations.router)
app.include_router(applications.router)
app.include_router(skills.router)
app.include_router(users.router)

72 changes: 72 additions & 0 deletions backend/app/routers/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,3 +344,75 @@ def delete_project(
db,
project,
)


@router.post(
"/{project_id}/invite/{user_id}",
status_code=status.HTTP_201_CREATED,
)
def invite_user(
project_id: uuid.UUID,
user_id: uuid.UUID,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):

project = ProjectService.get_project(db, project_id)
if project is None:
raise HTTPException(
status_code=404,
detail="Project not found",
)

if project.owner_id != current_user.id:
raise HTTPException(
status_code=403,
detail="Only the project owner can invite members",
)

from app.models.project_member import ProjectMember, MemberRole
from sqlalchemy import and_, select
existing_member = db.scalar(
select(ProjectMember).where(
and_(
ProjectMember.project_id == project_id,
ProjectMember.user_id == user_id,
)
)
)
if existing_member:
raise HTTPException(
status_code=400,
detail="User is already invited or a member of the project",
)

new_member = ProjectMember(
project_id=project_id,
user_id=user_id,
role=MemberRole.MEMBER,
is_active=False,
)
db.add(new_member)
db.commit()
db.refresh(new_member)

from app.models.notification import NotificationType
from app.schemas.notification import NotificationCreate
from app.services.notification_service import NotificationService

notification_data = NotificationCreate(
recipient_id=user_id,
type=NotificationType.PROJECT_INVITE,
title="Project Invitation",
message=f"You have been invited to join the project '{project.title}'.",
action_url=f"/projects/{project_id}",
project_id=project_id,
)
NotificationService.create_notification(
db=db,
recipient_id=user_id,
sender_id=current_user.id,
notification=notification_data,
)

return {"message": "User invited successfully"}
35 changes: 35 additions & 0 deletions backend/app/schemas/activity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from __future__ import annotations

import uuid
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, ConfigDict
from app.models.activity import ActivityType

class ActivityBase(BaseModel):
activity_type: ActivityType
title: str
description: Optional[str] = None
project_id: Optional[uuid.UUID] = None
organization_id: Optional[uuid.UUID] = None
repository_id: Optional[uuid.UUID] = None
application_id: Optional[uuid.UUID] = None
builder_flare_id: Optional[uuid.UUID] = None
icon: Optional[str] = None
color: Optional[str] = None

class ActivityCreate(ActivityBase):
actor_id: uuid.UUID

class ActivityUpdate(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
icon: Optional[str] = None
color: Optional[str] = None

class ActivityResponse(ActivityBase):
model_config = ConfigDict(from_attributes=True)

id: uuid.UUID
actor_id: uuid.UUID
created_at: datetime
38 changes: 38 additions & 0 deletions backend/app/schemas/application.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from __future__ import annotations

import uuid
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, ConfigDict
from app.models.application import ApplicationStatus

class ApplicationBase(BaseModel):
message: Optional[str] = None
portfolio_url: Optional[str] = None
github_url: Optional[str] = None
resume_url: Optional[str] = None

class ApplicationCreate(ApplicationBase):
pass

class ApplicationUpdate(BaseModel):
status: Optional[ApplicationStatus] = None
message: Optional[str] = None
portfolio_url: Optional[str] = None
github_url: Optional[str] = None
resume_url: Optional[str] = None
review_notes: Optional[str] = None
shortlisted: Optional[bool] = None

class ApplicationResponse(ApplicationBase):
model_config = ConfigDict(from_attributes=True)

id: uuid.UUID
applicant_id: uuid.UUID
project_id: uuid.UUID
flare_id: uuid.UUID
status: ApplicationStatus
review_notes: Optional[str] = None
shortlisted: bool
created_at: datetime
updated_at: datetime
13 changes: 13 additions & 0 deletions backend/app/schemas/bookmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from __future__ import annotations

import uuid
from datetime import datetime
from pydantic import BaseModel, ConfigDict

class BookmarkResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)

id: uuid.UUID
user_id: uuid.UUID
project_id: uuid.UUID
created_at: datetime
43 changes: 43 additions & 0 deletions backend/app/schemas/builder_flare.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from __future__ import annotations

import uuid
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, ConfigDict
from app.models.builder_flare import FlareStatus

class BuilderFlareBase(BaseModel):
title: str
description: str
role: str
location: Optional[str] = None
commitment: Optional[str] = None
experience_level: Optional[str] = None
openings: int = 1
status: FlareStatus = FlareStatus.OPEN
remote: bool = True

class BuilderFlareCreate(BuilderFlareBase):
project_id: uuid.UUID

class BuilderFlareUpdate(BaseModel):
title: Optional[str] = None
description: Optional[str] = None
role: Optional[str] = None
location: Optional[str] = None
commitment: Optional[str] = None
experience_level: Optional[str] = None
openings: Optional[int] = None
status: Optional[FlareStatus] = None
remote: Optional[bool] = None

class BuilderFlareResponse(BuilderFlareBase):
model_config = ConfigDict(from_attributes=True)

id: uuid.UUID
project_id: uuid.UUID
created_by: uuid.UUID
applicants_count: int
featured: bool
created_at: datetime
updated_at: datetime
30 changes: 30 additions & 0 deletions backend/app/schemas/conversation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from __future__ import annotations

import uuid
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, ConfigDict
from app.models.conversation import ConversationType

class ConversationBase(BaseModel):
type: ConversationType = ConversationType.DIRECT
title: Optional[str] = None
project_id: Optional[uuid.UUID] = None

class ConversationCreate(ConversationBase):
pass

class ConversationUpdate(BaseModel):
title: Optional[str] = None
is_active: Optional[bool] = None
archived: Optional[bool] = None

class ConversationResponse(ConversationBase):
model_config = ConfigDict(from_attributes=True)

id: uuid.UUID
created_by: uuid.UUID
is_active: bool
archived: bool
created_at: datetime
updated_at: datetime
13 changes: 13 additions & 0 deletions backend/app/schemas/follower.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from __future__ import annotations

import uuid
from datetime import datetime
from pydantic import BaseModel, ConfigDict

class FollowerResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)

id: uuid.UUID
follower_id: uuid.UUID
following_id: uuid.UUID
created_at: datetime
37 changes: 37 additions & 0 deletions backend/app/schemas/message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from __future__ import annotations

import uuid
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, ConfigDict
from app.models.message import MessageType

class MessageBase(BaseModel):
content: str
type: MessageType = MessageType.TEXT
parent_message_id: Optional[uuid.UUID] = None
attachment_url: Optional[str] = None
attachment_name: Optional[str] = None
attachment_size: Optional[int] = None
mime_type: Optional[str] = None

class MessageCreate(MessageBase):
pass

class MessageUpdate(BaseModel):
content: Optional[str] = None
is_edited: Optional[bool] = None
is_deleted: Optional[bool] = None

class MessageResponse(MessageBase):
model_config = ConfigDict(from_attributes=True)

id: uuid.UUID
conversation_id: uuid.UUID
sender_id: uuid.UUID
is_edited: bool
is_deleted: bool
created_at: datetime
updated_at: datetime
edited_at: Optional[datetime] = None
deleted_at: Optional[datetime] = None
Loading
Loading