import logging
from typing import Annotated
from sqlalchemy import or_
from fastapi import APIRouter, Request, 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, get_user_votes, paginate
from devplacepy.content import load_detail, delete_content_item, create_content_item, detail_context
from devplacepy.templating import templates
from devplacepy.utils import get_current_user, require_user, not_found, XP_PROJECT
from devplacepy.seo import base_seo_context, site_url, website_schema, software_application_schema, list_page_seo
logger = logging.getLogger(__name__)
router = APIRouter()
def get_projects_list(tab: str = "recent", search: str = "", user_uid: str = None, project_type: str = None, before: str = None, viewer: dict = 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"]
total = projects.count(*clauses, **filters)
page, next_cursor = paginate(projects, *clauses, before=before, order=order, **filters)
if page:
users_map = get_users_by_uids([p["user_uid"] for p in page])
my_votes = get_user_votes(viewer["uid"], [p["uid"] for p in page]) if viewer else {}
for p in page:
author = users_map.get(p["user_uid"])
p["author_name"] = author["username"] if author else "Unknown"
p["my_vote"] = my_votes.get(p["uid"], 0)
return page, next_cursor, total
@router.get("", response_class=HTMLResponse)
async def projects_page(
request: Request,
tab: str = "recent",
search: str = "",
user_uid: str = None,
project_type: str = None,
before: str = None,
):
user = get_current_user(request)
projects, next_cursor, total_count = get_projects_list(tab, search, user_uid, project_type, before, viewer=user)
total_members = get_site_stats()["total_members"]
seo_ctx = list_page_seo(
request,
title="Projects",
description=f"Explore {total_count} developer projects on DevPlace. Games, software, mobile apps and more.",
breadcrumbs=[
{"name": "Home", "url": "/feed"},
{"name": "Projects", "url": "/projects"},
],
)
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": total_count,
"next_cursor": next_cursor,
"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 not_found("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", detail_context(request, user, detail, "project", seo_ctx, {
"platforms": project.get("platforms", "").split(",") if project.get("platforms") else [],
}))
@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()
uid, project_slug = create_content_item("projects", "project", user, {
"title": title,
"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,
}, title, XP_PROJECT, "First Project", description, data.attachment_uids)
return RedirectResponse(url=f"/projects/{project_slug}", status_code=302)