@@ -26,8 +26,9 @@ import {
2626} from "~/components/primitives/Table" ;
2727import { prisma } from "~/db.server" ;
2828import { requireUser } from "~/services/session.server" ;
29- import { getSecretStore } from "~/services/secrets/secretStore.server" ;
3029import { ClickhouseConnectionSchema } from "~/services/clickhouse/clickhouseSecretSchemas.server" ;
30+ import { organizationDataStoresRegistry } from "~/services/dataStores/organizationDataStoresRegistryInstance.server" ;
31+ import { tryCatch } from "@trigger.dev/core" ;
3132
3233// ---------------------------------------------------------------------------
3334// Loader
@@ -55,79 +56,106 @@ const AddSchema = z.object({
5556 connectionUrl : z . string ( ) . url ( ) ,
5657} ) ;
5758
59+ const UpdateSchema = z . object ( {
60+ _action : z . literal ( "update" ) ,
61+ key : z . string ( ) . min ( 1 ) ,
62+ organizationIds : z . string ( ) . min ( 1 ) ,
63+ connectionUrl : z . string ( ) . url ( ) . optional ( ) ,
64+ } ) ;
65+
5866const DeleteSchema = z . object ( {
5967 _action : z . literal ( "delete" ) ,
60- id : z . string ( ) . min ( 1 ) ,
68+ key : z . string ( ) . min ( 1 ) ,
6169} ) ;
6270
71+ const FormSchema = z . discriminatedUnion ( "_action" , [ AddSchema , UpdateSchema , DeleteSchema ] ) ;
72+
6373export async function action ( { request } : ActionFunctionArgs ) {
6474 const user = await requireUser ( request ) ;
6575 if ( ! user . admin ) throw redirect ( "/" ) ;
6676
6777 const formData = await request . formData ( ) ;
68- const _action = formData . get ( "_action" ) ;
69-
70- if ( _action === "add" ) {
71- const result = AddSchema . safeParse ( Object . fromEntries ( formData ) ) ;
72- if ( ! result . success ) {
73- return typedjson (
74- { error : result . error . issues . map ( ( i ) => i . message ) . join ( ", " ) } ,
75- { status : 400 }
76- ) ;
77- }
7878
79- const { key, organizationIds : rawOrgIds , connectionUrl } = result . data ;
80- const organizationIds = rawOrgIds
81- . split ( "," )
82- . map ( ( s ) => s . trim ( ) )
83- . filter ( Boolean ) ;
79+ const result = FormSchema . safeParse ( Object . fromEntries ( formData ) ) ;
8480
85- const secretKey = `data-store:${ key } :clickhouse` ;
81+ if ( ! result . success ) {
82+ return typedjson (
83+ { error : result . error . issues . map ( ( i ) => i . message ) . join ( ", " ) } ,
84+ { status : 400 }
85+ ) ;
86+ }
8687
87- const secretStore = getSecretStore ( "DATABASE" ) ;
88- await secretStore . setSecret ( secretKey , ClickhouseConnectionSchema . parse ( { url : connectionUrl } ) ) ;
88+ switch ( result . data . _action ) {
89+ case "add" : {
90+ const { key, organizationIds : rawOrgIds , connectionUrl } = result . data ;
91+ const organizationIds = rawOrgIds
92+ . split ( "," )
93+ . map ( ( s ) => s . trim ( ) )
94+ . filter ( Boolean ) ;
95+
96+ const config = ClickhouseConnectionSchema . parse ( { url : connectionUrl } ) ;
97+
98+ const [ error , _ ] = await tryCatch (
99+ organizationDataStoresRegistry . addDataStore ( {
100+ key,
101+ kind : "CLICKHOUSE" ,
102+ organizationIds,
103+ config,
104+ } )
105+ ) ;
89106
90- await prisma . organizationDataStore . create ( {
91- data : {
92- key,
93- organizationIds,
94- kind : "CLICKHOUSE" ,
95- config : { version : 1 , data : { secretKey } } ,
96- } ,
97- } ) ;
107+ if ( error ) {
108+ return typedjson ( { error : error . message } , { status : 400 } ) ;
109+ }
98110
111+ return typedjson ( { success : true } ) ;
112+ }
113+ case "update" : {
114+ const { key, organizationIds : rawOrgIds , connectionUrl } = result . data ;
115+ const organizationIds = rawOrgIds
116+ . split ( "," )
117+ . map ( ( s ) => s . trim ( ) )
118+ . filter ( Boolean ) ;
119+
120+ const config = connectionUrl
121+ ? ClickhouseConnectionSchema . parse ( { url : connectionUrl } )
122+ : undefined ;
123+
124+ const [ error , _ ] = await tryCatch (
125+ organizationDataStoresRegistry . updateDataStore ( {
126+ key,
127+ kind : "CLICKHOUSE" ,
128+ organizationIds,
129+ config,
130+ } )
131+ ) ;
99132
100- return typedjson ( { success : true } ) ;
101- }
133+ if ( error ) {
134+ return typedjson ( { error : error . message } , { status : 400 } ) ;
135+ }
102136
103- if ( _action === "delete" ) {
104- const result = DeleteSchema . safeParse ( Object . fromEntries ( formData ) ) ;
105- if ( ! result . success ) {
106- return typedjson ( { error : "Invalid request" } , { status : 400 } ) ;
137+ return typedjson ( { success : true } ) ;
107138 }
139+ case "delete" : {
140+ const { key } = result . data ;
141+
142+ const [ error , _ ] = await tryCatch (
143+ organizationDataStoresRegistry . deleteDataStore ( {
144+ key,
145+ kind : "CLICKHOUSE" ,
146+ } )
147+ ) ;
108148
109- const { id } = result . data ;
149+ if ( error ) {
150+ return typedjson ( { error : error . message } , { status : 400 } ) ;
151+ }
110152
111- const dataStore = await prisma . organizationDataStore . findFirst ( { where : { id } } ) ;
112- if ( ! dataStore ) {
113- return typedjson ( { error : "Data store not found" } , { status : 404 } ) ;
153+ return typedjson ( { success : true } ) ;
114154 }
115-
116- // Delete secret if config references one
117- const config = dataStore . config as any ;
118- if ( config ?. data ?. secretKey ) {
119- const secretStore = getSecretStore ( "DATABASE" ) ;
120- await secretStore . deleteSecret ( config . data . secretKey ) . catch ( ( ) => {
121- // Secret may not exist — proceed with deletion
122- } ) ;
155+ default : {
156+ return typedjson ( { error : "Unknown action" } , { status : 400 } ) ;
123157 }
124-
125- await prisma . organizationDataStore . delete ( { where : { id } } ) ;
126-
127- return typedjson ( { success : true } ) ;
128158 }
129-
130- return typedjson ( { error : "Unknown action" } , { status : 400 } ) ;
131159}
132160
133161// ---------------------------------------------------------------------------
@@ -207,7 +235,7 @@ export default function AdminDataStoresRoute() {
207235 </ span >
208236 </ TableCell >
209237 < TableCell isSticky >
210- < DeleteButton id = { ds . id } name = { ds . key } />
238+ < DeleteButton name = { ds . key } />
211239 </ TableCell >
212240 </ TableRow >
213241 ) )
@@ -225,7 +253,7 @@ export default function AdminDataStoresRoute() {
225253// Delete button with popover confirmation
226254// ---------------------------------------------------------------------------
227255
228- function DeleteButton ( { id , name } : { id : string ; name : string } ) {
256+ function DeleteButton ( { name } : { name : string } ) {
229257 const [ open , setOpen ] = useState ( false ) ;
230258 const fetcher = useFetcher < { success ?: boolean ; error ?: string } > ( ) ;
231259 const isDeleting = fetcher . state !== "idle" ;
@@ -251,7 +279,7 @@ function DeleteButton({ id, name }: { id: string; name: string }) {
251279 </ Button >
252280 < fetcher . Form method = "post" onSubmit = { ( ) => setOpen ( false ) } >
253281 < input type = "hidden" name = "_action" value = "delete" />
254- < input type = "hidden" name = "id " value = { id } />
282+ < input type = "hidden" name = "key " value = { name } />
255283 < Button type = "submit" variant = "danger/small" >
256284 Confirm delete
257285 </ Button >
@@ -311,7 +339,13 @@ function AddDataStoreDialog({
311339 < label className = "text-xs font-medium text-text-dimmed" >
312340 Kind < span className = "text-rose-400" > *</ span >
313341 </ label >
314- < Input name = "kind" value = "CLICKHOUSE" readOnly variant = "medium" className = "opacity-60" />
342+ < Input
343+ name = "kind"
344+ value = "CLICKHOUSE"
345+ readOnly
346+ variant = "medium"
347+ className = "opacity-60"
348+ />
315349 </ div >
316350
317351 < div className = "space-y-1.5" >
@@ -344,9 +378,7 @@ function AddDataStoreDialog({
344378 </ p >
345379 </ div >
346380
347- { fetcher . data ?. error && (
348- < p className = "text-xs text-rose-400" > { fetcher . data . error } </ p >
349- ) }
381+ { fetcher . data ?. error && < p className = "text-xs text-rose-400" > { fetcher . data . error } </ p > }
350382
351383 < DialogFooter >
352384 < Button
0 commit comments