|
import logging
|
|
from typing import Annotated
|
|
from datetime import datetime, timezone
|
|
from sqlalchemy import or_
|
|
from fastapi import APIRouter, Request, HTTPException, Form
|
|
from devplacepy.models import ProjectForm
|
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
|
from devplacepy.database import get_table, get_users_by_uids, get_site_stats
|
|
from devplacepy.content import load_detail, delete_content_item
|
|
from devplacepy.attachments import link_attachments
|
|
from devplacepy.templating import templates
|
|
from devplacepy.utils import generate_uid, get_current_user, require_user, make_combined_slug, create_mention_notifications
|
|
from devplacepy.seo import base_seo_context, site_url, website_schema, software_application_schema
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter()
|
|
|
|
|
|
def get_projects_list(tab: str = "recent", search: str = "", user_uid: str = None, project_type: str = None):
|
|
projects = get_table("projects")
|
|
|
|
filters = {}
|
|
if user_uid:
|
|
filters["user_uid"] = user_uid
|
|
if project_type:
|
|
filters["project_type"] = project_type
|
|
if tab == "released":
|
|
filters["status"] = "Released"
|
|
|
|
clauses = []
|
|
if search and projects.exists:
|
|
like = f"%{search}%"
|
|
clauses.append(or_(projects.table.columns.title.ilike(like), projects.table.columns.description.ilike(like)))
|
|
|
|
order = ["-stars", "-created_at"] if tab == "popular" else ["-created_at"]
|
|
all_projects = list(projects.find(*clauses, **filters, order_by=order))
|
|
|
|
if all_projects:
|
|
users_map = get_users_by_uids([p["user_uid"] for p in all_projects])
|
|
for p in all_projects:
|
|
author = users_map.get(p["user_uid"])
|
|
p["author_name"] = author["username"] if author else "Unknown"
|
|
|
|
return all_projects
|
|
|
|
|
|
@router.get("", response_class=HTMLResponse)
|
|
async def projects_page(
|
|
request: Request,
|
|
tab: str = "recent",
|
|
search: str = "",
|
|
user_uid: str = None,
|
|
project_type: str = None,
|
|
):
|
|
user = get_current_user(request)
|
|
projects = get_projects_list(tab, search, user_uid, project_type)
|
|
total_members = get_site_stats()["total_members"]
|
|
|
|
base = site_url(request)
|
|
seo_ctx = base_seo_context(
|
|
request,
|
|
title="Projects",
|
|
description=f"Explore {len(projects)} developer projects on DevPlace. Games, software, mobile apps and more.",
|
|
breadcrumbs=[
|
|
{"name": "Home", "url": "/feed"},
|
|
{"name": "Projects", "url": "/projects"},
|
|
],
|
|
schemas=[website_schema(base)],
|
|
)
|
|
return templates.TemplateResponse(request, "projects.html", {
|
|
**seo_ctx,
|
|
"request": request,
|
|
"user": user,
|
|
"projects": projects,
|
|
"current_tab": tab,
|
|
"search": search,
|
|
"project_type": project_type,
|
|
"total_count": len(projects),
|
|
"total_members": total_members,
|
|
})
|
|
|
|
|
|
@router.get("/{project_slug}", response_class=HTMLResponse)
|
|
async def project_detail(request: Request, project_slug: str):
|
|
user = get_current_user(request)
|
|
detail = load_detail("projects", "project", project_slug, user)
|
|
if not detail:
|
|
raise HTTPException(status_code=404, detail="Project not found")
|
|
project = detail["item"]
|
|
|
|
base = site_url(request)
|
|
seo_ctx = base_seo_context(
|
|
request,
|
|
title=project.get("title", "Project"),
|
|
description=project.get("description", "")[:160],
|
|
breadcrumbs=[
|
|
{"name": "Home", "url": "/feed"},
|
|
{"name": "Projects", "url": "/projects"},
|
|
{"name": project.get("title", "Project"), "url": f"/projects/{project['slug'] or project['uid']}"},
|
|
],
|
|
schemas=[website_schema(base), software_application_schema(project, base)],
|
|
)
|
|
return templates.TemplateResponse(request, "project_detail.html", {
|
|
**seo_ctx,
|
|
"request": request,
|
|
"user": user,
|
|
"project": project,
|
|
"author": detail["author"],
|
|
"is_owner": detail["is_owner"],
|
|
"star_count": detail["star_count"],
|
|
"platforms": project.get("platforms", "").split(",") if project.get("platforms") else [],
|
|
"comments": detail["comments"],
|
|
"attachments": detail["attachments"],
|
|
})
|
|
|
|
|
|
@router.post("/delete/{project_slug}")
|
|
async def delete_project(request: Request, project_slug: str):
|
|
user = require_user(request)
|
|
return delete_content_item("projects", "project", user, project_slug, "/projects")
|
|
|
|
|
|
@router.post("/create")
|
|
async def create_project(request: Request, data: Annotated[ProjectForm, Form()]):
|
|
user = require_user(request)
|
|
title = data.title.strip()
|
|
description = data.description.strip()
|
|
|
|
projects = get_table("projects")
|
|
uid = generate_uid()
|
|
project_slug = make_combined_slug(title, uid)
|
|
projects.insert({
|
|
"uid": uid,
|
|
"user_uid": user["uid"],
|
|
"title": title,
|
|
"slug": project_slug,
|
|
"description": description,
|
|
"release_date": data.release_date or None,
|
|
"demo_date": data.demo_date or None,
|
|
"project_type": data.project_type,
|
|
"platforms": data.platforms.strip(),
|
|
"status": data.status,
|
|
"stars": 0,
|
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
})
|
|
|
|
if data.attachment_uids:
|
|
link_attachments(data.attachment_uids, "project", uid)
|
|
|
|
create_mention_notifications(description, user["uid"], f"/projects/{project_slug}")
|
|
logger.info(f"Project {uid} created by {user['username']}")
|
|
return RedirectResponse(url=f"/projects/{project_slug}", status_code=302)
|