cat_register.py
Ricky/cat_project/backend/app/routes/cat_register.py
import base64
import os
import re
import uuid
from typing import Optional
from flask import Blueprint, request, jsonify, current_app
from werkzeug.security import generate_password_hash
from ..services.storage import Storage
from ..services.s3_storage import s3_enabled, upload_bytes, ext_to_content_type
from ..utils.auth import issue_jwt
from ..utils.media_url import resolve_stored_media_url
bp = Blueprint("cat_register", __name__, url_prefix="/api")
_EMAIL_RE = re.compile(r"^[^\s@]+@[^\s@]+\.[^\s@]+$")
def _decode_data_url(data_url: str) -> Optional[tuple[bytes, str]]:
if not data_url or not data_url.startswith("data:"):
return None
try:
header, b64 = data_url.split(",", 1)
ext = "bin"
if ";" in header:
mime = header[5:].split(";", 1)[0]
if "/" in mime:
ext = mime.split("/", 1)[1].strip() or "bin"
raw = base64.b64decode(b64)
return raw, ext
except Exception:
return None
def _persist_cat_photo(data_url: str, upload_dir: str) -> Optional[str]:
"""
Store questionnaire photo: S3 key upload/cat_{uuid}.{ext} when AWS_S3_BUCKET is set,
else local /uploads/cat_{uuid}.{ext}.
Returns DB value: s3://bucket/... or /uploads/... path.
"""
decoded = _decode_data_url(data_url)
if not decoded:
return None
raw, ext = decoded
if s3_enabled():
info = upload_bytes(
prefix="upload",
ext=ext,
body=raw,
content_type=ext_to_content_type(ext),
)
return info["s3_uri"]
os.makedirs(upload_dir, exist_ok=True)
filename = f"cat_{uuid.uuid4().hex}.{ext}"
path = os.path.join(upload_dir, filename)
with open(path, "wb") as f:
f.write(raw)
return f"/uploads/{filename}"
@bp.route("/cat-questionnaire", methods=["OPTIONS"])
def cat_questionnaire_options():
return jsonify({"ok": True}), 200
@bp.post("/cat-questionnaire")
def register_with_cat():
"""Register user (email+password) and upsert the single cat profile."""
data = request.get_json(silent=True) or {}
email = (data.get("email") or "").strip().lower()
password = data.get("password") or ""
if not email or not password:
return jsonify({"status": "error", "message": "email and password are required"}), 400
if not _EMAIL_RE.match(email):
return jsonify({"status": "error", "message": "invalid email"}), 400
if len(password) < 8:
return jsonify({"status": "error", "message": "password must be at least 8 characters"}), 400
# Cat fields
name = (data.get("cat_name") or "").strip()
breed = (data.get("breed") or data.get("cat_breed") or "").strip()
age = data.get("age")
gender = (data.get("gender") or "").strip()
health = (data.get("health") or "").strip()
vaccinations = data.get("vaccines") or data.get("vaccinations") or []
photo_data_url = data.get("photo_data_url") or ""
if not name or not breed:
return jsonify({"status": "error", "message": "cat_name and breed are required"}), 400
try:
age_int = int(age)
if age_int < 0:
raise ValueError
except Exception:
return jsonify({"status": "error", "message": "invalid age"}), 400
if not gender:
return jsonify({"status": "error", "message": "gender is required"}), 400
if not health:
return jsonify({"status": "error", "message": "health is required"}), 400
if not isinstance(vaccinations, list):
vaccinations = []
required_v = {"FVRCP", "Rabies", "FeLV", "FIP"}
if not required_v.issubset(set(vaccinations)):
return jsonify({"status": "error", "message": "all four core vaccines are required"}), 400
store = Storage(current_app.config["SQLITE_PATH"])
if store.get_user_by_email(email):
return jsonify({"status": "error", "message": "user already exists"}), 409
password_hash = generate_password_hash(password, method='pbkdf2:sha256')
store.create_user(email, password_hash)
user = store.get_user_by_email(email)
if not user:
return jsonify({"status": "error", "message": "failed to create user"}), 500
upload_dir = current_app.config.get("UPLOAD_DIR", "./uploads")
photo_path = _persist_cat_photo(photo_data_url, upload_dir)
profile = dict(data)
cat_row = store.upsert_cat_for_user(
user_id=user["id"],
name=name,
breed=breed,
age=age_int,
gender=gender,
health=health,
vaccinations=vaccinations,
profile=profile,
photo_path=photo_path,
)
cat_id = (cat_row or {}).get("id")
photo_for_client = resolve_stored_media_url(current_app.config, photo_path)
token = issue_jwt(user_id=user["id"], email=user["email"])
return jsonify({
"status": "ok",
"token": token,
"user": {"id": user["id"], "email": user["email"]},
"cat": {
"id": cat_id,
"name": name,
"breed": breed,
"age": age_int,
"gender": gender,
"health": health,
"vaccinations": vaccinations,
"photo_path": photo_for_client,
},
}), 201
Bài viết liên quan
__init__.py
__init__.py — python source code from the Ricky learning materials (Ricky/Cat Project Final/back_web-4/app/__init__.py).
Đọc bài viết →auth.py
auth.py — python source code from the Ricky learning materials (Ricky/Cat Project Final/back_web-4/app/admin/auth.py).
Đọc bài viết →routes.py
routes.py — python source code from the Ricky learning materials (Ricky/Cat Project Final/back_web-4/app/admin/routes.py).
Đọc bài viết →config.py
config.py — python source code from the Ricky learning materials (Ricky/Cat Project Final/back_web-4/app/config.py).
Đọc bài viết →admin_ingest.py
admin_ingest.py — python source code from the Ricky learning materials (Ricky/Cat Project Final/back_web-4/app/routes/admin_ingest.py).
Đọc bài viết →admin_stats.py
admin_stats.py — python source code from the Ricky learning materials (Ricky/Cat Project Final/back_web-4/app/routes/admin_stats.py).
Đọc bài viết →