Files
iAOP/core/auth/postgres_users_schema.py
T

85 lines
2.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""④-1 PostgreSQL users 表 DDL + 映射 —— issue #150 / #30 / PRD 8.2。
定义本地账号在 PostgreSQL 中的存储结构(#30 users 表 schema),并提供
`UserStore` 与 PG 之间的映射。DDL 仅用标准库拼装(无 psycopg2 依赖),
实际接库时上层注入连接即可。
表结构(对齐 core/data-bus/postgres_schema.py 的命名风格):
users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(64) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL, -- pbkdf2_sha256$<iter>$<salt>$<hash>
role VARCHAR(16) NOT NULL DEFAULT 'readonly', -- readonly/engineer/admin
active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_login_at TIMESTAMPTZ
)
索引:username 唯一索引(登录走 username);role 普通索引(用户管理筛选)。
"""
from __future__ import annotations
from typing import Any, Dict, List
from .users import User, VALID_ROLES
USERS_TABLE_DDL = """CREATE TABLE IF NOT EXISTS users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(64) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
role VARCHAR(16) NOT NULL DEFAULT 'readonly',
active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_login_at TIMESTAMPTZ,
CONSTRAINT users_role_chk CHECK (role IN ('readonly','engineer','admin'))
);
CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
"""
def row_to_user(row: Any) -> User:
"""PG 行(dict/tuple-like)→ User。row 需含 keys: id/username/password_hash/role/active/created_at/last_login_at。"""
def g(k, default=None):
if isinstance(row, dict):
return row.get(k, default)
return getattr(row, k, default)
created = g("created_at")
last = g("last_login_at")
# PG TIMESTAMPTZ → epoch(若为 datetime 有 timestamp())
def to_epoch(v):
if v is None:
return None
if hasattr(v, "timestamp"):
return v.timestamp()
return v
return User(
id=int(g("id")),
username=str(g("username")),
password_hash=str(g("password_hash")),
role=str(g("role", "readonly")) or "readonly",
active=bool(g("active", True)),
created_at=to_epoch(created) or 0.0,
last_login_at=to_epoch(last),
)
def user_to_row(user: User) -> Dict[str, Any]:
"""User → PG 列字典(不含 id,用于 INSERT;UPDATE 时按 id 定位)。"""
return {
"username": user.username,
"password_hash": user.password_hash,
"role": user.role,
"active": user.active,
}
def validate_role(role: str) -> str:
if role not in VALID_ROLES:
raise ValueError("role must be one of %r" % (VALID_ROLES,))
return role
__all__ = ["USERS_TABLE_DDL", "row_to_user", "user_to_row", "validate_role"]