Skip to content

Commit 1d42792

Browse files
committed
feat: implement image management system with S3 and variants
1 parent 5c45b81 commit 1d42792

5 files changed

Lines changed: 1130 additions & 0 deletions

File tree

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""add_created_at_updated_at_fields
2+
3+
Revision ID: b2374a5f43e5
4+
Revises: db8bf70fc16a
5+
Create Date: 2025-11-19 15:03:22.029859
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
from sqlalchemy.dialects import postgresql
12+
13+
# revision identifiers, used by Alembic.
14+
revision = 'b2374a5f43e5'
15+
down_revision = 'db8bf70fc16a'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
op.alter_column('image', 'file_size',
23+
existing_type=sa.BIGINT(),
24+
type_=sa.Integer(),
25+
existing_nullable=False)
26+
op.alter_column('image', 'processing_status',
27+
existing_type=sa.VARCHAR(length=20),
28+
nullable=False,
29+
existing_server_default=sa.text("'pending'::character varying"))
30+
op.alter_column('image', 'created_at',
31+
existing_type=postgresql.TIMESTAMP(timezone=True),
32+
type_=sa.DateTime(),
33+
existing_nullable=True,
34+
existing_server_default=sa.text('now()'))
35+
op.alter_column('image', 'updated_at',
36+
existing_type=postgresql.TIMESTAMP(timezone=True),
37+
type_=sa.DateTime(),
38+
existing_nullable=True,
39+
existing_server_default=sa.text('now()'))
40+
op.drop_index('ix_image_filename', table_name='image')
41+
op.drop_index('ix_image_owner_id', table_name='image')
42+
op.drop_index('ix_image_processing_status', table_name='image')
43+
op.alter_column('imageprocessingjob', 'status',
44+
existing_type=sa.VARCHAR(length=20),
45+
nullable=False,
46+
existing_server_default=sa.text("'pending'::character varying"))
47+
op.alter_column('imageprocessingjob', 'retry_count',
48+
existing_type=sa.INTEGER(),
49+
nullable=False,
50+
existing_server_default=sa.text('0'))
51+
op.drop_index('ix_imageprocessingjob_image_id', table_name='imageprocessingjob')
52+
op.drop_index('ix_imageprocessingjob_status', table_name='imageprocessingjob')
53+
op.alter_column('imagevariant', 'file_size',
54+
existing_type=sa.BIGINT(),
55+
type_=sa.Integer(),
56+
existing_nullable=False)
57+
op.alter_column('imagevariant', 'quality',
58+
existing_type=sa.INTEGER(),
59+
nullable=False,
60+
existing_server_default=sa.text('85'))
61+
op.alter_column('imagevariant', 'format',
62+
existing_type=sa.VARCHAR(length=10),
63+
nullable=False,
64+
existing_server_default=sa.text("'jpeg'::character varying"))
65+
op.alter_column('imagevariant', 'created_at',
66+
existing_type=postgresql.TIMESTAMP(timezone=True),
67+
type_=sa.DateTime(),
68+
existing_nullable=True,
69+
existing_server_default=sa.text('now()'))
70+
op.drop_index('ix_imagevariant_image_id', table_name='imagevariant')
71+
op.drop_index('ix_imagevariant_variant_type', table_name='imagevariant')
72+
# ### end Alembic commands ###
73+
74+
75+
def downgrade():
76+
# ### commands auto generated by Alembic - please adjust! ###
77+
op.create_index('ix_imagevariant_variant_type', 'imagevariant', ['variant_type'], unique=False)
78+
op.create_index('ix_imagevariant_image_id', 'imagevariant', ['image_id'], unique=False)
79+
op.alter_column('imagevariant', 'created_at',
80+
existing_type=sa.DateTime(),
81+
type_=postgresql.TIMESTAMP(timezone=True),
82+
existing_nullable=True,
83+
existing_server_default=sa.text('now()'))
84+
op.alter_column('imagevariant', 'format',
85+
existing_type=sa.VARCHAR(length=10),
86+
nullable=True,
87+
existing_server_default=sa.text("'jpeg'::character varying"))
88+
op.alter_column('imagevariant', 'quality',
89+
existing_type=sa.INTEGER(),
90+
nullable=True,
91+
existing_server_default=sa.text('85'))
92+
op.alter_column('imagevariant', 'file_size',
93+
existing_type=sa.Integer(),
94+
type_=sa.BIGINT(),
95+
existing_nullable=False)
96+
op.create_index('ix_imageprocessingjob_status', 'imageprocessingjob', ['status'], unique=False)
97+
op.create_index('ix_imageprocessingjob_image_id', 'imageprocessingjob', ['image_id'], unique=False)
98+
op.alter_column('imageprocessingjob', 'retry_count',
99+
existing_type=sa.INTEGER(),
100+
nullable=True,
101+
existing_server_default=sa.text('0'))
102+
op.alter_column('imageprocessingjob', 'status',
103+
existing_type=sa.VARCHAR(length=20),
104+
nullable=True,
105+
existing_server_default=sa.text("'pending'::character varying"))
106+
op.create_index('ix_image_processing_status', 'image', ['processing_status'], unique=False)
107+
op.create_index('ix_image_owner_id', 'image', ['owner_id'], unique=False)
108+
op.create_index('ix_image_filename', 'image', ['filename'], unique=False)
109+
op.alter_column('image', 'updated_at',
110+
existing_type=sa.DateTime(),
111+
type_=postgresql.TIMESTAMP(timezone=True),
112+
existing_nullable=True,
113+
existing_server_default=sa.text('now()'))
114+
op.alter_column('image', 'created_at',
115+
existing_type=sa.DateTime(),
116+
type_=postgresql.TIMESTAMP(timezone=True),
117+
existing_nullable=True,
118+
existing_server_default=sa.text('now()'))
119+
op.alter_column('image', 'processing_status',
120+
existing_type=sa.VARCHAR(length=20),
121+
nullable=True,
122+
existing_server_default=sa.text("'pending'::character varying"))
123+
op.alter_column('image', 'file_size',
124+
existing_type=sa.Integer(),
125+
type_=sa.BIGINT(),
126+
existing_nullable=False)
127+
# ### end Alembic commands ###
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""Add images and image variants tables
2+
3+
Revision ID: db8bf70fc16a
4+
Revises: 1a31ce608336
5+
Create Date: 2025-11-19 03:59:49.501431
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
12+
13+
# revision identifiers, used by Alembic.
14+
revision = 'db8bf70fc16a'
15+
down_revision = '1a31ce608336'
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
op.create_table('image',
23+
sa.Column('filename', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
24+
sa.Column('original_filename', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
25+
sa.Column('content_type', sqlmodel.sql.sqltypes.AutoString(length=100), nullable=False),
26+
sa.Column('file_size', sa.BigInteger(), nullable=False),
27+
sa.Column('width', sa.Integer(), nullable=True),
28+
sa.Column('height', sa.Integer(), nullable=True),
29+
sa.Column('s3_bucket', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
30+
sa.Column('s3_key', sqlmodel.sql.sqltypes.AutoString(length=500), nullable=False),
31+
sa.Column('s3_url', sqlmodel.sql.sqltypes.AutoString(length=1000), nullable=False),
32+
sa.Column('processing_status', sqlmodel.sql.sqltypes.AutoString(length=20), nullable=True, server_default='pending'),
33+
sa.Column('alt_text', sqlmodel.sql.sqltypes.AutoString(length=500), nullable=True),
34+
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(length=1000), nullable=True),
35+
sa.Column('tags', sqlmodel.sql.sqltypes.AutoString(length=500), nullable=True),
36+
sa.Column('id', sa.UUID(), nullable=False),
37+
sa.Column('owner_id', sa.UUID(), nullable=False),
38+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
39+
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
40+
sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ondelete='CASCADE'),
41+
sa.PrimaryKeyConstraint('id')
42+
)
43+
op.create_index(op.f('ix_image_filename'), 'image', ['filename'], unique=False)
44+
op.create_index(op.f('ix_image_owner_id'), 'image', ['owner_id'], unique=False)
45+
op.create_index(op.f('ix_image_processing_status'), 'image', ['processing_status'], unique=False)
46+
47+
op.create_table('imagevariant',
48+
sa.Column('variant_type', sqlmodel.sql.sqltypes.AutoString(length=20), nullable=False),
49+
sa.Column('width', sa.Integer(), nullable=True),
50+
sa.Column('height', sa.Integer(), nullable=True),
51+
sa.Column('file_size', sa.BigInteger(), nullable=False),
52+
sa.Column('s3_bucket', sqlmodel.sql.sqltypes.AutoString(length=255), nullable=False),
53+
sa.Column('s3_key', sqlmodel.sql.sqltypes.AutoString(length=500), nullable=False),
54+
sa.Column('s3_url', sqlmodel.sql.sqltypes.AutoString(length=1000), nullable=False),
55+
sa.Column('quality', sa.Integer(), nullable=True, server_default='85'),
56+
sa.Column('format', sqlmodel.sql.sqltypes.AutoString(length=10), nullable=True, server_default='jpeg'),
57+
sa.Column('id', sa.UUID(), nullable=False),
58+
sa.Column('image_id', sa.UUID(), nullable=False),
59+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
60+
sa.ForeignKeyConstraint(['image_id'], ['image.id'], ondelete='CASCADE'),
61+
sa.PrimaryKeyConstraint('id')
62+
)
63+
op.create_index(op.f('ix_imagevariant_image_id'), 'imagevariant', ['image_id'], unique=False)
64+
op.create_index(op.f('ix_imagevariant_variant_type'), 'imagevariant', ['variant_type'], unique=False)
65+
66+
op.create_table('imageprocessingjob',
67+
sa.Column('status', sqlmodel.sql.sqltypes.AutoString(length=20), nullable=True, server_default='pending'),
68+
sa.Column('error_message', sqlmodel.sql.sqltypes.AutoString(length=1000), nullable=True),
69+
sa.Column('retry_count', sa.Integer(), nullable=True, server_default='0'),
70+
sa.Column('id', sa.UUID(), nullable=False),
71+
sa.Column('image_id', sa.UUID(), nullable=False),
72+
sa.ForeignKeyConstraint(['image_id'], ['image.id'], ondelete='CASCADE'),
73+
sa.PrimaryKeyConstraint('id')
74+
)
75+
op.create_index(op.f('ix_imageprocessingjob_image_id'), 'imageprocessingjob', ['image_id'], unique=False)
76+
op.create_index(op.f('ix_imageprocessingjob_status'), 'imageprocessingjob', ['status'], unique=False)
77+
# ### end Alembic commands ###
78+
79+
80+
def downgrade():
81+
# ### commands auto generated by Alembic - please adjust! ###
82+
op.drop_index(op.f('ix_imageprocessingjob_status'), table_name='imageprocessingjob')
83+
op.drop_index(op.f('ix_imageprocessingjob_image_id'), table_name='imageprocessingjob')
84+
op.drop_table('imageprocessingjob')
85+
op.drop_index(op.f('ix_imagevariant_variant_type'), table_name='imagevariant')
86+
op.drop_index(op.f('ix_imagevariant_image_id'), table_name='imagevariant')
87+
op.drop_table('imagevariant')
88+
op.drop_index(op.f('ix_image_processing_status'), table_name='image')
89+
op.drop_index(op.f('ix_image_owner_id'), table_name='image')
90+
op.drop_index(op.f('ix_image_filename'), table_name='image')
91+
op.drop_table('image')
92+
# ### end Alembic commands ###

0 commit comments

Comments
 (0)