Skip to content

Conversation

@danielwpz
Copy link
Contributor

@danielwpz danielwpz commented Nov 20, 2025

Note

Refactors model history to a richer table and repository with audit tracking, updates admin APIs to record/expose changes (including soft delete reason), and adds seed tooling, tests, and Makefile/README updates.

  • Database:
    • Migration: Rename model_pricing_historymodel_history; add model_name, model_icon, verifiable, is_active, changed_by_user_id, changed_by_user_email; drop trigger and changed_by.
    • Repositories: Record history in application code within transactions (close previous, insert new); support soft delete with default/custom changeReason; new history getters and mappings.
    • Tooling: Add seed binary to run migrations and SQL seeds; add mock admin seed (001__seed_mock_admin.sql); add tracing-subscriber dep.
  • API/Services:
    • Extend UpdateModelApiRequest with changeReason; add DeleteModelRequest (optional reason).
    • Admin routes now pass changed_by_user_id/email and changeReason on upsert/delete; delete accepts optional body; history response includes modelName, modelIcon, verifiable, isActive, changedByUserId/Email.
    • Update service/ports to carry audit fields across layers.
  • Tests:
    • Add E2E tests for model history creation, update sequencing, soft delete (with/without reason), and user audit tracking.
  • Tooling/Docs:
    • New Makefile (dev/test/lint/fmt/preflight) and README updates to use make commands.

Written by Cursor Bugbot for commit e782ea5. This will update automatically on new commits. Configure here.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is being reviewed by Cursor Bugbot

Details

Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refactors the model pricing history table into a comprehensive model history table that captures complete snapshots of model state changes with full audit tracking capabilities.

Key Changes:

  • Renamed model_pricing_history to model_history and expanded it to include all model fields (name, icon, verifiable, is_active) for complete state snapshots
  • Replaced generic changed_by string field with structured user audit fields (changed_by_user_id, changed_by_user_email)
  • Updated model upsert and soft delete operations to automatically record history with change reasons and user context
  • Added changeReason parameter to admin model update/delete APIs and DeleteModelRequest type for optional delete reasons

Reviewed Changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/database/src/migrations/sql/V0023__refactor_model_pricing_history_to_model_history.sql Migration to rename table, add new columns (model_name, model_icon, verifiable, is_active), backfill data, rename indexes, drop old trigger/function, and add user audit columns
crates/database/src/models.rs Renamed ModelPricingHistory to ModelHistory and added new fields (model_name, model_icon, verifiable, is_active, changed_by_user_id, changed_by_user_email) plus audit fields to UpdateModelPricingRequest
crates/database/src/repositories/model.rs Added record_model_history helper method to close previous history and insert new snapshots; updated upsert_model_pricing and soft_delete_model to record history; renamed query methods; added default soft delete reason constant
crates/database/src/repositories/admin_composite.rs Updated to pass through new audit fields (change_reason, changed_by_user_id, changed_by_user_email) and map expanded history fields in responses
crates/services/src/admin/ports.rs Added audit fields to UpdateModelAdminRequest, expanded ModelHistoryEntry with new fields, and updated soft_delete_model and delete_model signatures to accept audit parameters
crates/services/src/admin/mod.rs Updated delete_model implementation to accept and pass through change_reason and user audit parameters
crates/api/src/routes/admin.rs Extract admin user context in batch_upsert_models and delete_model; pass user ID/email for audit tracking; updated get_model_history response mapping; added DeleteModelRequest body parameter
crates/api/src/models.rs Added changeReason field to UpdateModelApiRequest, created DeleteModelRequest type, and expanded ModelHistoryEntry with new fields matching database schema
crates/api/tests/model_history_test.rs Comprehensive E2E tests covering history creation, updates with proper ordering, soft delete with/without custom reasons, user tracking, and progression through multiple updates

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@danielwpz danielwpz temporarily deployed to Cloud API test env November 26, 2025 02:39 — with GitHub Actions Inactive
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 15 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.

Comment on lines 300 to 307
input_cost_per_token = COALESCE(EXCLUDED.input_cost_per_token, models.input_cost_per_token),
output_cost_per_token = COALESCE(EXCLUDED.output_cost_per_token, models.output_cost_per_token),
model_display_name = COALESCE(EXCLUDED.model_display_name, models.model_display_name),
model_description = COALESCE(EXCLUDED.model_description, models.model_description),
model_icon = COALESCE(EXCLUDED.model_icon, models.model_icon),
context_length = COALESCE(EXCLUDED.context_length, models.context_length),
verifiable = COALESCE(EXCLUDED.verifiable, models.verifiable),
is_active = COALESCE(EXCLUDED.is_active, models.is_active),
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using COALESCE(EXCLUDED.*, models.*) in the ON CONFLICT UPDATE clause means that NULL values in the insert will preserve existing values. However, this conflicts with the partial update pattern used in the UPDATE branch (lines 226-235) where NULL means 'no change'. For consistency and to avoid confusion, the INSERT path should use the same semantics. Consider using separate code paths or explicitly handling which fields can be NULL vs which represent 'no update'.

Suggested change
input_cost_per_token = COALESCE(EXCLUDED.input_cost_per_token, models.input_cost_per_token),
output_cost_per_token = COALESCE(EXCLUDED.output_cost_per_token, models.output_cost_per_token),
model_display_name = COALESCE(EXCLUDED.model_display_name, models.model_display_name),
model_description = COALESCE(EXCLUDED.model_description, models.model_description),
model_icon = COALESCE(EXCLUDED.model_icon, models.model_icon),
context_length = COALESCE(EXCLUDED.context_length, models.context_length),
verifiable = COALESCE(EXCLUDED.verifiable, models.verifiable),
is_active = COALESCE(EXCLUDED.is_active, models.is_active),
input_cost_per_token = CASE WHEN EXCLUDED.input_cost_per_token IS NOT NULL THEN EXCLUDED.input_cost_per_token ELSE models.input_cost_per_token END,
output_cost_per_token = CASE WHEN EXCLUDED.output_cost_per_token IS NOT NULL THEN EXCLUDED.output_cost_per_token ELSE models.output_cost_per_token END,
model_display_name = CASE WHEN EXCLUDED.model_display_name IS NOT NULL THEN EXCLUDED.model_display_name ELSE models.model_display_name END,
model_description = CASE WHEN EXCLUDED.model_description IS NOT NULL THEN EXCLUDED.model_description ELSE models.model_description END,
model_icon = CASE WHEN EXCLUDED.model_icon IS NOT NULL THEN EXCLUDED.model_icon ELSE models.model_icon END,
context_length = CASE WHEN EXCLUDED.context_length IS NOT NULL THEN EXCLUDED.context_length ELSE models.context_length END,
verifiable = CASE WHEN EXCLUDED.verifiable IS NOT NULL THEN EXCLUDED.verifiable ELSE models.verifiable END,
is_active = CASE WHEN EXCLUDED.is_active IS NOT NULL THEN EXCLUDED.is_active ELSE models.is_active END,

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed via a shared table lock

Makefile Outdated
@echo "Formatting code with rustfmt..."
cargo fmt

check-clippy:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check-clippy is identical to lint?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated

@@ -0,0 +1,100 @@
use std::fs;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we move the seed related code and SQLs to tests folder as it's only needed for testing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's also used by local development as well, when you try to create a new model

@danielwpz danielwpz temporarily deployed to Cloud API test env December 1, 2025 10:08 — with GitHub Actions Inactive
@danielwpz danielwpz temporarily deployed to Cloud API test env December 1, 2025 10:25 — with GitHub Actions Inactive
@@ -0,0 +1,94 @@
.PHONY: help seed api dev build test test-unit test-integration lint fmt check-fmt preflight clean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haven't used that one before, if it provides more benefits than makefile we can definitely switch to it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants