claude code

This commit is contained in:
2026-01-28 19:22:30 +01:00
parent f9b1d43c81
commit bdbfa4e25a
104 changed files with 9591 additions and 261 deletions

99
backend/alembic/env.py Normal file
View File

@@ -0,0 +1,99 @@
"""Environnement Alembic pour les migrations de base de données.
Ce fichier configure et exécute les migrations SQLAlchemy avec Alembic.
Il supporte les migrations synchrones et asynchrones.
"""
import asyncio
from logging.config import fileConfig
from sqlalchemy import pool
from sqlalchemy.engine import Connection
from sqlalchemy.ext.asyncio import async_engine_from_config
from alembic import context
# Import de la configuration et des modèles
from app.core.config import settings
from app.core.database import Base
# Import explicite de tous les modèles pour autogenerate
import app.models # noqa: F401
# Configuration Alembic
config = context.config
# Interpréter le fichier de configuration pour le logging Python
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# Métadonnées des modèles pour autogenerate
target_metadata = Base.metadata
# Override de l'URL de connexion depuis settings
config.set_main_option("sqlalchemy.url", settings.DATABASE_URL)
def run_migrations_offline() -> None:
"""Exécute les migrations en mode 'offline'.
Configure le contexte avec uniquement une URL sans créer d'Engine.
Les commandes SQL sont émises vers un fichier script au lieu d'être
exécutées directement sur la base de données.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
compare_type=True, # Détecte les changements de types
compare_server_default=True, # Détecte les changements de valeurs par défaut
)
with context.begin_transaction():
context.run_migrations()
def do_run_migrations(connection: Connection) -> None:
"""Exécute les migrations avec une connexion donnée."""
context.configure(
connection=connection,
target_metadata=target_metadata,
compare_type=True,
compare_server_default=True,
)
with context.begin_transaction():
context.run_migrations()
async def run_async_migrations() -> None:
"""Exécute les migrations en mode asynchrone.
Crée un moteur asynchrone et exécute les migrations.
"""
connectable = async_engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
async with connectable.connect() as connection:
await connection.run_sync(do_run_migrations)
await connectable.dispose()
def run_migrations_online() -> None:
"""Exécute les migrations en mode 'online'.
Crée un Engine et associe une connexion au contexte.
"""
asyncio.run(run_async_migrations())
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@@ -0,0 +1,26 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade() -> None:
"""Applique la migration (passage à la version suivante)."""
${upgrades if upgrades else "pass"}
def downgrade() -> None:
"""Annule la migration (retour à la version précédente)."""
${downgrades if downgrades else "pass"}

View File

@@ -0,0 +1,107 @@
"""Initial migration: create all tables
Revision ID: 8ba5962640dd
Revises:
Create Date: 2026-01-27 21:22:27.022127
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '8ba5962640dd'
down_revision = None
branch_labels = None
depends_on = None
def upgrade() -> None:
"""Applique la migration (passage à la version suivante)."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('categories',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('name', sa.String(length=100), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('color', sa.String(length=7), nullable=True),
sa.Column('icon', sa.String(length=50), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_categories_name'), 'categories', ['name'], unique=True)
op.create_table('locations',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('name', sa.String(length=100), nullable=False),
sa.Column('type', sa.Enum('ROOM', 'FURNITURE', 'DRAWER', 'BOX', name='locationtype', native_enum=False, length=20), nullable=False),
sa.Column('parent_id', sa.Integer(), nullable=True),
sa.Column('path', sa.String(length=500), nullable=False),
sa.Column('description', sa.String(length=500), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
sa.ForeignKeyConstraint(['parent_id'], ['locations.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_locations_name'), 'locations', ['name'], unique=False)
op.create_index(op.f('ix_locations_parent_id'), 'locations', ['parent_id'], unique=False)
op.create_index(op.f('ix_locations_path'), 'locations', ['path'], unique=False)
op.create_table('items',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('name', sa.String(length=200), nullable=False),
sa.Column('description', sa.Text(), nullable=True),
sa.Column('quantity', sa.Integer(), nullable=False),
sa.Column('status', sa.Enum('IN_STOCK', 'IN_USE', 'BROKEN', 'SOLD', 'LENT', name='itemstatus', native_enum=False, length=20), nullable=False),
sa.Column('brand', sa.String(length=100), nullable=True),
sa.Column('model', sa.String(length=100), nullable=True),
sa.Column('serial_number', sa.String(length=100), nullable=True),
sa.Column('price', sa.Numeric(precision=10, scale=2), nullable=True),
sa.Column('purchase_date', sa.Date(), nullable=True),
sa.Column('notes', sa.Text(), nullable=True),
sa.Column('category_id', sa.Integer(), nullable=False),
sa.Column('location_id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
sa.ForeignKeyConstraint(['category_id'], ['categories.id'], ondelete='RESTRICT'),
sa.ForeignKeyConstraint(['location_id'], ['locations.id'], ondelete='RESTRICT'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('serial_number')
)
op.create_index(op.f('ix_items_category_id'), 'items', ['category_id'], unique=False)
op.create_index(op.f('ix_items_location_id'), 'items', ['location_id'], unique=False)
op.create_index(op.f('ix_items_name'), 'items', ['name'], unique=False)
op.create_table('documents',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('filename', sa.String(length=255), nullable=False),
sa.Column('original_name', sa.String(length=255), nullable=False),
sa.Column('type', sa.Enum('PHOTO', 'MANUAL', 'INVOICE', 'WARRANTY', 'OTHER', name='documenttype', native_enum=False, length=20), nullable=False),
sa.Column('mime_type', sa.String(length=100), nullable=False),
sa.Column('size_bytes', sa.Integer(), nullable=False),
sa.Column('file_path', sa.String(length=500), nullable=False),
sa.Column('description', sa.String(length=500), nullable=True),
sa.Column('item_id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False),
sa.ForeignKeyConstraint(['item_id'], ['items.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('filename')
)
op.create_index(op.f('ix_documents_item_id'), 'documents', ['item_id'], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
"""Annule la migration (retour à la version précédente)."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_documents_item_id'), table_name='documents')
op.drop_table('documents')
op.drop_index(op.f('ix_items_name'), table_name='items')
op.drop_index(op.f('ix_items_location_id'), table_name='items')
op.drop_index(op.f('ix_items_category_id'), table_name='items')
op.drop_table('items')
op.drop_index(op.f('ix_locations_path'), table_name='locations')
op.drop_index(op.f('ix_locations_parent_id'), table_name='locations')
op.drop_index(op.f('ix_locations_name'), table_name='locations')
op.drop_table('locations')
op.drop_index(op.f('ix_categories_name'), table_name='categories')
op.drop_table('categories')
# ### end Alembic commands ###

View File

@@ -0,0 +1,30 @@
"""add_url_to_items
Revision ID: ee8035073398
Revises: 8ba5962640dd
Create Date: 2026-01-28 18:17:51.225223
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'ee8035073398'
down_revision = '8ba5962640dd'
branch_labels = None
depends_on = None
def upgrade() -> None:
"""Applique la migration (passage à la version suivante)."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('items', sa.Column('url', sa.String(length=500), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Annule la migration (retour à la version précédente)."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('items', 'url')
# ### end Alembic commands ###