forked from retoor/devplacepy
Bsic version.
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import RedirectResponse, HTMLResponse
|
||||
from devplacepy.database import get_table
|
||||
from devplacepy.templating import templates
|
||||
from devplacepy.utils import hash_password, verify_password, create_session, generate_uid, get_current_user
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/signup", response_class=HTMLResponse)
|
||||
async def signup_page(request: Request):
|
||||
user = get_current_user(request)
|
||||
if user:
|
||||
return RedirectResponse(url="/feed", status_code=302)
|
||||
return templates.TemplateResponse("signup.html", {"request": request})
|
||||
|
||||
|
||||
@router.get("/login", response_class=HTMLResponse)
|
||||
async def login_page(request: Request):
|
||||
user = get_current_user(request)
|
||||
if user:
|
||||
return RedirectResponse(url="/feed", status_code=302)
|
||||
return templates.TemplateResponse("login.html", {"request": request})
|
||||
|
||||
|
||||
@router.post("/signup")
|
||||
async def signup(request: Request):
|
||||
form = await request.form()
|
||||
username = form.get("username", "").strip()
|
||||
email = form.get("email", "").strip().lower()
|
||||
password = form.get("password", "")
|
||||
confirm_password = form.get("confirm_password", "")
|
||||
|
||||
errors = []
|
||||
if len(username) < 3 or len(username) > 32:
|
||||
errors.append("Username must be between 3 and 32 characters")
|
||||
if not username.isascii() or not all(c.isalnum() or c in ("-", "_") for c in username):
|
||||
errors.append("Username can only contain letters, numbers, hyphens, and underscores")
|
||||
if not email or "@" not in email or len(email) > 255:
|
||||
errors.append("Valid email is required")
|
||||
if len(password) < 6:
|
||||
errors.append("Password must be at least 6 characters")
|
||||
if password != confirm_password:
|
||||
errors.append("Passwords do not match")
|
||||
|
||||
users = get_table("users")
|
||||
if users.find_one(username=username):
|
||||
errors.append("Username already taken")
|
||||
if users.find_one(email=email):
|
||||
errors.append("Email already registered")
|
||||
|
||||
if errors:
|
||||
return templates.TemplateResponse(
|
||||
"signup.html",
|
||||
{"request": request, "errors": errors, "username": username, "email": email},
|
||||
)
|
||||
|
||||
uid = generate_uid()
|
||||
users.insert({
|
||||
"uid": uid,
|
||||
"username": username,
|
||||
"email": email,
|
||||
"password_hash": hash_password(password),
|
||||
"bio": "",
|
||||
"location": "",
|
||||
"git_link": "",
|
||||
"website": "",
|
||||
"avatar": "",
|
||||
"role": "Member",
|
||||
"level": 1,
|
||||
"xp": 0,
|
||||
"stars": 0,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
})
|
||||
|
||||
token = create_session(uid)
|
||||
response = RedirectResponse(url="/feed", status_code=302)
|
||||
response.set_cookie(key="session", value=token, max_age=86400 * 7, httponly=True)
|
||||
logger.info(f"User {username} signed up")
|
||||
return response
|
||||
|
||||
|
||||
@router.post("/login")
|
||||
async def login(request: Request):
|
||||
form = await request.form()
|
||||
email = form.get("email", "").strip().lower()
|
||||
password = form.get("password", "")
|
||||
remember_me = form.get("remember_me") == "on"
|
||||
|
||||
errors = []
|
||||
if not email or not password:
|
||||
errors.append("Email and password are required")
|
||||
|
||||
users = get_table("users")
|
||||
user = users.find_one(email=email) if not errors else None
|
||||
|
||||
if not user or not verify_password(password, user["password_hash"]):
|
||||
errors.append("Invalid email or password")
|
||||
|
||||
if errors:
|
||||
return templates.TemplateResponse(
|
||||
"login.html",
|
||||
{"request": request, "errors": errors, "email": email},
|
||||
)
|
||||
|
||||
token = create_session(user["uid"])
|
||||
max_age = 86400 * 30 if remember_me else 86400 * 7
|
||||
response = RedirectResponse(url="/feed", status_code=302)
|
||||
response.set_cookie(key="session", value=token, max_age=max_age, httponly=True)
|
||||
logger.info(f"User {user['username']} logged in")
|
||||
return response
|
||||
|
||||
|
||||
@router.get("/logout")
|
||||
async def logout(request: Request):
|
||||
token = request.cookies.get("session")
|
||||
if token:
|
||||
sessions = get_table("sessions")
|
||||
session = sessions.find_one(session_token=token)
|
||||
if session:
|
||||
sessions.delete(id=session["id"])
|
||||
response = RedirectResponse(url="/", status_code=302)
|
||||
response.delete_cookie("session")
|
||||
return response
|
||||
@@ -0,0 +1,60 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import RedirectResponse
|
||||
from devplacepy.database import get_table
|
||||
from devplacepy.utils import generate_uid, require_user
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/create")
|
||||
async def create_comment(request: Request):
|
||||
user = require_user(request)
|
||||
form = await request.form()
|
||||
content = form.get("content", "").strip()
|
||||
post_uid = form.get("post_uid", "")
|
||||
parent_uid = form.get("parent_uid", "")
|
||||
|
||||
if not content or not post_uid:
|
||||
return RedirectResponse(url="/feed", status_code=302)
|
||||
if len(content) > 1000:
|
||||
return RedirectResponse(url=f"/posts/{post_uid}", status_code=302)
|
||||
|
||||
comments = get_table("comments")
|
||||
comments.insert({
|
||||
"uid": generate_uid(),
|
||||
"post_uid": post_uid,
|
||||
"user_uid": user["uid"],
|
||||
"content": content,
|
||||
"parent_uid": parent_uid or None,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
})
|
||||
|
||||
posts = get_table("posts")
|
||||
post = posts.find_one(uid=post_uid)
|
||||
if post and post["user_uid"] != user["uid"]:
|
||||
notifications = get_table("notifications")
|
||||
notifications.insert({
|
||||
"uid": generate_uid(),
|
||||
"user_uid": post["user_uid"],
|
||||
"type": "comment",
|
||||
"message": f"{user['username']} commented on your post",
|
||||
"related_uid": post_uid,
|
||||
"read": False,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
})
|
||||
|
||||
logger.info(f"Comment by {user['username']} on post {post_uid}")
|
||||
return RedirectResponse(url=f"/posts/{post_uid}", status_code=302)
|
||||
|
||||
|
||||
@router.post("/delete/{comment_uid}")
|
||||
async def delete_comment(request: Request, comment_uid: str):
|
||||
user = require_user(request)
|
||||
comments = get_table("comments")
|
||||
comment = comments.find_one(uid=comment_uid)
|
||||
if comment and comment["user_uid"] == user["uid"]:
|
||||
comments.delete(id=comment["id"])
|
||||
return RedirectResponse(url=f"/posts/{comment['post_uid']}", status_code=302)
|
||||
@@ -0,0 +1,76 @@
|
||||
import logging
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from devplacepy.database import get_table
|
||||
from devplacepy.templating import templates
|
||||
from devplacepy.utils import get_current_user, require_user, time_ago
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def get_feed_posts(user, tab: str = "all", topic: str = None):
|
||||
posts_table = get_table("posts")
|
||||
users_table = get_table("users")
|
||||
|
||||
query = "1=1"
|
||||
params = {}
|
||||
|
||||
if topic:
|
||||
query += " AND topic = :topic"
|
||||
params["topic"] = topic
|
||||
|
||||
if tab == "following":
|
||||
follows = get_table("follows")
|
||||
following = [f["following_uid"] for f in follows.find(follower_uid=user["uid"])]
|
||||
if following:
|
||||
placeholders = ",".join(f":uid_{i}" for i in range(len(following)))
|
||||
query += f" AND user_uid IN ({placeholders})"
|
||||
for i, uid in enumerate(following):
|
||||
params[f"uid_{i}"] = uid
|
||||
else:
|
||||
return []
|
||||
|
||||
order = "created_at DESC"
|
||||
if tab == "trending":
|
||||
order = "stars DESC, created_at DESC"
|
||||
|
||||
posts = list(posts_table.find(**params, _limit=50))
|
||||
posts.sort(key=lambda p: p.get("created_at", ""), reverse=True)
|
||||
if tab == "trending":
|
||||
posts.sort(key=lambda p: int(p.get("stars", 0)), reverse=True)
|
||||
|
||||
result = []
|
||||
for post in posts:
|
||||
author = users_table.find_one(uid=post["user_uid"])
|
||||
comment_count = len(list(get_table("comments").find(post_uid=post["uid"])))
|
||||
result.append({
|
||||
"post": post,
|
||||
"author": author,
|
||||
"time_ago": time_ago(post["created_at"]),
|
||||
"comment_count": comment_count,
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
@router.get("", response_class=HTMLResponse)
|
||||
async def feed_page(request: Request, tab: str = "all", topic: str = None):
|
||||
user = require_user(request)
|
||||
posts = get_feed_posts(user, tab, topic)
|
||||
users_table = get_table("users")
|
||||
total_members = len(list(users_table.all()))
|
||||
posts_today = len(list(get_table("posts").find()))
|
||||
total_projects = len(list(get_table("projects").all()))
|
||||
top_authors = list(users_table.find(order_by=["-stars"], _limit=5))
|
||||
|
||||
return templates.TemplateResponse("feed.html", {
|
||||
"request": request,
|
||||
"user": user,
|
||||
"posts": posts,
|
||||
"current_tab": tab,
|
||||
"current_topic": topic,
|
||||
"total_members": total_members,
|
||||
"posts_today": posts_today,
|
||||
"total_projects": total_projects,
|
||||
"top_authors": top_authors,
|
||||
})
|
||||
@@ -0,0 +1,128 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from devplacepy.database import get_table
|
||||
from devplacepy.templating import templates
|
||||
from devplacepy.utils import generate_uid, get_current_user, require_user, time_ago
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def get_conversations(user_uid: str):
|
||||
messages_table = get_table("messages")
|
||||
users_table = get_table("users")
|
||||
all_messages = list(messages_table.find(
|
||||
"sender_uid = :uid OR receiver_uid = :uid",
|
||||
{"uid": user_uid},
|
||||
))
|
||||
|
||||
conversation_map = {}
|
||||
for msg in all_messages:
|
||||
other_uid = msg["receiver_uid"] if msg["sender_uid"] == user_uid else msg["sender_uid"]
|
||||
if other_uid not in conversation_map or msg["created_at"] > conversation_map[other_uid]["last_message_at"]:
|
||||
other_user = users_table.find_one(uid=other_uid)
|
||||
conversation_map[other_uid] = {
|
||||
"other_user": other_user,
|
||||
"last_message": msg["content"],
|
||||
"last_message_at": msg["created_at"],
|
||||
"unread": msg["receiver_uid"] == user_uid and not msg["read"],
|
||||
}
|
||||
|
||||
conversations = sorted(
|
||||
conversation_map.values(),
|
||||
key=lambda c: c["last_message_at"],
|
||||
reverse=True,
|
||||
)
|
||||
return conversations
|
||||
|
||||
|
||||
def get_conversation_messages(user_uid: str, other_uid: str):
|
||||
messages_table = get_table("messages")
|
||||
msgs = list(messages_table.find(
|
||||
"(sender_uid = :me AND receiver_uid = :other) OR (sender_uid = :other AND receiver_uid = :me)",
|
||||
{"me": user_uid, "other": other_uid},
|
||||
))
|
||||
msgs.sort(key=lambda m: m["created_at"])
|
||||
|
||||
for msg in msgs:
|
||||
if msg["receiver_uid"] == user_uid and not msg["read"]:
|
||||
messages_table.update({"id": msg["id"], "read": True}, ["id"])
|
||||
|
||||
users_table = get_table("users")
|
||||
other_user = users_table.find_one(uid=other_uid)
|
||||
|
||||
result = []
|
||||
for m in msgs:
|
||||
sender = users_table.find_one(uid=m["sender_uid"])
|
||||
result.append({
|
||||
"message": m,
|
||||
"sender": sender,
|
||||
"is_mine": m["sender_uid"] == user_uid,
|
||||
"time_ago": time_ago(m["created_at"]),
|
||||
})
|
||||
return result, other_user
|
||||
|
||||
|
||||
@router.get("", response_class=HTMLResponse)
|
||||
async def messages_page(request: Request, with_uid: str = None, search: str = ""):
|
||||
user = require_user(request)
|
||||
conversations = get_conversations(user["uid"])
|
||||
|
||||
if search:
|
||||
users_table = get_table("users")
|
||||
found = users_table.find_one(username=search)
|
||||
if found:
|
||||
with_uid = found["uid"]
|
||||
|
||||
current_conversation = None
|
||||
messages = []
|
||||
other_user = None
|
||||
if with_uid:
|
||||
messages, other_user = get_conversation_messages(user["uid"], with_uid)
|
||||
current_conversation = with_uid
|
||||
|
||||
return templates.TemplateResponse("messages.html", {
|
||||
"request": request,
|
||||
"user": user,
|
||||
"conversations": conversations,
|
||||
"messages": messages,
|
||||
"other_user": other_user,
|
||||
"current_conversation": current_conversation,
|
||||
"search": search,
|
||||
})
|
||||
|
||||
|
||||
@router.post("/send")
|
||||
async def send_message(request: Request):
|
||||
user = require_user(request)
|
||||
form = await request.form()
|
||||
content = form.get("content", "").strip()
|
||||
receiver_uid = form.get("receiver_uid", "")
|
||||
|
||||
if content and receiver_uid:
|
||||
messages_table = get_table("messages")
|
||||
messages_table.insert({
|
||||
"uid": generate_uid(),
|
||||
"sender_uid": user["uid"],
|
||||
"receiver_uid": receiver_uid,
|
||||
"content": content,
|
||||
"read": False,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
})
|
||||
|
||||
notifications = get_table("notifications")
|
||||
notifications.insert({
|
||||
"uid": generate_uid(),
|
||||
"user_uid": receiver_uid,
|
||||
"type": "message",
|
||||
"message": f"{user['username']} sent you a message",
|
||||
"related_uid": user["uid"],
|
||||
"read": False,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
})
|
||||
|
||||
logger.info(f"Message from {user['username']} to {receiver_uid}")
|
||||
|
||||
return RedirectResponse(url=f"/messages?with_uid={receiver_uid}", status_code=302)
|
||||
@@ -0,0 +1,53 @@
|
||||
import logging
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from devplacepy.database import get_table
|
||||
from devplacepy.templating import templates
|
||||
from devplacepy.utils import get_current_user, require_user, time_ago
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("", response_class=HTMLResponse)
|
||||
async def notifications_page(request: Request):
|
||||
user = require_user(request)
|
||||
notifications_table = get_table("notifications")
|
||||
raw_notifications = list(
|
||||
notifications_table.find(user_uid=user["uid"], order_by=["-created_at"])
|
||||
)
|
||||
|
||||
users_table = get_table("users")
|
||||
notifications = []
|
||||
for n in raw_notifications:
|
||||
actor = users_table.find_one(uid=n.get("related_uid")) if n.get("related_uid") else None
|
||||
notifications.append({
|
||||
"notification": n,
|
||||
"actor": actor,
|
||||
"time_ago": time_ago(n["created_at"]),
|
||||
})
|
||||
|
||||
return templates.TemplateResponse("notifications.html", {
|
||||
"request": request,
|
||||
"user": user,
|
||||
"notifications": notifications,
|
||||
})
|
||||
|
||||
|
||||
@router.post("/mark-read/{notification_uid}")
|
||||
async def mark_read(request: Request, notification_uid: str):
|
||||
user = require_user(request)
|
||||
notifications_table = get_table("notifications")
|
||||
n = notifications_table.find_one(uid=notification_uid)
|
||||
if n and n["user_uid"] == user["uid"]:
|
||||
notifications_table.update({"id": n["id"], "read": True}, ["id"])
|
||||
return RedirectResponse(url="/notifications", status_code=302)
|
||||
|
||||
|
||||
@router.post("/mark-all-read")
|
||||
async def mark_all_read(request: Request):
|
||||
user = require_user(request)
|
||||
notifications_table = get_table("notifications")
|
||||
for n in notifications_table.find(user_uid=user["uid"], read=False):
|
||||
notifications_table.update({"id": n["id"], "read": True}, ["id"])
|
||||
return RedirectResponse(url="/notifications", status_code=302)
|
||||
@@ -0,0 +1,84 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Request, HTTPException, status
|
||||
from fastapi.responses import RedirectResponse, HTMLResponse
|
||||
from devplacepy.database import get_table
|
||||
from devplacepy.templating import templates
|
||||
from devplacepy.utils import generate_uid, get_current_user, require_user, time_ago
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/create")
|
||||
async def create_post(request: Request):
|
||||
user = require_user(request)
|
||||
form = await request.form()
|
||||
content = form.get("content", "").strip()
|
||||
title = form.get("title", "").strip()
|
||||
topic = form.get("topic", "random")
|
||||
project_uid = form.get("project_uid", "")
|
||||
|
||||
errors = []
|
||||
if not content:
|
||||
errors.append("Content is required")
|
||||
if len(content) > 2000:
|
||||
errors.append("Content too long (max 2000 characters)")
|
||||
if topic not in ("devlog", "showcase", "question", "rant", "fun", "random"):
|
||||
topic = "random"
|
||||
|
||||
if errors:
|
||||
return RedirectResponse(url="/feed", status_code=302)
|
||||
|
||||
posts = get_table("posts")
|
||||
uid = generate_uid()
|
||||
posts.insert({
|
||||
"uid": uid,
|
||||
"user_uid": user["uid"],
|
||||
"title": title or None,
|
||||
"content": content,
|
||||
"topic": topic,
|
||||
"project_uid": project_uid or None,
|
||||
"image": None,
|
||||
"stars": 0,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
})
|
||||
|
||||
logger.info(f"Post {uid} created by {user['username']}")
|
||||
return RedirectResponse(url=f"/posts/{uid}", status_code=302)
|
||||
|
||||
|
||||
@router.get("/{post_uid}", response_class=HTMLResponse)
|
||||
async def view_post(request: Request, post_uid: str):
|
||||
user = get_current_user(request)
|
||||
posts = get_table("posts")
|
||||
post = posts.find_one(uid=post_uid)
|
||||
if not post:
|
||||
raise HTTPException(status_code=404, detail="Post not found")
|
||||
|
||||
users_table = get_table("users")
|
||||
author = users_table.find_one(uid=post["user_uid"])
|
||||
|
||||
comments_table = get_table("comments")
|
||||
raw_comments = list(comments_table.find(post_uid=post_uid, order_by=["created_at"]))
|
||||
comments = []
|
||||
for c in raw_comments:
|
||||
commenter = users_table.find_one(uid=c["user_uid"])
|
||||
comments.append({
|
||||
"comment": c,
|
||||
"author": commenter,
|
||||
"time_ago": time_ago(c["created_at"]),
|
||||
"votes": {
|
||||
"up": len(list(get_table("votes").find(target_uid=c["uid"], value=1))),
|
||||
"down": len(list(get_table("votes").find(target_uid=c["uid"], value=-1))),
|
||||
},
|
||||
})
|
||||
|
||||
return templates.TemplateResponse("post.html", {
|
||||
"request": request,
|
||||
"user": user,
|
||||
"post": post,
|
||||
"author": author,
|
||||
"comments": comments,
|
||||
"time_ago": time_ago(post["created_at"]),
|
||||
})
|
||||
@@ -0,0 +1,66 @@
|
||||
import logging
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from devplacepy.database import get_table
|
||||
from devplacepy.templating import templates
|
||||
from devplacepy.utils import get_current_user, require_user, time_ago
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/{username}", response_class=HTMLResponse)
|
||||
async def profile_page(request: Request, username: str, tab: str = "posts"):
|
||||
current_user = get_current_user(request)
|
||||
users = get_table("users")
|
||||
profile_user = users.find_one(username=username)
|
||||
if not profile_user:
|
||||
return RedirectResponse(url="/feed", status_code=302)
|
||||
|
||||
posts = []
|
||||
if tab == "posts":
|
||||
posts_table = get_table("posts")
|
||||
raw_posts = list(posts_table.find(user_uid=profile_user["uid"]))
|
||||
for p in raw_posts:
|
||||
comments_count = len(list(get_table("comments").find(post_uid=p["uid"])))
|
||||
posts.append({
|
||||
"post": p,
|
||||
"time_ago": time_ago(p["created_at"]),
|
||||
"comment_count": comments_count,
|
||||
})
|
||||
posts.sort(key=lambda x: x["post"]["created_at"], reverse=True)
|
||||
|
||||
badges = list(get_table("badges").find(user_uid=profile_user["uid"]))
|
||||
projects = list(get_table("projects").find(user_uid=profile_user["uid"]))
|
||||
|
||||
return templates.TemplateResponse("profile.html", {
|
||||
"request": request,
|
||||
"user": current_user,
|
||||
"profile_user": profile_user,
|
||||
"posts": posts,
|
||||
"badges": badges,
|
||||
"projects": projects,
|
||||
"current_tab": tab,
|
||||
})
|
||||
|
||||
|
||||
@router.post("/update")
|
||||
async def update_profile(request: Request):
|
||||
user = require_user(request)
|
||||
form = await request.form()
|
||||
bio = form.get("bio", "").strip()
|
||||
location = form.get("location", "").strip()
|
||||
git_link = form.get("git_link", "").strip()
|
||||
website = form.get("website", "").strip()
|
||||
|
||||
users = get_table("users")
|
||||
users.update({
|
||||
"uid": user["uid"],
|
||||
"bio": bio,
|
||||
"location": location,
|
||||
"git_link": git_link,
|
||||
"website": website,
|
||||
}, ["uid"])
|
||||
|
||||
logger.info(f"Profile updated for {user['username']}")
|
||||
return RedirectResponse(url=f"/profile/{user['username']}", status_code=302)
|
||||
@@ -0,0 +1,100 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from devplacepy.database import get_table
|
||||
from devplacepy.templating import templates
|
||||
from devplacepy.utils import generate_uid, get_current_user, require_user
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def get_projects_list(tab: str = "recent", search: str = "", user_uid: str = None):
|
||||
projects = get_table("projects")
|
||||
users = get_table("users")
|
||||
all_projects = list(projects.all())
|
||||
|
||||
if user_uid:
|
||||
all_projects = [p for p in all_projects if p["user_uid"] == user_uid]
|
||||
|
||||
if search:
|
||||
search_lower = search.lower()
|
||||
all_projects = [
|
||||
p for p in all_projects
|
||||
if search_lower in p.get("title", "").lower()
|
||||
or search_lower in p.get("description", "").lower()
|
||||
]
|
||||
|
||||
for p in all_projects:
|
||||
author = users.find_one(uid=p["user_uid"])
|
||||
p["author_name"] = author["username"] if author else "Unknown"
|
||||
|
||||
if tab == "released":
|
||||
all_projects = [p for p in all_projects if p.get("status") == "Released"]
|
||||
elif tab == "popular":
|
||||
all_projects.sort(key=lambda p: int(p.get("stars", 0)), reverse=True)
|
||||
elif tab == "new":
|
||||
all_projects.sort(key=lambda p: p.get("created_at", ""), reverse=True)
|
||||
else:
|
||||
all_projects.sort(key=lambda p: p.get("created_at", ""), reverse=True)
|
||||
|
||||
return all_projects
|
||||
|
||||
|
||||
@router.get("", response_class=HTMLResponse)
|
||||
async def projects_page(
|
||||
request: Request,
|
||||
tab: str = "recent",
|
||||
search: str = "",
|
||||
user_uid: str = None,
|
||||
):
|
||||
user = get_current_user(request)
|
||||
projects = get_projects_list(tab, search, user_uid)
|
||||
users = get_table("users")
|
||||
total_members = len(list(users.all()))
|
||||
|
||||
return templates.TemplateResponse("projects.html", {
|
||||
"request": request,
|
||||
"user": user,
|
||||
"projects": projects,
|
||||
"current_tab": tab,
|
||||
"search": search,
|
||||
"total_count": len(projects),
|
||||
"total_members": total_members,
|
||||
})
|
||||
|
||||
|
||||
@router.post("/create")
|
||||
async def create_project(request: Request):
|
||||
user = require_user(request)
|
||||
form = await request.form()
|
||||
title = form.get("title", "").strip()
|
||||
description = form.get("description", "").strip()
|
||||
release_date = form.get("release_date", "")
|
||||
demo_date = form.get("demo_date", "")
|
||||
project_type = form.get("project_type", "software")
|
||||
platforms = form.get("platforms", "").strip()
|
||||
status = form.get("status", "In Development")
|
||||
|
||||
if not title or not description:
|
||||
return RedirectResponse(url="/projects", status_code=302)
|
||||
|
||||
projects = get_table("projects")
|
||||
uid = generate_uid()
|
||||
projects.insert({
|
||||
"uid": uid,
|
||||
"user_uid": user["uid"],
|
||||
"title": title,
|
||||
"description": description,
|
||||
"release_date": release_date or None,
|
||||
"demo_date": demo_date or None,
|
||||
"project_type": project_type,
|
||||
"platforms": platforms,
|
||||
"status": status,
|
||||
"stars": 0,
|
||||
"created_at": datetime.utcnow().isoformat(),
|
||||
})
|
||||
|
||||
logger.info(f"Project {uid} created by {user['username']}")
|
||||
return RedirectResponse(url="/projects", status_code=302)
|
||||
@@ -0,0 +1,46 @@
|
||||
import logging
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import RedirectResponse
|
||||
from devplacepy.database import get_table
|
||||
from devplacepy.utils import generate_uid, require_user
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/{target_type}/{target_uid}")
|
||||
async def vote(request: Request, target_type: str, target_uid: str):
|
||||
user = require_user(request)
|
||||
form = await request.form()
|
||||
value = int(form.get("value", "1"))
|
||||
|
||||
if value not in (1, -1):
|
||||
return RedirectResponse(url="/feed", status_code=302)
|
||||
|
||||
votes = get_table("votes")
|
||||
existing = votes.find_one(user_uid=user["uid"], target_uid=target_uid, target_type=target_type)
|
||||
|
||||
if existing:
|
||||
if int(existing["value"]) == value:
|
||||
votes.delete(id=existing["id"])
|
||||
else:
|
||||
votes.update({"id": existing["id"], "value": value}, ["id"])
|
||||
else:
|
||||
votes.insert({
|
||||
"uid": generate_uid(),
|
||||
"user_uid": user["uid"],
|
||||
"target_uid": target_uid,
|
||||
"target_type": target_type,
|
||||
"value": value,
|
||||
"created_at": __import__("datetime").datetime.utcnow().isoformat(),
|
||||
})
|
||||
|
||||
up = len(list(votes.find(target_uid=target_uid, value=1)))
|
||||
down = len(list(votes.find(target_uid=target_uid, value=-1)))
|
||||
|
||||
if target_type == "post":
|
||||
posts = get_table("posts")
|
||||
posts.update({"uid": target_uid, "stars": up - down}, ["uid"])
|
||||
|
||||
referer = request.headers.get("Referer", "/feed")
|
||||
return RedirectResponse(url=referer, status_code=302)
|
||||
Reference in New Issue
Block a user