Skip to content

Commit 5b099ae

Browse files
Update d8f1b52c96e3_drop_legacy_comment_tables.py
1 parent b2fb131 commit 5b099ae

1 file changed

Lines changed: 173 additions & 4 deletions

File tree

backend/app/alembic/versions/d8f1b52c96e3_drop_legacy_comment_tables.py

Lines changed: 173 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
copied into district_notes by b3d9f47a25c1; form comments/commenters/tags are
77
dropped without migration by decision.
88
9-
Irreversible: downgrade raises. Restore from a backup if this ever needs
10-
undoing.
9+
Downgrade recreates the tables (final shape as of 0db008690d60 + da39a3ee5e6b)
10+
empty — the data is gone; restore from a backup if it's needed.
1111
1212
Revision ID: d8f1b52c96e3
1313
Revises: c7e2a94d81f5
@@ -19,13 +19,45 @@
1919

2020
from alembic import op
2121
import sqlalchemy as sa
22+
from sqlalchemy.dialects import postgresql
23+
import app.core.models
24+
from app.constants import SQL_DIR
2225

2326
# revision identifiers, used by Alembic.
2427
revision: str = "d8f1b52c96e3"
2528
down_revision: Union[str, None] = "c7e2a94d81f5"
2629
branch_labels: Union[str, Sequence[str], None] = None
2730
depends_on: Union[str, Sequence[str], None] = None
2831

32+
# Inlined (the app-side ReviewStatus enum was deleted with the legacy
33+
# comment tables; this migration must stay runnable). create_type=False so
34+
# create_table doesn't re-emit CREATE TYPE; downgrade creates it explicitly.
35+
review_status_enum = postgresql.ENUM(
36+
"REVIEWED",
37+
"APPROVED",
38+
"REJECTED",
39+
name="review_status_enum",
40+
schema="comments",
41+
create_type=False,
42+
)
43+
44+
45+
def _timestamps():
46+
return (
47+
sa.Column(
48+
"created_at",
49+
sa.TIMESTAMP(timezone=True),
50+
server_default=sa.text("CURRENT_TIMESTAMP"),
51+
nullable=False,
52+
),
53+
sa.Column(
54+
"updated_at",
55+
sa.TIMESTAMP(timezone=True),
56+
server_default=sa.text("CURRENT_TIMESTAMP"),
57+
nullable=False,
58+
),
59+
)
60+
2961

3062
def upgrade() -> None:
3163
op.drop_table("document_comment", schema="comments")
@@ -40,6 +72,143 @@ def upgrade() -> None:
4072

4173

4274
def downgrade() -> None:
43-
raise NotImplementedError(
44-
"The legacy comment tables are gone for good — restore from a backup."
75+
# Recreates the schema only; rows are unrecoverable without a backup.
76+
op.execute(
77+
sa.text(
78+
"CREATE TYPE comments.review_status_enum AS ENUM "
79+
"('REVIEWED', 'APPROVED', 'REJECTED')"
80+
)
81+
)
82+
83+
op.create_table(
84+
"commenter",
85+
*_timestamps(),
86+
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
87+
sa.Column("first_name", sa.String(length=255), nullable=False),
88+
sa.Column("email", sa.String(length=320), nullable=False),
89+
sa.Column("salutation", sa.String(length=255), nullable=True),
90+
sa.Column("last_name", sa.String(length=255), nullable=True),
91+
sa.Column("place", sa.String(length=255), nullable=True),
92+
sa.Column("state", sa.String(length=255), nullable=True),
93+
sa.Column("zip_code", sa.String(length=255), nullable=True),
94+
sa.Column("moderation_score", sa.Float(), nullable=True),
95+
sa.Column("review_status", review_status_enum, nullable=True),
96+
sa.CheckConstraint(
97+
"email ~* '^[a-zA-Z0-9.!#$%%&''*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$'",
98+
name="valid_email_format",
99+
),
100+
sa.CheckConstraint("LENGTH(TRIM(email)) > 0", name="email_not_empty"),
101+
sa.CheckConstraint("LENGTH(TRIM(first_name)) > 0", name="first_name_not_empty"),
102+
sa.PrimaryKeyConstraint("id"),
103+
sa.UniqueConstraint(
104+
"first_name", "email", name="commenter_unique_on_first_name_and_email"
105+
),
106+
schema="comments",
107+
)
108+
op.create_index(
109+
"idx_commenter_first_name_and_email",
110+
"commenter",
111+
[sa.text("lower(trim(first_name))"), sa.text("lower(trim(email))")],
112+
unique=False,
113+
schema="comments",
114+
)
115+
op.create_index(
116+
op.f("ix_comments_commenter_id"),
117+
"commenter",
118+
["id"],
119+
unique=True,
120+
schema="comments",
121+
)
122+
123+
op.create_table(
124+
"comment",
125+
*_timestamps(),
126+
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
127+
sa.Column("title", sa.String(length=255), nullable=False),
128+
sa.Column("comment", sa.String(length=5000), nullable=False),
129+
sa.Column("commenter_id", sa.Integer(), nullable=True),
130+
sa.Column("moderation_score", sa.Float(), nullable=True),
131+
sa.Column("review_status", review_status_enum, nullable=True),
132+
sa.Column(
133+
"review_flagged", sa.Boolean(), nullable=False, server_default="false"
134+
),
135+
sa.ForeignKeyConstraint(["commenter_id"], ["comments.commenter.id"]),
136+
sa.PrimaryKeyConstraint("id"),
137+
sa.CheckConstraint("LENGTH(TRIM(title)) > 0", name="title_not_empty"),
138+
sa.CheckConstraint("LENGTH(TRIM(comment)) > 0", name="comment_not_empty"),
139+
schema="comments",
140+
)
141+
op.create_index(
142+
op.f("ix_comments_comment_commenter_id"),
143+
"comment",
144+
["commenter_id"],
145+
unique=False,
146+
schema="comments",
147+
)
148+
op.create_index(
149+
op.f("ix_comments_comment_id"),
150+
"comment",
151+
["id"],
152+
unique=True,
153+
schema="comments",
154+
)
155+
156+
op.create_table(
157+
"tag",
158+
*_timestamps(),
159+
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
160+
sa.Column("slug", sa.String(length=255), nullable=False),
161+
sa.Column("moderation_score", sa.Float(), nullable=True),
162+
sa.Column("review_status", review_status_enum, nullable=True),
163+
sa.PrimaryKeyConstraint("id"),
164+
sa.CheckConstraint("LENGTH(slug) > 0", name="slug_not_empty"),
165+
schema="comments",
166+
)
167+
op.create_index(
168+
op.f("ix_comments_tag_id"), "tag", ["id"], unique=True, schema="comments"
169+
)
170+
op.create_index(
171+
op.f("ix_comments_tag_slug"), "tag", ["slug"], unique=True, schema="comments"
172+
)
173+
174+
op.create_table(
175+
"comment_tag",
176+
sa.Column("comment_id", sa.Integer(), nullable=False),
177+
sa.Column("tag_id", sa.Integer(), nullable=False),
178+
sa.ForeignKeyConstraint(["comment_id"], ["comments.comment.id"]),
179+
sa.ForeignKeyConstraint(["tag_id"], ["comments.tag.id"]),
180+
sa.PrimaryKeyConstraint("comment_id", "tag_id"),
181+
sa.UniqueConstraint("comment_id", "tag_id", name="unique_comment_tag_link"),
182+
schema="comments",
183+
)
184+
185+
op.create_table(
186+
"document_comment",
187+
sa.Column("comment_id", sa.Integer(), nullable=False),
188+
sa.Column("document_id", app.core.models.UUIDType(), nullable=False),
189+
sa.Column("zone", sa.Integer(), nullable=True),
190+
sa.ForeignKeyConstraint(["comment_id"], ["comments.comment.id"]),
191+
sa.ForeignKeyConstraint(["document_id"], ["document.document.document_id"]),
192+
sa.PrimaryKeyConstraint("comment_id"),
193+
sa.UniqueConstraint("comment_id"),
194+
schema="comments",
195+
)
196+
op.create_index(
197+
op.f("ix_comments_document_comment_document_id"),
198+
"document_comment",
199+
["document_id"],
200+
unique=False,
201+
schema="comments",
202+
)
203+
204+
with open(SQL_DIR / "normalize_email.sql", "r") as f:
205+
op.execute(sa.text(f.read()))
206+
op.execute(
207+
sa.text("""
208+
CREATE TRIGGER normalize_email_trigger
209+
BEFORE INSERT OR UPDATE ON comments.commenter
210+
FOR EACH ROW EXECUTE FUNCTION normalize_email();
211+
""")
45212
)
213+
with open(SQL_DIR / "slugify_tag.sql", "r") as f:
214+
op.execute(sa.text(f.read()))

0 commit comments

Comments
 (0)