Skip to content

Commit 87ee523

Browse files
committed
first template commit success
1 parent 8c68668 commit 87ee523

24 files changed

Lines changed: 3386 additions & 7 deletions
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""Add template and generation models
2+
3+
Revision ID: 6f44bc66fd3f
4+
Revises: fe56fa70289e
5+
Create Date: 2026-02-21 23:20:00.000000
6+
7+
"""
8+
9+
from alembic import op
10+
import sqlalchemy as sa
11+
from sqlalchemy.dialects import postgresql
12+
13+
14+
# revision identifiers, used by Alembic.
15+
revision = "6f44bc66fd3f"
16+
down_revision = "fe56fa70289e"
17+
branch_labels = None
18+
depends_on = None
19+
20+
21+
template_category_enum = sa.Enum(
22+
"cover_letter",
23+
"email",
24+
"proposal",
25+
"other",
26+
name="templatecategory",
27+
)
28+
template_language_enum = sa.Enum(
29+
"fr",
30+
"en",
31+
"zh",
32+
"other",
33+
name="templatelanguage",
34+
)
35+
36+
37+
def upgrade() -> None:
38+
bind = op.get_bind()
39+
template_category_enum.create(bind, checkfirst=True)
40+
template_language_enum.create(bind, checkfirst=True)
41+
42+
op.create_table(
43+
"template",
44+
sa.Column("name", sa.String(length=255), nullable=False),
45+
sa.Column("category", template_category_enum, nullable=False),
46+
sa.Column("language", template_language_enum, nullable=False),
47+
sa.Column("tags", sa.JSON(), nullable=False),
48+
sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False),
49+
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=False),
50+
sa.Column("is_archived", sa.Boolean(), nullable=False),
51+
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
52+
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True),
53+
sa.ForeignKeyConstraint(["user_id"], ["user.id"], ondelete="CASCADE"),
54+
sa.PrimaryKeyConstraint("id"),
55+
)
56+
57+
op.create_table(
58+
"templateversion",
59+
sa.Column("content", sa.Text(), nullable=False),
60+
sa.Column("variables_schema", sa.JSON(), nullable=False),
61+
sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False),
62+
sa.Column("template_id", postgresql.UUID(as_uuid=True), nullable=False),
63+
sa.Column("version", sa.Integer(), nullable=False),
64+
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
65+
sa.Column("created_by", postgresql.UUID(as_uuid=True), nullable=False),
66+
sa.ForeignKeyConstraint(
67+
["template_id"], ["template.id"], ondelete="CASCADE"
68+
),
69+
sa.ForeignKeyConstraint(["created_by"], ["user.id"], ondelete="CASCADE"),
70+
sa.PrimaryKeyConstraint("id"),
71+
sa.UniqueConstraint("template_id", "version"),
72+
)
73+
74+
op.create_table(
75+
"generation",
76+
sa.Column("title", sa.String(length=255), nullable=False),
77+
sa.Column("input_text", sa.Text(), nullable=False),
78+
sa.Column("extracted_values", sa.JSON(), nullable=False),
79+
sa.Column("output_text", sa.Text(), nullable=False),
80+
sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False),
81+
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=False),
82+
sa.Column("template_id", postgresql.UUID(as_uuid=True), nullable=False),
83+
sa.Column("template_version_id", postgresql.UUID(as_uuid=True), nullable=False),
84+
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
85+
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True),
86+
sa.ForeignKeyConstraint(["user_id"], ["user.id"], ondelete="CASCADE"),
87+
sa.ForeignKeyConstraint(
88+
["template_id"], ["template.id"], ondelete="CASCADE"
89+
),
90+
sa.ForeignKeyConstraint(
91+
["template_version_id"], ["templateversion.id"], ondelete="CASCADE"
92+
),
93+
sa.PrimaryKeyConstraint("id"),
94+
)
95+
96+
97+
def downgrade() -> None:
98+
op.drop_table("generation")
99+
op.drop_table("templateversion")
100+
op.drop_table("template")
101+
102+
bind = op.get_bind()
103+
template_language_enum.drop(bind, checkfirst=True)
104+
template_category_enum.drop(bind, checkfirst=True)

backend/app/api/main.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
from fastapi import APIRouter
22

3-
from app.api.routes import items, login, private, users, utils
3+
from app.api.routes import (
4+
generate,
5+
generations,
6+
items,
7+
login,
8+
private,
9+
templates,
10+
users,
11+
utils,
12+
)
413
from app.core.config import settings
514

615
api_router = APIRouter()
716
api_router.include_router(login.router)
817
api_router.include_router(users.router)
918
api_router.include_router(utils.router)
1019
api_router.include_router(items.router)
20+
api_router.include_router(templates.router)
21+
api_router.include_router(generate.router)
22+
api_router.include_router(generations.router)
1123

1224

1325
if settings.ENVIRONMENT == "local":

backend/app/api/routes/generate.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from typing import Any, NoReturn
2+
3+
from fastapi import APIRouter, HTTPException
4+
5+
from app.api.deps import CurrentUser, SessionDep
6+
from app.models import (
7+
ExtractVariablesRequest,
8+
ExtractVariablesResponse,
9+
RenderTemplateRequest,
10+
RenderTemplateResponse,
11+
)
12+
from app.services import generation_service
13+
from app.services.exceptions import ServiceError
14+
15+
router = APIRouter(prefix="/generate", tags=["generate"])
16+
17+
18+
def _raise_http_from_service_error(exc: ServiceError) -> NoReturn:
19+
raise HTTPException(status_code=exc.status_code, detail=exc.detail)
20+
21+
22+
@router.post("/extract", response_model=ExtractVariablesResponse)
23+
def extract_variables(
24+
*,
25+
session: SessionDep,
26+
current_user: CurrentUser,
27+
extract_in: ExtractVariablesRequest,
28+
) -> Any:
29+
try:
30+
return generation_service.extract_values_for_user(
31+
session=session,
32+
current_user=current_user,
33+
extract_in=extract_in,
34+
)
35+
except ServiceError as exc:
36+
_raise_http_from_service_error(exc)
37+
38+
39+
@router.post("/render", response_model=RenderTemplateResponse)
40+
def render_template(
41+
*,
42+
session: SessionDep,
43+
current_user: CurrentUser,
44+
render_in: RenderTemplateRequest,
45+
) -> Any:
46+
try:
47+
return generation_service.render_for_user(
48+
session=session,
49+
current_user=current_user,
50+
render_in=render_in,
51+
)
52+
except ServiceError as exc:
53+
_raise_http_from_service_error(exc)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import uuid
2+
from typing import Any, NoReturn
3+
4+
from fastapi import APIRouter, HTTPException
5+
6+
from app.api.deps import CurrentUser, SessionDep
7+
from app.models import (
8+
GenerationCreate,
9+
GenerationPublic,
10+
GenerationsPublic,
11+
GenerationUpdate,
12+
)
13+
from app.services import generation_service
14+
from app.services.exceptions import ServiceError
15+
16+
router = APIRouter(prefix="/generations", tags=["generations"])
17+
18+
19+
def _raise_http_from_service_error(exc: ServiceError) -> NoReturn:
20+
raise HTTPException(status_code=exc.status_code, detail=exc.detail)
21+
22+
23+
@router.get("/", response_model=GenerationsPublic)
24+
def read_generations(
25+
session: SessionDep, current_user: CurrentUser, skip: int = 0, limit: int = 100
26+
) -> Any:
27+
generations, count = generation_service.list_generations_for_user(
28+
session=session,
29+
current_user=current_user,
30+
skip=skip,
31+
limit=limit,
32+
)
33+
data = [GenerationPublic.model_validate(generation) for generation in generations]
34+
return GenerationsPublic(data=data, count=count)
35+
36+
37+
@router.get("/{generation_id}", response_model=GenerationPublic)
38+
def read_generation(
39+
session: SessionDep, current_user: CurrentUser, generation_id: uuid.UUID
40+
) -> Any:
41+
try:
42+
generation = generation_service.get_generation_for_user(
43+
session=session,
44+
current_user=current_user,
45+
generation_id=generation_id,
46+
)
47+
except ServiceError as exc:
48+
_raise_http_from_service_error(exc)
49+
50+
return GenerationPublic.model_validate(generation)
51+
52+
53+
@router.post("/", response_model=GenerationPublic)
54+
def create_generation(
55+
*,
56+
session: SessionDep,
57+
current_user: CurrentUser,
58+
generation_in: GenerationCreate,
59+
) -> Any:
60+
try:
61+
generation = generation_service.create_generation_for_user(
62+
session=session,
63+
current_user=current_user,
64+
generation_in=generation_in,
65+
)
66+
except ServiceError as exc:
67+
_raise_http_from_service_error(exc)
68+
69+
return GenerationPublic.model_validate(generation)
70+
71+
72+
@router.patch("/{generation_id}", response_model=GenerationPublic)
73+
def update_generation(
74+
*,
75+
session: SessionDep,
76+
current_user: CurrentUser,
77+
generation_id: uuid.UUID,
78+
generation_in: GenerationUpdate,
79+
) -> Any:
80+
try:
81+
generation = generation_service.update_generation_for_user(
82+
session=session,
83+
current_user=current_user,
84+
generation_id=generation_id,
85+
generation_in=generation_in,
86+
)
87+
except ServiceError as exc:
88+
_raise_http_from_service_error(exc)
89+
90+
return GenerationPublic.model_validate(generation)

0 commit comments

Comments
 (0)