standalone

This commit is contained in:
Nicolas Cantu 2026-03-16 15:19:17 +01:00
parent 61cec6f430
commit 95bfa36b00
6 changed files with 62 additions and 12 deletions

View File

@ -107,8 +107,37 @@ def list_project_ids() -> list[str]:
return [d.name for d in projects_dir.iterdir() if d.is_dir() and (d / "conf.json").is_file()] return [d.name for d in projects_dir.iterdir() if d.is_dir() and (d / "conf.json").is_file()]
def _normalize_conf_to_addresses(auth_to: object) -> set[str]:
"""Return set of normalized (lowercase) email addresses from authorized_emails.to.
Supports: str (single address), list of str, or list of dict with env keys (e.g. test, pprod, prod).
Address pattern AI.<project_id>.<env>@4nkweb.com; project_id and env may be uppercase."""
out: set[str] = set()
if not auth_to:
return out
if isinstance(auth_to, str):
a = auth_to.strip().lower()
if a:
out.add(a)
return out
if isinstance(auth_to, list):
for item in auth_to:
if isinstance(item, str):
a = item.strip().lower()
if a:
out.add(a)
elif isinstance(item, dict):
for v in item.values():
if isinstance(v, str):
a = v.strip().lower()
if a:
out.add(a)
return out
def resolve_project_id_by_email_to(to_address: str) -> str | None: def resolve_project_id_by_email_to(to_address: str) -> str | None:
"""Find project id whose tickets.authorized_emails.to matches the given address (case-insensitive).""" """Find project id whose tickets.authorized_emails.to matches the given address (case-insensitive).
authorized_emails.to may be a single string or a list of objects { test, pprod, prod } with addresses
AI.<project_id>.<env>@4nkweb.com (project_id and env may be uppercase)."""
if not to_address or not to_address.strip(): if not to_address or not to_address.strip():
return None return None
to_normalized = to_address.strip().lower() to_normalized = to_address.strip().lower()
@ -121,8 +150,8 @@ def resolve_project_id_by_email_to(to_address: str) -> str | None:
continue continue
tickets = conf.get("tickets") or {} tickets = conf.get("tickets") or {}
auth = tickets.get("authorized_emails") or {} auth = tickets.get("authorized_emails") or {}
conf_to = (auth.get("to") or "").strip().lower() conf_to_set = _normalize_conf_to_addresses(auth.get("to"))
if conf_to == to_normalized: if to_normalized in conf_to_set:
return pid return pid
return None return None

View File

@ -35,7 +35,7 @@ One JSON file per project: `projects/<id>/conf.json` (e.g. `projects/lecoffreio/
| `version.splash_app_name` | no | App name used in splash message template | | `version.splash_app_name` | no | App name used in splash message template |
| `mail` | no | Mail/imap bridge config | | `mail` | no | Mail/imap bridge config |
| `git` | no | Git hosting: `wiki_url`, `token_file` (path relative to repo root for token file). Ticketing URL is under `tickets`, not `git`. | | `git` | no | Git hosting: `wiki_url`, `token_file` (path relative to repo root for token file). Ticketing URL is under `tickets`, not `git`. |
| `tickets` | no | Ticketing: `ticketing_url` (Gitea issues URL), `authorized_emails` (`to`: alias, `from`: list of allowed sender addresses for mail-based ticketing). The **to** address is used to resolve the project id when processing mails. See projects/ia_dev/docs/TICKETS_SPOOL_FORMAT.md. | | `tickets` | no | Ticketing: `ticketing_url` (Gitea issues URL), `authorized_emails` (`to`: alias or list of env-keyed objects, `from`: list of allowed sender addresses). **to** resolves the project id when processing mails. It may be a single string or a list of one object with env keys: `[{ "test": "AI.<project_id>.TEST@4nkweb.com", "pprod": "...", "prod": "..." }]`. Pattern: `AI.<project_id>.<env>@4nkweb.com` (`project_id` and `env` may be uppercase). See projects/ia_dev/docs/TICKETS_SPOOL_FORMAT.md. |
The API token for ai_working_help is **not** in conf.json. It is found by scanning all projects and all envs (as described above): each file `projects/<id>/.secrets/<env>/ia_token` is read and the Bearer is compared to the file content or to (file content + env). Token value is **base** + **env**; **env** is the environment name (test, pprod, prod), to be adapted per environment. The first match gives project id and env. The API token for ai_working_help is **not** in conf.json. It is found by scanning all projects and all envs (as described above): each file `projects/<id>/.secrets/<env>/ia_token` is read and the Bearer is compared to the file content or to (file content + env). Token value is **base** + **env**; **env** is the environment name (test, pprod, prod), to be adapted per environment. The first match gives project id and env.

View File

@ -30,7 +30,13 @@
"tickets": { "tickets": {
"ticketing_url": "https://git.4nkweb.com/nicolas.cantu/algo/issues", "ticketing_url": "https://git.4nkweb.com/nicolas.cantu/algo/issues",
"authorized_emails": { "authorized_emails": {
"to": "ai.support.algo@4nkweb.com", "to": [
{
"test": "AI.ALGO.TEST@4nkweb.com",
"pprod": "AI.ALGO.PPROD@4nkweb.com",
"prod": "AI.ALGO.PROD@4nkweb.com"
}
],
"from": [] "from": []
} }
} }

View File

@ -20,7 +20,6 @@
"splash_app_name": "enso" "splash_app_name": "enso"
}, },
"mail": { "mail": {
"email": "ai.support.enso@4nkweb.com",
"imap_bridge_env": ".secrets/gitea-issues/imap-bridge.env" "imap_bridge_env": ".secrets/gitea-issues/imap-bridge.env"
}, },
"git": { "git": {
@ -30,7 +29,13 @@
"tickets": { "tickets": {
"ticketing_url": "https://git.4nkweb.com/nicolas.cantu/enso/issues", "ticketing_url": "https://git.4nkweb.com/nicolas.cantu/enso/issues",
"authorized_emails": { "authorized_emails": {
"to": "ai.support.enso@4nkweb.com", "to": [
{
"test": "AI.ENSO.TEST@4nkweb.com",
"pprod": "AI.ENSO.PPROD@4nkweb.com",
"prod": "AI.ENSO.PROD@4nkweb.com"
}
],
"from": [] "from": []
} }
} }

View File

@ -1,8 +1,7 @@
{ {
"id": "ia_dev", "id": "ia_dev",
"name": "ia_dev", "name": "ia_dev",
"project_path": "../", "project_path": "home/desk/code/ia_dev",
"base_path": "ia_dev",
"build_dirs": [], "build_dirs": [],
"deploy": {}, "deploy": {},
"version": { "version": {
@ -10,7 +9,6 @@
"splash_app_name": "ia_dev" "splash_app_name": "ia_dev"
}, },
"mail": { "mail": {
"email": "ai.support.ia_dev@4nkweb.com",
"imap_bridge_env": ".secrets/gitea-issues/imap-bridge.env" "imap_bridge_env": ".secrets/gitea-issues/imap-bridge.env"
}, },
"git": { "git": {
@ -20,7 +18,13 @@
"tickets": { "tickets": {
"ticketing_url": "https://git.4nkweb.com/4nk/ia_dev/issues", "ticketing_url": "https://git.4nkweb.com/4nk/ia_dev/issues",
"authorized_emails": { "authorized_emails": {
"to": "ai.support.ia_dev@4nkweb.com", "to": [
{
"test": "AI.IA_DEV.TEST@4nkweb.com",
"pprod": "AI.IA_DEV.PPROD@4nkweb.com",
"prod": "AI.IA_DEV.PROD@4nkweb.com"
}
],
"from": ["nicolas.cantu@pm.me"] "from": ["nicolas.cantu@pm.me"]
} }
} }

View File

@ -30,7 +30,13 @@
"tickets": { "tickets": {
"ticketing_url": "https://git.4nkweb.com/4nk/lecoffre_ng/issues", "ticketing_url": "https://git.4nkweb.com/4nk/lecoffre_ng/issues",
"authorized_emails": { "authorized_emails": {
"to": "ai.support.lecoffreio@4nkweb.com", "to": [
{
"test": "AI.LECOFFREIO.TEST@4nkweb.com",
"pprod": "AI.LECOFFREIO.PPROD@4nkweb.com",
"prod": "AI.LECOFFREIO.PROD@4nkweb.com"
}
],
"from": [ "from": [
"laurence@lecoffre.io", "laurence@lecoffre.io",
"gwendal@lecoffre.io" "gwendal@lecoffre.io"