Files
automiz_app_full/development/CLAUDE.md
2026-08-28 19:24:04 +05:30

15 KiB
Raw Blame History

TPM CMS — Compact Codebase Reference

Purpose: Load this file at the start of any AI session to understand the full stack without reading source files. Last updated: 2026-06-05


Architecture Overview

Browser (React)  ←→  tpm_cms_backend (Flask :5000)
                         ├── tpm_cms_workflow (Flask :5001)        # email/node workflow engine
                         ├── tpm_cms_asset_manager (Flask :5002)   # S3 file ops
                         └── tpm_asset_manager_app_web_socket      # Node.js WS bridge
                              ├── WebSocket server (:WS_PORT)      # desktop Flutter app connects here
                              └── HTTP bridge (:HTTP_PORT)         # backend POSTs to /send_message
Flutter Desktop App  ←→  WebSocket bridge

All Python services share the same MongoDB database (default database from MONGO_URI). No message queue; services call each other via HTTP.


Services

1. tpm_cms_backend — main API (Flask + JWT)

  • Entry: app.py (~3466 lines, single file)
  • Port: 5000 (dev), proxied via Nginx in prod
  • Auth: JWT (flask_jwt_extended), bcrypt passwords, optional Azure SSO
  • Key env vars: MONGO_URI, JWT_SECRET_KEY, FLASK_SECRET_KEY, AZURE_REDIRECT_URI, WS_BRIDGE_HTTP_URL, DESKTOP_UNIQUE_ID, ASSET_MANAGER_URL

Collections used

Collection Purpose
users auth, roles (admin/pm/lead/user)
clients client companies
projects projects with embedded stages[]
tasks user task assignments with time tracking
brief_templates project brief + workflow templates
sections reusable brief sections
histories project history log
dashboard_configs per-user dashboard widget configs
planxo_auth_keys desktop app auth tokens
azure_config Azure SSO config (single doc)
settings {type:'desktop_sync'}, {type:'folder_sync'}
planxo_approvals desktop access approval records

API routes (all @jwt_required() unless noted)

POST   /register                        # public
POST   /login                           # public; GET returns HTML login page
GET    /me
GET    /health                          # public

# Clients
GET/POST         /clients
GET/PUT/DELETE   /clients/<id>

# Projects
GET/POST         /projects
GET/PUT/DELETE   /projects/<id>         # id = MongoDB ObjectId
GET              /projects/<int:id>     # id = numeric project_id
GET              /projects/search
GET/POST         /projects/<id>/history
GET              /projects/<id>/brief_template
POST             /projects/<id>/brief
GET              /projects/<id>/stages
POST             /projects/<id>/stages/<key>       # set status
POST             /projects/<id>/stages/<key>/start
POST             /projects/<id>/stages/<key>/pause
POST             /projects/<id>/stages/<key>/complete
POST             /projects/<id>/stages/<key>/assign
POST             /projects/<id>/desktop-download   # triggers WS bridge

# Brief Templates / Sections
GET/POST         /brief_templates
GET/PUT/DELETE   /brief_templates/<id>
GET              /clients/<id>/brief_templates
GET/POST         /sections
PUT/DELETE       /sections/<id>

# Tasks
GET/POST         /tasks
POST             /tasks/<id>/assign|start|pause|resume|complete|reject

# Users (admin only for write)
GET/POST         /users
PUT              /users/<id>
POST             /users/<id>/activate|deactivate

# Dashboard
GET              /dashboard-stats
GET/POST         /dashboard-configs
GET/PUT/DELETE   /dashboard-configs/<id>
GET              /dashboard-configs/default
POST             /dashboard-data         # widget data with filters
POST             /dashboard-export       # CSV

# Settings
GET/POST         /settings/desktop-sync
GET/POST         /settings/folder-sync

# PlanXO Desktop Auth
GET              /planxo-auth/<auth_key>            # HTML approval page
POST             /planxo/app/auth/register-key      # public
POST             /api/planxo/app/auth/<key>/approve
POST             /planxo/app/auth/<key>/approve     # alias
POST             /api/planxo-auth/<key>/approve     # alias
GET              /planxo/app/auth/check
POST             /planxo/app/auth/revoke
GET              /planxo/app/auth/keys
POST             /planxo/app/auth/validate-key      # public (called by WS server)
GET              /planxo/folders/filtered           # role-filtered S3 folder list

# Azure SSO
GET              /azure-login-url
POST             /azure-callback
POST             /azure-config  (admin)

# Server admin
GET              /server-status
GET              /server-status/logs/<service>
POST             /server-status/restart/<service>   # admin only
GET/POST         /desktop-bridge/health|clients     # proxy to WS bridge

# Files (GridFS — legacy)
POST             /files/upload
GET              /files/<id>

# Color (prepress)
POST             /compute                           # Pantone→ECG via transicc

Role system

  • role_required('admin') — admin only
  • role_required('pm','admin','lead','user') — most write ops
  • jwt_required() — any authenticated user
  • get_authenticated_user() — reads token from URL param ?token= OR Authorization: Bearer

2. tpm_cms_asset_manager — S3 file service (Flask :5002)

  • Entry: asset_manager_app.py
  • No auth — internal calls only from backend
  • Key env vars: S3_BUCKET, AWS_REGION, MONGO_URI

S3 path convention

clients/{client_name}/{numeric_project_id}/       ← if client exists
projects/{numeric_project_id}/                    ← no client

Folders created by uploading {path}/.keep placeholder.

Routes

POST   /clients/<id>/create-folder
POST   /projects/<id>/create-folder
POST   /folders/create              # generic, validates root prefix
POST   /folders/batch-create        # {base_folder, subfolders:[]}
GET    /assets                      # ?folder=&q=&owner_id=&include_folders=
DELETE /assets/<id>
POST   /assets/presign-upload       # returns S3 presigned URL
POST   /assets/confirm-upload       # save asset record to MongoDB
GET    /assets/<id>/download        # presigned GET URL
POST   /assets/batch-delete
GET    /assets/folder-contents      # flat list with S3 metadata
POST   /zip-download                # zip multiple keys

Assets stored in MongoDB assets collection with s3_key, folder, filename, content_type, size, owner_id, created_at.


3. tpm_cms_workflow — workflow engine (Flask :5001)

  • Entry: workflow_app.py
  • No auth — internal calls only from backend
  • Key env vars: MONGO_URI, AWS_REGION, SENDER_EMAIL, SENDER_NAME
  • Uses ThreadPoolExecutor(max_workers=5) for async node processing

Collections

  • workflows{project_id, nodes:[], edges:[]}
  • workflow_logs — execution log per node

Node types

  • project — pass-through start node
  • sendEmail — sends via AWS SES; resolves {{variable}} placeholders
  • router — conditional branching
  • stage — maps to project stage key
  • approval — waits for human approval action

Routes

POST   /workflow                    # create workflow for project
POST   /workflow/<id>/process       # {node_id, variables:{}} — triggers async execution
GET    /workflow/<id>
GET    /workflow/project/<project_id>
POST   /workflow/<id>/nodes/<node_id>/approve|reject

4. tpm_asset_manager_app_web_socket — WS bridge (Node.js)

  • Entry: server.js
  • Ports: WS_PORT (WebSocket), HTTP_PORT (Express HTTP)
  • Auth: ?apiKey=<key> on WebSocket connect + unique_id per client
  • Stores connected clients in Map<unique_id, ws>

HTTP endpoints (called by backend)

POST   /send_message        # {unique_id, message:{type,data}}  → forward to WS client
POST   /request_download    # {unique_id, folder, projectId}    → trigger download on desktop
GET    /health              # returns {clients:[...unique_ids]}
GET    /clients

WebSocket message types (server→client)

  • auth_approved — desktop auth approved; includes user, expires_at, sync_interval
  • auth_revoked — user logged in from web; desktop must logout
  • download_request — trigger file download

WebSocket message types (client→server)

  • register_auth_key — desktop registers key before browser approval
  • file_sync_complete, file_upload_complete — status callbacks

Data Models (key fields)

Project

{
  "_id": ObjectId,
  "project_id": 1000007,          // numeric, auto-increment from last+1
  "projectName": "string",        // indexed unique per clientId
  "clientId": ObjectId|null,
  "project_name": "string",       // duplicate of projectName (legacy)
  "client_id": "string"|null,     // duplicate of clientId (legacy)
  "brand": "", "product": "", "variant": "",
  "brief": {},
  "template_id": "string",
  "stages": [
    {
      "key": "stage_key",
      "title": "Stage Title",
      "status": "pending|in_progress|paused|completed",
      "time": {"total_seconds": 0},
      "start_time": 1234567890,    // unix timestamp, present only when in_progress
      "assigned_to": "user_id",
      "history": [{"action":"start","timestamp":0,"user_id":""}]
    }
  ],
  "version": 1,
  "created_at": ISODate,
  "created_by": "user_id",
  "project_update_history": [{"user_id":"","user_name":"","changed_fields":[],"timestamp":ms}]
}

User

{
  "_id": ObjectId,
  "username": "string",
  "password": "bcrypt_hash",
  "role": "admin|pm|lead|user",
  "email": "", "name": "", "fullName": "",  // name+fullName kept in sync
  "mobile": "",
  "isActive": true, "active": true,         // both kept in sync
  "last_active": ISODate                     // updated on /me call
}

PlanXO Auth Key

{
  "auth_key": "string",
  "unique_id": "desktop_client_id",
  "status": "pending|approved|revoked",
  "is_active": true,
  "user_id": ObjectId,
  "role": "string",
  "expires_at": ISODate,           // admin: +100 years; others: +8 hours
  "approved_at": ISODate
}

Code Review Findings

🔴 Critical Issues

1. Password logged in plaintext (backend/app.py:300)

print(f"Login attempt for user: {username}, password: {password}")  # REMOVE THIS

2. get_current_user_id() referenced but never defined (app.py:3402) /planxo/folders/filtered calls get_current_user_id() — this will raise NameError at runtime. Replace with get_jwt_identity() and add @jwt_required().

3. safe_text() referenced but never defined (app.py:3142) /projects/<id>/desktop-download calls safe_text(resp) which doesn't exist — will crash.

4. pymongo imported twice; pymongo.errors.DuplicateKeyError used without top-level import app.py:7 imports secrets twice. pymongo is used at line 785 but only imported via from pymongo import ...pymongo.errors won't resolve. Add import pymongo at top.

5. /compute endpoint has no auth The Pantone/ECG color conversion endpoint is publicly accessible. Add @jwt_required().

6. Asset manager has no authentication All routes in asset_manager_app.py are unauthenticated. If the port is ever exposed, anyone can read/write S3. Add at minimum an internal shared secret header check.

7. Unreachable code in start_stage (app.py:1232)

return jsonify({'success': True, 'stages': stages, 'workflow_result': workflow_result})
return jsonify({'success': True, 'stages': stages})  # ← dead code, never reached

🟡 Important Issues

8. list_projects loads ALL projects with no pagination projects_col.find().sort(...) returns everything. With hundreds of projects this will be slow. Add limit/skip or cursor-based pagination.

9. Duplicate field names in MongoDB documents project_name/projectName, client_id/clientId, name/fullName, active/isActive, created_at/createdAt are all stored twice. This doubles write load and creates inconsistency risk. Pick one canonical name per field.

10. Workflow service URL hardcoded app.py:878,885,894 uses http://localhost:5001 directly instead of an env var like WORKFLOW_URL. Inconsistent with other services.

11. Stage parsing handles both JSON string and list Multiple endpoints do:

if isinstance(project['stages'], str):
    stages = json.loads(project['stages'])
else:
    stages = project['stages']

Stages should always be stored as a list. This defensive code masks a schema inconsistency.

12. before_request logs every endpoint — noisy in production

@app.before_request
def log_request():
    print(f">>> Endpoint called: {request.endpoint}")

Replace with proper logging or remove for production.

13. DEBUG print statements left in approval flows (app.py:23942512) Many print(f"DEBUG: ...") lines in the PlanXO approval endpoints should be removed or converted to logging.debug().

14. get_azure_config() is a function, not a route app.py:2937 defines get_azure_config() without a @app.route decorator, so it's never reachable.

15. WS bridge API key is in env with a weak default server.js:22: process.env.API_KEY || "aasdf345scwe" — the fallback default is weak. Fail hard if API_KEY not set in production.

16. delete_asset in asset manager uses string _id assets.find_one({"_id": asset_id}) — MongoDB stores _id as ObjectId, not string. This will never find anything. Use ObjectId(asset_id).

🟢 Suggestions

17. Extract stage manipulation into a helper start_stage, pause_stage, complete_stage, and assign_stage share ~40 lines of identical stage-parsing boilerplate. Extract to _get_stages(project) and _save_stages(project_id, stages).

18. get_project_folder_path is duplicated logic The folder path logic in app.py:3082 and asset_manager_app.py should live in one place (asset manager) and backend should call it.

19. JWT token expiry is 1 hour; comment says 12h/1d/30d JWT_ACCESS_TOKEN_EXPIRES = timedelta(hours=1) — the comment is stale. Clarify intended expiry and consider refresh tokens.

20. No index on planxo_auth_keys.auth_key This field is queried on every desktop connect. Add a unique index.


Environment Variables Reference

Backend (.env / .env.prod)

MONGO_URI=
JWT_SECRET_KEY=
FLASK_SECRET_KEY=
FLASK_ENV=production
AZURE_REDIRECT_URI=https://cms.techpremedia.com/azure-callback
AZURE_CLIENT_SECRET=
WS_BRIDGE_HTTP_URL=http://localhost:5003
DESKTOP_UNIQUE_ID=
ASSET_MANAGER_URL=http://localhost:5002

Asset Manager

MONGO_URI=
S3_BUCKET=
AWS_REGION=

Workflow

MONGO_URI=
AWS_REGION=
SENDER_EMAIL=
SENDER_NAME=
FLASK_SECRET_KEY=

WebSocket Bridge

WS_PORT=
HTTP_PORT=
API_KEY=
ASSET_MANAGER_URL=http://localhost:5002
CMS_BACKEND_URL=https://cms.techpremedia.com
NODE_ENV=production

Key Patterns

  • ObjectId serialization: every route converts _id via str(doc['_id']) before jsonify
  • now(): all services define their own now() returning datetime.utcnow()
  • S3 "folders": implemented as empty .keep placeholder objects
  • Project numeric ID: auto-incremented by querying projects_col.find_one(sort=[("project_id",-1)]) — not atomic, could duplicate under concurrent creates
  • Desktop auth flow: desktop → register-key → browser opens /planxo-auth/<key> → user approves → WS bridge notified → desktop receives auth_approved