mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-23 07:08:12 +02:00
Add files via upload
This commit is contained in:
72 files changed
+12339
No files matched your search
@@ -0,0 +1,82 @@
|
||||
from logging.config import fileConfig
|
||||
|
||||
from sqlalchemy import engine_from_config, pool
|
||||
|
||||
from alembic import context
|
||||
from app.database import DATABASE_URL
|
||||
from app.models import Base
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
# This line sets up loggers basically.
|
||||
if config.config_file_name is not None:
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
# add your model's MetaData object here
|
||||
# for 'autogenerate' support
|
||||
# from myapp import mymodel
|
||||
# target_metadata = mymodel.Base.metadata
|
||||
|
||||
|
||||
target_metadata = Base.metadata
|
||||
|
||||
# other values from the config, defined by the needs of env.py,
|
||||
# can be acquired:
|
||||
# my_important_option = config.get_main_option("my_important_option")
|
||||
# ... etc.
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
"""Run migrations in 'offline' mode.
|
||||
|
||||
This configures the context with just a URL
|
||||
and not an Engine, though an Engine is acceptable
|
||||
here as well. By skipping the Engine creation
|
||||
we don't even need a DBAPI to be available.
|
||||
|
||||
Calls to context.execute() here emit the given string to the
|
||||
script output.
|
||||
|
||||
"""
|
||||
url = DATABASE_URL
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={"paramstyle": "named"},
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
"""Run migrations in 'online' mode.
|
||||
|
||||
In this scenario we need to create an Engine
|
||||
and associate a connection with the context.
|
||||
|
||||
"""
|
||||
configuration = config.get_section(config.config_ini_section)
|
||||
configuration["sqlalchemy.url"] = DATABASE_URL
|
||||
|
||||
connectable = engine_from_config(
|
||||
configuration,
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
context.configure(connection=connection, target_metadata=target_metadata)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
@@ -0,0 +1,26 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = ${repr(up_revision)}
|
||||
down_revision: Union[str, None] = ${repr(down_revision)}
|
||||
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
||||
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
${downgrades if downgrades else "pass"}
|
||||
@@ -0,0 +1,37 @@
|
||||
"""Add refresh fields
|
||||
|
||||
Revision ID: 6ecb6f39fd8c
|
||||
Revises: initial_migration
|
||||
Create Date: 2025-11-19 12:53:09.412700
|
||||
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
from app.models import Base
|
||||
|
||||
target_metadata = Base.metadata
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "6ecb6f39fd8c"
|
||||
down_revision: str | None = "initial_migration"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column("items", sa.Column("is_refreshing", sa.Boolean(), nullable=True))
|
||||
op.add_column("items", sa.Column("last_error", sa.String(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column("items", "last_error")
|
||||
op.drop_column("items", "is_refreshing")
|
||||
# ### end Alembic commands ###
|
||||
@@ -0,0 +1,39 @@
|
||||
"""add_performance_indexes
|
||||
|
||||
Revision ID: 7f8e9d2a1b3c
|
||||
Revises: a1b2c3d4e5f6
|
||||
Create Date: 2025-11-19 19:14:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "7f8e9d2a1b3c"
|
||||
down_revision = "a1b2c3d4e5f6"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# Add indexes for performance optimization
|
||||
# Items table indexes
|
||||
op.create_index("ix_items_last_checked", "items", ["last_checked"])
|
||||
op.create_index("ix_items_is_active", "items", ["is_active"])
|
||||
# Note: url already has an index from the model definition
|
||||
|
||||
# Composite index for common query pattern (active items due for refresh)
|
||||
op.create_index("ix_items_active_last_checked", "items", ["is_active", "last_checked"])
|
||||
|
||||
# Price history indexes for time-series queries
|
||||
op.create_index("ix_price_history_timestamp", "price_history", ["timestamp"])
|
||||
op.create_index("ix_price_history_item_timestamp", "price_history", ["item_id", "timestamp"])
|
||||
|
||||
|
||||
def downgrade():
|
||||
# Remove indexes in reverse order
|
||||
op.drop_index("ix_price_history_item_timestamp", table_name="price_history")
|
||||
op.drop_index("ix_price_history_timestamp", table_name="price_history")
|
||||
op.drop_index("ix_items_active_last_checked", table_name="items")
|
||||
op.drop_index("ix_items_is_active", table_name="items")
|
||||
op.drop_index("ix_items_last_checked", table_name="items")
|
||||
@@ -0,0 +1,55 @@
|
||||
"""Add confidence and AI metadata
|
||||
|
||||
Revision ID: a1b2c3d4e5f6
|
||||
Revises: 6ecb6f39fd8c
|
||||
Create Date: 2025-11-19 13:55:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
from app.models import Base
|
||||
|
||||
target_metadata = Base.metadata
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "a1b2c3d4e5f6"
|
||||
down_revision: str | None = "6ecb6f39fd8c"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Add confidence scores and AI metadata to price_history and items tables."""
|
||||
|
||||
# Add columns to price_history table
|
||||
op.add_column("price_history", sa.Column("price_confidence", sa.Float(), nullable=True))
|
||||
op.add_column("price_history", sa.Column("in_stock_confidence", sa.Float(), nullable=True))
|
||||
op.add_column("price_history", sa.Column("ai_model", sa.String(), nullable=True))
|
||||
op.add_column("price_history", sa.Column("ai_provider", sa.String(), nullable=True))
|
||||
op.add_column("price_history", sa.Column("prompt_version", sa.String(), nullable=True))
|
||||
op.add_column("price_history", sa.Column("repair_used", sa.Boolean(), nullable=True, server_default="false"))
|
||||
|
||||
# Add columns to items table for quick access to latest confidence
|
||||
op.add_column("items", sa.Column("current_price_confidence", sa.Float(), nullable=True))
|
||||
op.add_column("items", sa.Column("in_stock_confidence", sa.Float(), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Remove confidence scores and AI metadata columns."""
|
||||
|
||||
# Remove columns from items table
|
||||
op.drop_column("items", "in_stock_confidence")
|
||||
op.drop_column("items", "current_price_confidence")
|
||||
|
||||
# Remove columns from price_history table
|
||||
op.drop_column("price_history", "repair_used")
|
||||
op.drop_column("price_history", "prompt_version")
|
||||
op.drop_column("price_history", "ai_provider")
|
||||
op.drop_column("price_history", "ai_model")
|
||||
op.drop_column("price_history", "in_stock_confidence")
|
||||
op.drop_column("price_history", "price_confidence")
|
||||
@@ -0,0 +1,116 @@
|
||||
"""Initial migration
|
||||
|
||||
Revision ID: initial_migration
|
||||
Revises:
|
||||
Create Date: 2023-11-19 00:00:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "initial_migration"
|
||||
down_revision: str | None = None
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# We are assuming the database is already created by the previous code
|
||||
# But for a fresh install, we need to create tables.
|
||||
# However, since we are retrofitting, we should use --autogenerate usually.
|
||||
# But I cannot run that easily.
|
||||
# So I will define the tables here to be safe.
|
||||
|
||||
# Check if tables exist before creating to avoid errors on existing DBs
|
||||
# Actually, standard alembic practice is to define them.
|
||||
# But since we had `Base.metadata.create_all`, tables might exist.
|
||||
# Ideally we would use `alembic stamp head` on existing deployments.
|
||||
|
||||
# For now, I will leave this empty and assume the user will run
|
||||
# `alembic revision --autogenerate` if they want to capture the state,
|
||||
# or I should try to capture it.
|
||||
# Given I can't run the DB, I'll leave it as a placeholder or try to define it.
|
||||
# Let's define it properly so new installs work.
|
||||
|
||||
# ... actually, writing all create_table calls manually is error prone without running it.
|
||||
# I will rely on the fact that I removed `create_all` from main.py, so this MUST do it.
|
||||
|
||||
op.create_table(
|
||||
"notification_profiles",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("name", sa.String(), nullable=True),
|
||||
sa.Column("apprise_url", sa.String(), nullable=True),
|
||||
sa.Column("notify_on_price_drop", sa.Boolean(), nullable=True),
|
||||
sa.Column("notify_on_target_price", sa.Boolean(), nullable=True),
|
||||
sa.Column("price_drop_threshold_percent", sa.Float(), nullable=True),
|
||||
sa.Column("notify_on_stock_change", sa.Boolean(), nullable=True),
|
||||
sa.Column("check_interval_minutes", sa.Integer(), nullable=True),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_notification_profiles_id"), "notification_profiles", ["id"], unique=False)
|
||||
op.create_index(op.f("ix_notification_profiles_name"), "notification_profiles", ["name"], unique=True)
|
||||
|
||||
op.create_table(
|
||||
"items",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("url", sa.String(), nullable=True),
|
||||
sa.Column("name", sa.String(), nullable=True),
|
||||
sa.Column("selector", sa.String(), nullable=True),
|
||||
sa.Column("target_price", sa.Float(), nullable=True),
|
||||
sa.Column("check_interval_minutes", sa.Integer(), nullable=True),
|
||||
sa.Column("current_price", sa.Float(), nullable=True),
|
||||
sa.Column("in_stock", sa.Boolean(), nullable=True),
|
||||
sa.Column("tags", sa.String(), nullable=True),
|
||||
sa.Column("description", sa.String(), nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=True),
|
||||
sa.Column("last_checked", sa.DateTime(), nullable=True),
|
||||
sa.Column("notification_profile_id", sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["notification_profile_id"],
|
||||
["notification_profiles.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_items_id"), "items", ["id"], unique=False)
|
||||
op.create_index(op.f("ix_items_url"), "items", ["url"], unique=False)
|
||||
|
||||
op.create_table(
|
||||
"price_history",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("item_id", sa.Integer(), nullable=True),
|
||||
sa.Column("price", sa.Float(), nullable=True),
|
||||
sa.Column("timestamp", sa.DateTime(), nullable=True),
|
||||
sa.Column("screenshot_path", sa.String(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["item_id"],
|
||||
["items.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("ix_price_history_id"), "price_history", ["id"], unique=False)
|
||||
|
||||
op.create_table(
|
||||
"settings",
|
||||
sa.Column("key", sa.String(), nullable=False),
|
||||
sa.Column("value", sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint("key"),
|
||||
)
|
||||
op.create_index(op.f("ix_settings_key"), "settings", ["key"], unique=False)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index(op.f("ix_settings_key"), table_name="settings")
|
||||
op.drop_table("settings")
|
||||
op.drop_index(op.f("ix_price_history_id"), table_name="price_history")
|
||||
op.drop_table("price_history")
|
||||
op.drop_index(op.f("ix_items_url"), table_name="items")
|
||||
op.drop_index(op.f("ix_items_id"), table_name="items")
|
||||
op.drop_table("items")
|
||||
op.drop_index(op.f("ix_notification_profiles_name"), table_name="notification_profiles")
|
||||
op.drop_index(op.f("ix_notification_profiles_id"), table_name="notification_profiles")
|
||||
op.drop_table("notification_profiles")
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Add performance indexes
|
||||
|
||||
Revision ID: performance_indexes_v2
|
||||
Revises: 7f8e9d2a1b3c
|
||||
Create Date: 2025-11-19
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "performance_indexes_v2"
|
||||
down_revision = "7f8e9d2a1b3c"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# Add indexes only if they don't exist
|
||||
# Using raw SQL with IF NOT EXISTS for safety
|
||||
op.execute(
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS ix_price_history_timestamp
|
||||
ON price_history (timestamp)
|
||||
"""
|
||||
)
|
||||
|
||||
op.execute(
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS ix_price_history_item_timestamp
|
||||
ON price_history (item_id, timestamp)
|
||||
"""
|
||||
)
|
||||
|
||||
op.execute(
|
||||
"""
|
||||
CREATE INDEX IF NOT EXISTS ix_items_is_active
|
||||
ON items (is_active)
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_index("ix_items_is_active", table_name="items", if_exists=True)
|
||||
op.drop_index("ix_price_history_item_timestamp", table_name="price_history", if_exists=True)
|
||||
op.drop_index("ix_price_history_timestamp", table_name="price_history", if_exists=True)
|
||||
Reference in new issue
Block a user