Skip to content

Commit fdb5a1c

Browse files
authored
Merge pull request #468 from maximhq/09-14-configstore_updates_for_migrations
configstore updates for migrations
2 parents 1970e66 + b895f24 commit fdb5a1c

File tree

9 files changed

+706
-40
lines changed

9 files changed

+706
-40
lines changed

framework/configstore/internal/migration/migrator.go

Lines changed: 512 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package configstore
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/maximhq/bifrost/framework/configstore/internal/migration"
7+
"gorm.io/gorm"
8+
)
9+
10+
// Migrate performs the necessary database migrations.
11+
func triggerMigrations(db *gorm.DB) error {
12+
var err error
13+
err = migrationInit(db)
14+
if err != nil {
15+
return err
16+
}
17+
return nil
18+
}
19+
20+
// migrationInit is the first migration
21+
func migrationInit(db *gorm.DB) error {
22+
m := migration.New(db, migration.DefaultOptions, []*migration.Migration{{
23+
ID: "init",
24+
Migrate: func(tx *gorm.DB) error {
25+
migrator := tx.Migrator()
26+
if !migrator.HasTable(&TableConfigHash{}) {
27+
if err := migrator.CreateTable(&TableConfigHash{}); err != nil {
28+
return err
29+
}
30+
}
31+
if !migrator.HasTable(&TableProvider{}) {
32+
if err := migrator.CreateTable(&TableProvider{}); err != nil {
33+
return err
34+
}
35+
}
36+
if !migrator.HasTable(&TableKey{}) {
37+
if err := migrator.CreateTable(&TableKey{}); err != nil {
38+
return err
39+
}
40+
}
41+
if !migrator.HasTable(&TableModel{}) {
42+
if err := migrator.CreateTable(&TableModel{}); err != nil {
43+
return err
44+
}
45+
}
46+
if !migrator.HasTable(&TableMCPClient{}) {
47+
if err := migrator.CreateTable(&TableMCPClient{}); err != nil {
48+
return err
49+
}
50+
}
51+
if !migrator.HasTable(&TableClientConfig{}) {
52+
if err := migrator.CreateTable(&TableClientConfig{}); err != nil {
53+
return err
54+
}
55+
}
56+
if !migrator.HasTable(&TableEnvKey{}) {
57+
if err := migrator.CreateTable(&TableEnvKey{}); err != nil {
58+
return err
59+
}
60+
}
61+
if !migrator.HasTable(&TableVectorStoreConfig{}) {
62+
if err := migrator.CreateTable(&TableVectorStoreConfig{}); err != nil {
63+
return err
64+
}
65+
}
66+
if !migrator.HasTable(&TableLogStoreConfig{}) {
67+
if err := migrator.CreateTable(&TableLogStoreConfig{}); err != nil {
68+
return err
69+
}
70+
}
71+
if !migrator.HasTable(&TableBudget{}) {
72+
if err := migrator.CreateTable(&TableBudget{}); err != nil {
73+
return err
74+
}
75+
}
76+
if !migrator.HasTable(&TableRateLimit{}) {
77+
if err := migrator.CreateTable(&TableRateLimit{}); err != nil {
78+
return err
79+
}
80+
}
81+
if !migrator.HasTable(&TableCustomer{}) {
82+
if err := migrator.CreateTable(&TableCustomer{}); err != nil {
83+
return err
84+
}
85+
}
86+
if !migrator.HasTable(&TableTeam{}) {
87+
if err := migrator.CreateTable(&TableTeam{}); err != nil {
88+
return err
89+
}
90+
}
91+
if !migrator.HasTable(&TableVirtualKey{}) {
92+
if err := migrator.CreateTable(&TableVirtualKey{}); err != nil {
93+
return err
94+
}
95+
}
96+
if !migrator.HasTable(&TableConfig{}) {
97+
if err := migrator.CreateTable(&TableConfig{}); err != nil {
98+
return err
99+
}
100+
}
101+
if !migrator.HasTable(&TableModelPricing{}) {
102+
if err := migrator.CreateTable(&TableModelPricing{}); err != nil {
103+
return err
104+
}
105+
}
106+
if !migrator.HasTable(&TablePlugin{}) {
107+
if err := migrator.CreateTable(&TablePlugin{}); err != nil {
108+
return err
109+
}
110+
}
111+
return nil
112+
},
113+
Rollback: func(tx *gorm.DB) error {
114+
migrator := tx.Migrator()
115+
// Drop children first, then parents (adjust if your actual FKs differ)
116+
if err := migrator.DropTable(&TableVirtualKey{}); err != nil {
117+
return err
118+
}
119+
if err := migrator.DropTable(&TableKey{}); err != nil {
120+
return err
121+
}
122+
if err := migrator.DropTable(&TableTeam{}); err != nil {
123+
return err
124+
}
125+
if err := migrator.DropTable(&TableProvider{}); err != nil {
126+
return err
127+
}
128+
if err := migrator.DropTable(&TableCustomer{}); err != nil {
129+
return err
130+
}
131+
if err := migrator.DropTable(&TableBudget{}); err != nil {
132+
return err
133+
}
134+
if err := migrator.DropTable(&TableRateLimit{}); err != nil {
135+
return err
136+
}
137+
if err := migrator.DropTable(&TableModel{}); err != nil {
138+
return err
139+
}
140+
if err := migrator.DropTable(&TableMCPClient{}); err != nil {
141+
return err
142+
}
143+
if err := migrator.DropTable(&TableClientConfig{}); err != nil {
144+
return err
145+
}
146+
if err := migrator.DropTable(&TableEnvKey{}); err != nil {
147+
return err
148+
}
149+
if err := migrator.DropTable(&TableVectorStoreConfig{}); err != nil {
150+
return err
151+
}
152+
if err := migrator.DropTable(&TableLogStoreConfig{}); err != nil {
153+
return err
154+
}
155+
if err := migrator.DropTable(&TableConfig{}); err != nil {
156+
return err
157+
}
158+
if err := migrator.DropTable(&TableModelPricing{}); err != nil {
159+
return err
160+
}
161+
if err := migrator.DropTable(&TablePlugin{}); err != nil {
162+
return err
163+
}
164+
if err := migrator.DropTable(&TableConfigHash{}); err != nil {
165+
return err
166+
}
167+
return nil
168+
},
169+
}})
170+
err := m.Migrate()
171+
if err != nil {
172+
return fmt.Errorf("error while running db migration: %s", err.Error())
173+
}
174+
return nil
175+
}

framework/configstore/sqlite.go

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,10 +1227,10 @@ func (s *SQLiteConfigStore) removeDuplicateKeysAndNullKeys() error {
12271227
// Find and delete duplicate keys, keeping only the one with the smallest ID
12281228
// This query deletes all records except the one with the minimum ID for each (key_id, value) pair
12291229
result := s.db.Exec(`
1230-
DELETE FROM config_keys
1230+
DELETE FROM config_keys
12311231
WHERE id NOT IN (
1232-
SELECT MIN(id)
1233-
FROM config_keys
1232+
SELECT MIN(id)
1233+
FROM config_keys
12341234
GROUP BY key_id, value
12351235
)
12361236
`)
@@ -1269,25 +1269,7 @@ func newSqliteConfigStore(config *SQLiteConfig, logger schemas.Logger) (ConfigSt
12691269
return nil, fmt.Errorf("failed to remove duplicate keys: %w", err)
12701270
}
12711271
// Auto migrate to all new tables
1272-
if err := db.AutoMigrate(
1273-
&TableConfigHash{},
1274-
&TableProvider{},
1275-
&TableKey{},
1276-
&TableModel{},
1277-
&TableMCPClient{},
1278-
&TableClientConfig{},
1279-
&TableEnvKey{},
1280-
&TableVectorStoreConfig{},
1281-
&TableLogStoreConfig{},
1282-
&TableBudget{},
1283-
&TableRateLimit{},
1284-
&TableCustomer{},
1285-
&TableTeam{},
1286-
&TableVirtualKey{},
1287-
&TableConfig{},
1288-
&TableModelPricing{},
1289-
&TablePlugin{},
1290-
); err != nil {
1272+
if err := triggerMigrations(db); err != nil {
12911273
return nil, err
12921274
}
12931275
return s, nil

transports/bifrost-http/handlers/providers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func (h *ProviderHandler) listProviders(ctx *fasthttp.RequestCtx) {
7575
return
7676
}
7777

78-
var providerResponses []ProviderResponse
78+
providerResponses := []ProviderResponse{}
7979

8080
// Sort providers alphabetically
8181
sort.Slice(providers, func(i, j int) bool {

ui/app/providers/fragments/proxyFormFragment.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { AlertTriangle } from "lucide-react";
1616
import { useEffect } from "react";
1717
import { useForm } from "react-hook-form";
1818
import { toast } from "sonner";
19+
import { fi } from "zod/v4/locales";
1920

2021
interface ProxyFormFragmentProps {
2122
provider: ModelProvider;
@@ -31,7 +32,7 @@ export function ProxyFormFragment({ provider, showRestartAlert = false }: ProxyF
3132
reValidateMode: "onChange",
3233
defaultValues: {
3334
proxy_config: {
34-
type: provider.proxy_config?.type === "none" ? undefined : provider.proxy_config?.type,
35+
type: provider.proxy_config?.type,
3536
url: provider.proxy_config?.url || "",
3637
username: provider.proxy_config?.username || "",
3738
password: provider.proxy_config?.password || "",
@@ -46,7 +47,7 @@ export function ProxyFormFragment({ provider, showRestartAlert = false }: ProxyF
4647
useEffect(() => {
4748
form.reset({
4849
proxy_config: {
49-
type: provider.proxy_config?.type === "none" ? undefined : provider.proxy_config?.type,
50+
type: provider.proxy_config?.type,
5051
url: provider.proxy_config?.url || "",
5152
username: provider.proxy_config?.username || "",
5253
password: provider.proxy_config?.password || "",
@@ -67,6 +68,9 @@ export function ProxyFormFragment({ provider, showRestartAlert = false }: ProxyF
6768
},
6869
})
6970
.unwrap()
71+
.then(() => {
72+
toast.success("Provider configuration updated successfully");
73+
})
7074
.catch((err) => {
7175
toast.error("Failed to update provider configuration", {
7276
description: getErrorMessage(err),
@@ -96,7 +100,7 @@ export function ProxyFormFragment({ provider, showRestartAlert = false }: ProxyF
96100
render={({ field }) => (
97101
<FormItem>
98102
<FormLabel>Proxy Type</FormLabel>
99-
<Select onValueChange={field.onChange} defaultValue={field.value}>
103+
<Select onValueChange={field.onChange} value={field.value === "none" ? "" : field.value}>
100104
<FormControl>
101105
<SelectTrigger className="w-48">
102106
<SelectValue placeholder="Select type" />
@@ -172,8 +176,6 @@ export function ProxyFormFragment({ provider, showRestartAlert = false }: ProxyF
172176
type="button"
173177
variant="outline"
174178
onClick={() => {
175-
form.reset();
176-
// Saving configuration with none
177179
onSubmit({ proxy_config: { type: "none", url: "" } });
178180
}}
179181
disabled={isUpdatingProvider || !provider.proxy_config || provider.proxy_config.type === "none"}

ui/app/providers/page.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export default function Providers() {
3636
const [provider, setProvider] = useQueryState("provider");
3737

3838
const { data: savedProviders, isLoading: isLoadingProviders } = useGetProvidersQuery();
39-
4039
const [createProvider, { isLoading: creatingProvider }] = useCreateProviderMutation();
4140
const [getProvider, { isLoading: isLoadingProvider }] = useLazyGetProviderQuery();
4241

@@ -88,10 +87,10 @@ export default function Providers() {
8887
}, [provider]);
8988

9089
useEffect(() => {
91-
if (selectedProvider || !savedProviders || !customProviders) return;
92-
setProvider(savedProviders[0]?.name ?? customProviders[0]?.name ?? "");
90+
if (selectedProvider || !allProviders || allProviders.length === 0) return;
91+
setProvider(allProviders[0].name);
9392
// eslint-disable-next-line react-hooks/exhaustive-deps
94-
}, [selectedProvider, savedProviders, customProviders]);
93+
}, [selectedProvider, allProviders]);
9594

9695
if (isLoadingProviders) {
9796
return <FullPageLoader />;

ui/components/sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ const externalLinks = [
107107
},
108108
{
109109
title: "Full Documentation",
110-
url: "https://getmaxim.ai/bifrost/docs",
110+
url: "https://docs.getbifrost.ai",
111111
icon: BooksIcon,
112112
strokeWidth: 1,
113113
},

ui/components/ui/tagInput.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ export const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(({ cla
4646
};
4747

4848
return (
49-
<div className={cn("border-input flex flex-wrap items-center gap-2 rounded-sm border", className)}>
49+
<div className={cn("border-input dark:bg-accent flex flex-wrap items-center gap-2 rounded-sm border", className)}>
5050
<div className={cn("flex flex-row gap-2", value.length > 0 && "pl-2")}>
5151
{value.map((tag) => (
52-
<Badge key={tag} variant="secondary" className="flex items-center gap-1">
52+
<Badge key={tag} variant="secondary" className="bg-accent dark:bg-card flex items-center gap-1">
5353
{tag}
5454
<button
5555
type="button"
@@ -68,7 +68,7 @@ export const TagInput = React.forwardRef<HTMLInputElement, TagInputProps>(({ cla
6868
onChange={handleInputChange}
6969
onKeyDown={handleKeyDown}
7070
onBlur={handleBlur}
71-
className={cn("min-w-32 flex-1 border-0 shadow-none focus-visible:ring-0", value.length > 0 ? "px-0" : "px-1")}
71+
className={cn("dark:bg-accent min-w-32 flex-1 border-0 shadow-none focus-visible:ring-0", value.length > 0 ? "px-0" : "px-1")}
7272
{...props}
7373
/>
7474
</div>

ui/lib/store/slices/providerSlice.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,9 @@ const providerSlice = createSlice({
6060
// Listen to updateProvider fulfilled to update selected provider if it's the same one
6161
builder.addMatcher(providersApi.endpoints.updateProvider.matchFulfilled, (state, action) => {
6262
const updatedProvider = action.meta.arg.originalArgs;
63-
6463
// If the updated provider is the currently selected one, update it
6564
if (state.selectedProvider && updatedProvider.name === state.selectedProvider.name) {
6665
state.selectedProvider = updatedProvider;
67-
} else {
68-
// Selection no longer valid
69-
state.selectedProvider = null;
7066
}
7167
});
7268
},

0 commit comments

Comments
 (0)