feat: scaffold initial DevPlace project with FastAPI, SQLite auth, and SSR routing

Add complete project skeleton including .gitignore, Makefile, AGENTS.md, config, database init with indexes, main app with router mounting for auth/feed/posts/comments/projects/profile/messages/notifications/votes, Pydantic models for signup/login/post/comment/project/message/profile, and auth router with signup/login page rendering and form handling.
This commit is contained in:
2026-05-10 07:08:12 +00:00
commit 15a3b990fe
54 changed files with 5906 additions and 0 deletions
+158
View File
@@ -0,0 +1,158 @@
import os, sys, tempfile, subprocess, time
from pathlib import Path
import pytest
PROJECT_ROOT = Path(__file__).resolve().parent.parent
BASE_URL = "http://127.0.0.1:10501"
SCREENSHOT_DIR = Path("/tmp/devplace_test_screenshots")
def save_failure_screenshot(page, test_name):
SCREENSHOT_DIR.mkdir(parents=True, exist_ok=True)
try:
if page and not page.is_closed():
path = str(SCREENSHOT_DIR / f"{test_name}.png")
page.screenshot(path=path, full_page=True)
except Exception:
pass
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
report = outcome.get_result()
if report.when == "call" and report.failed:
for name in ("page", "alice", "bob"):
if name in item.funcargs:
try:
obj = item.funcargs[name]
if name in ("alice", "bob"):
obj = obj[0]
save_failure_screenshot(obj, item.nodeid.replace("::", "_").replace("/", "_"))
except Exception:
pass
@pytest.fixture(scope="session")
def test_db_path():
tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
tmp.close()
yield tmp.name
try:
os.unlink(tmp.name)
except OSError:
pass
@pytest.fixture(scope="session")
def app_server(test_db_path):
env = os.environ.copy()
env["DEVPLACE_DATABASE_URL"] = f"sqlite:///{test_db_path}"
env["SECRET_KEY"] = "test-secret-key"
proc = subprocess.Popen(
[sys.executable, "-m", "uvicorn", "devplacepy.main:app",
"--host", "127.0.0.1", "--port", "10501"],
cwd=str(PROJECT_ROOT), env=env,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
deadline = time.time() + 20
while time.time() < deadline:
try:
import urllib.request
urllib.request.urlopen(f"{BASE_URL}/", timeout=2)
break
except Exception:
time.sleep(0.5)
else:
proc.terminate(); proc.wait()
raise RuntimeError("Server did not start")
yield proc
proc.terminate()
proc.wait(timeout=10)
@pytest.fixture(scope="session")
def playwright_instance():
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
yield p
@pytest.fixture(scope="session")
def browser(playwright_instance):
headless = os.environ.get("PLAYWRIGHT_HEADLESS", "1") == "1"
b = playwright_instance.chromium.launch(
headless=headless, slow_mo=300, args=["--window-size=1400,900"],
)
yield b
b.close()
@pytest.fixture
def browser_context(browser):
ctx = browser.new_context(viewport={"width": 1400, "height": 900})
yield ctx
ctx.close()
@pytest.fixture
def page(browser_context):
p = browser_context.new_page()
p.set_default_timeout(10000)
yield p
p.close()
def signup_user(page, user):
page.goto(f"{BASE_URL}/auth/signup")
page.fill("#username", user["username"])
page.fill("#email", user["email"])
page.fill("#password", user["password"])
page.fill("#confirm_password", user["password"])
page.click("button:has-text('Create account')")
page.wait_for_url("**/feed", timeout=10000)
def login_user(page, user):
page.goto(f"{BASE_URL}/auth/login")
page.fill("#email", user["email"])
page.fill("#password", user["password"])
page.click("button:has-text('Sign in')")
page.wait_for_url("**/feed", timeout=10000)
@pytest.fixture(scope="session")
def seeded_db(app_server):
"""Create test users once at session level using requests directly."""
import urllib.request
import urllib.parse
users = [
("alice_test", "alice@test.devplace", "secret123"),
("bob_test", "bob@test.devplace", "secret456"),
]
for username, email, pw in users:
data = urllib.parse.urlencode({
"username": username, "email": email,
"password": pw, "confirm_password": pw,
}).encode()
req = urllib.request.Request(
f"{BASE_URL}/auth/signup",
data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
urllib.request.urlopen(req)
return {
"alice": {"username": "alice_test", "email": "alice@test.devplace", "password": "secret123"},
"bob": {"username": "bob_test", "email": "bob@test.devplace", "password": "secret456"},
}
@pytest.fixture
def alice(page, seeded_db):
login_user(page, seeded_db["alice"])
return page, seeded_db["alice"]
@pytest.fixture
def bob(browser, seeded_db):
ctx = browser.new_context(viewport={"width": 1400, "height": 900})
p = ctx.new_page()
p.set_default_timeout(10000)
login_user(p, seeded_db["bob"])
yield p, seeded_db["bob"]
p.close()
ctx.close()