Default per-user main.py; invite-only by default

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-10 01:45:20 +12:00
parent 6fc651ad72
commit 687a8347f8
7 changed files with 97 additions and 14 deletions

View File

@@ -1,7 +1,6 @@
from __future__ import annotations
import os
import re
from pathlib import Path
from fastapi import Cookie, Depends, Header, HTTPException, Query
@@ -11,6 +10,7 @@ from editor_app.db.session import get_db
from editor_app.db.models import User
from editor_app import config
from editor_app.services import accounts
from editor_app.services import user_workspace
def api_key_valid(authorization: str | None) -> bool:
@@ -66,13 +66,8 @@ async def require_superuser(
return user
def _safe_workspace_leaf(user: User) -> str:
base = re.sub(r"[^a-zA-Z0-9._-]+", "-", user.username).strip("-").lower() or "user"
return f"{base}-{user.id}"
def _seed_user_workspace(user_root: Path) -> None:
(user_root / "code").mkdir(parents=True, exist_ok=True)
user_workspace.ensure_default_code_main(user_root)
async def get_workspace_root(
@@ -91,6 +86,6 @@ async def get_workspace_root(
if lookup is None:
raise HTTPException(status_code=404, detail="Workspace user not found")
target_user = lookup
user_root = root / "users" / _safe_workspace_leaf(target_user)
user_root = user_workspace.user_workspace_root(target_user.id, target_user.username, workspace_root=root)
_seed_user_workspace(user_root)
return user_root

View File

@@ -12,6 +12,7 @@ from sqlalchemy import func, select
from sqlalchemy.orm import Session
from editor_app.db.models import AuthSession, InviteToken, User
from editor_app.services import user_workspace
if TYPE_CHECKING:
pass
@@ -67,6 +68,10 @@ def create_user(db: Session, username: str, password: str, *, is_superuser: bool
db.add(user)
db.commit()
db.refresh(user)
if auth_enabled():
user_workspace.ensure_default_code_main(
user_workspace.user_workspace_root(user.id, user.username)
)
return user
@@ -145,7 +150,7 @@ def delete_user(db: Session, user_id: int) -> bool:
def invite_required() -> bool:
return os.environ.get("AUTH_INVITE_ONLY", "false").strip().lower() in ("1", "true", "yes", "on")
return os.environ.get("AUTH_INVITE_ONLY", "true").strip().lower() in ("1", "true", "yes", "on")
def create_invite(db: Session, email: str, invited_by_user_id: int | None = None, expires_days: int = 7) -> InviteToken:

View File

@@ -0,0 +1,27 @@
from __future__ import annotations
import re
from pathlib import Path
from editor_app import config
DEFAULT_MAIN_PY = 'print("Hello, World!")\n'
def safe_workspace_leaf(username: str, user_id: int) -> str:
base = re.sub(r"[^a-zA-Z0-9._-]+", "-", username.strip()).strip("-").lower() or "user"
return f"{base}-{user_id}"
def user_workspace_root(user_id: int, username: str, workspace_root: Path | None = None) -> Path:
root = (workspace_root or config.WORKSPACE_ROOT).resolve()
return root / "users" / safe_workspace_leaf(username, user_id)
def ensure_default_code_main(user_root: Path) -> None:
"""Ensure code/ exists and add a starter main.py when missing."""
code_dir = user_root / "code"
code_dir.mkdir(parents=True, exist_ok=True)
main_py = code_dir / "main.py"
if not main_py.exists():
main_py.write_text(DEFAULT_MAIN_PY, encoding="utf-8")

View File

@@ -89,7 +89,11 @@ class TextEditor {
this.updateWorkspaceBanner();
this.prewarmPyWorker();
this.fetchViewerRole()
.finally(() => this.loadInitialDirectoryState().then(() => this.restoreSessionTabs()));
.finally(() =>
this.loadInitialDirectoryState().then(() =>
this.restoreSessionTabs().then(() => this.ensureDefaultMainOpen())
)
);
}
async fetchViewerRole() {
@@ -308,6 +312,13 @@ class TextEditor {
this.saveSessionState();
}
async ensureDefaultMainOpen() {
if (this.openTabs.length > 0) {
return;
}
await this.openFile('code/main.py');
}
async restoreExplorerState(session) {
const expanded = Array.isArray(session.expandedDirs)
? session.expandedDirs.filter((path) => typeof path === 'string')