Per-project access control + UI/feedback/admin refinements

Access control:
- project_members table; non-admins only see/operate on assigned
  projects (enforced across projects, SOPs, work packages — 403 else),
  admins bypass. Creating a project auto-grants its creator access.
- Admin API to get/set a user's project assignments, plus a checkbox
  assignment dialog in the Admin Console user list.

UI / workflow:
- Login page: drop the "Prime Controls" wordmark next to the logo.
- SOP tool: remove emoji icons from buttons and nav tabs.
- Rename "Step Comments" to "Feedback"; the author auto-populates
  (read-only) from the signed-in user.
- Move usage-log viewing to the Admin Console; add an admin card that
  lists all feedback/comments (who, what, page + step, when).
- Sample project name -> "Micron FMCS Install (sample)".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-29 16:38:56 -07:00
parent bdb798efdd
commit 5ad3ffa58e
8 changed files with 322 additions and 38 deletions

View File

@@ -12,7 +12,7 @@ can upsert without round-tripping a sequence.
"""
from datetime import datetime, timezone
from typing import Optional
from sqlalchemy import String, Boolean, Integer, DateTime, ForeignKey, Text, JSON
from sqlalchemy import String, Boolean, Integer, DateTime, ForeignKey, Text, JSON, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column
from .db import Base
@@ -137,6 +137,23 @@ class User(Base):
}
class ProjectMember(Base):
"""Which users may access which projects. A user sees/operates on a project
only if a row links them to it (admins bypass this entirely). One row per
(user, project) pair."""
__tablename__ = "project_members"
__table_args__ = (UniqueConstraint("user_id", "project_id", name="uq_project_member"),)
id: Mapped[str] = mapped_column(String(40), primary_key=True)
user_id: Mapped[str] = mapped_column(
String(40), ForeignKey("users.id", ondelete="CASCADE"), index=True
)
project_id: Mapped[str] = mapped_column(
String(40), ForeignKey("projects.id", ondelete="CASCADE"), index=True
)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
class Comment(Base):
__tablename__ = "comments"