131 lines
5.3 KiB
Python
131 lines
5.3 KiB
Python
# 2026-03-19
|
||
# table_rw.py — чтение и запись строк в terraform_demo_table.
|
||
# Два entrypoint в одном файле: list_rows (JSON API) и add_row (HTML-страница + POST-обработчик).
|
||
# ENV: PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD, PGSSLMODE
|
||
|
||
import os
|
||
import json
|
||
import psycopg2
|
||
import psycopg2.extras
|
||
|
||
|
||
def _connect():
|
||
return psycopg2.connect(
|
||
host=os.environ["PGHOST"],
|
||
port=os.environ.get("PGPORT", "5432"),
|
||
dbname=os.environ["PGDATABASE"],
|
||
user=os.environ["PGUSER"],
|
||
password=os.environ["PGPASSWORD"],
|
||
sslmode=os.environ.get("PGSSLMODE", "require"),
|
||
)
|
||
|
||
|
||
def list_rows(event):
|
||
# Возвращает все строки terraform_demo_table, отсортированные по убыванию created_at.
|
||
conn = _connect()
|
||
try:
|
||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||
cur.execute(
|
||
"SELECT id, title, created_at::text FROM terraform_demo_table ORDER BY created_at DESC"
|
||
)
|
||
rows = [dict(r) for r in cur.fetchall()]
|
||
return {"rows": rows, "count": len(rows)}
|
||
finally:
|
||
conn.close()
|
||
|
||
|
||
def _render_page(rows, message=""):
|
||
# HTML-страница с формой ввода и таблицей строк.
|
||
# message — статус последней операции (успех / ошибка).
|
||
rows_html = "".join(
|
||
f"<tr><td>{r['id']}</td><td>{r['title']}</td><td>{r['created_at']}</td></tr>"
|
||
for r in rows
|
||
)
|
||
msg_html = f'<p class="msg">{message}</p>' if message else ""
|
||
return f"""<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<title>pg-table-writer</title>
|
||
<style>
|
||
body {{ font-family: sans-serif; max-width: 700px; margin: 40px auto; background: #111; color: #eee; }}
|
||
h1 {{ color: #7dd3fc; }}
|
||
form {{ display: flex; gap: 8px; margin-bottom: 24px; }}
|
||
input[type=text] {{ flex: 1; padding: 8px 12px; border-radius: 6px; border: 1px solid #444; background: #1e1e1e; color: #eee; font-size: 15px; }}
|
||
button {{ padding: 8px 18px; background: #2563eb; color: #fff; border: none; border-radius: 6px; cursor: pointer; font-size: 15px; }}
|
||
button:hover {{ background: #1d4ed8; }}
|
||
table {{ width: 100%; border-collapse: collapse; }}
|
||
th, td {{ padding: 8px 10px; border-bottom: 1px solid #333; text-align: left; }}
|
||
th {{ color: #7dd3fc; }}
|
||
.msg {{ color: #4ade80; margin-bottom: 12px; }}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>pg-table-writer</h1>
|
||
<form method="POST">
|
||
<input type="text" name="title" placeholder="Введите строку..." autofocus required>
|
||
<button type="submit">Добавить</button>
|
||
</form>
|
||
{msg_html}
|
||
<table>
|
||
<thead><tr><th>#</th><th>title</th><th>created_at</th></tr></thead>
|
||
<tbody>{rows_html}</tbody>
|
||
</table>
|
||
</body>
|
||
</html>"""
|
||
|
||
|
||
def add_row(event):
|
||
# GET → HTML-страница с формой и списком строк.
|
||
# POST → вставляет строку из form-поля title или JSON-поля title,
|
||
# затем возвращает обновлённую HTML-страницу.
|
||
# POST с Content-Type: application/json (curl/API) → возвращает JSON.
|
||
method = event.get("_method", "GET")
|
||
|
||
if method == "GET":
|
||
conn = _connect()
|
||
try:
|
||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||
cur.execute("SELECT id, title, created_at::text FROM terraform_demo_table ORDER BY created_at DESC")
|
||
rows = [dict(r) for r in cur.fetchall()]
|
||
finally:
|
||
conn.close()
|
||
return _render_page(rows)
|
||
|
||
# POST — вставка строки
|
||
# Поле title приходит либо из JSON-тела, либо из application/x-www-form-urlencoded.
|
||
# Сервер уже распарсил JSON в event; form-данные приходят как event["body"] = "title=...".
|
||
title = event.get("title", "").strip()
|
||
if not title:
|
||
# Попытка распарсить form-encoded body (браузерная форма)
|
||
body = event.get("body", "")
|
||
if body.startswith("title="):
|
||
from urllib.parse import unquote_plus
|
||
title = unquote_plus(body[len("title="):].split("&")[0]).strip()
|
||
|
||
if not title:
|
||
return {"ok": False, "error": "title is required"}
|
||
|
||
conn = _connect()
|
||
try:
|
||
cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
||
cur.execute(
|
||
"INSERT INTO terraform_demo_table (title) VALUES (%s) RETURNING id, title, created_at::text",
|
||
(title,),
|
||
)
|
||
row = dict(cur.fetchone())
|
||
conn.commit()
|
||
|
||
# Если запрос из браузера (form POST) — возвращаем обновлённую страницу.
|
||
# Если из curl/API — возвращаем JSON.
|
||
accept = event.get("_accept", "")
|
||
if "application/json" in accept:
|
||
return {"ok": True, "row": row}
|
||
|
||
# Перечитываем все строки для обновлённой страницы
|
||
cur.execute("SELECT id, title, created_at::text FROM terraform_demo_table ORDER BY created_at DESC")
|
||
rows = [dict(r) for r in cur.fetchall()]
|
||
return _render_page(rows, message=f"Добавлено: «{row['title']}»")
|
||
finally:
|
||
conn.close()
|