diff --git a/.sqlx/query-0701c93547a515170803136ed093c06df78d08e83c9f2eee241204820fd20f5c.json b/.sqlx/query-0701c93547a515170803136ed093c06df78d08e83c9f2eee241204820fd20f5c.json new file mode 100644 index 0000000..b6b95cc --- /dev/null +++ b/.sqlx/query-0701c93547a515170803136ed093c06df78d08e83c9f2eee241204820fd20f5c.json @@ -0,0 +1,56 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n id,\n slug,\n display_name,\n status,\n settings_json,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\"\n from workspaces\n order by slug asc", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "slug", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "display_name", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "settings_json", + "type_info": "Jsonb" + }, + { + "ordinal": 5, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "updated_at!", + "type_info": "Text" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + false, + false, + null, + null + ] + }, + "hash": "0701c93547a515170803136ed093c06df78d08e83c9f2eee241204820fd20f5c" +} diff --git a/.sqlx/query-0c383ab1d4c5c178f9ded8219a81da145a4ba6198a67de5c7e060803619ad7fc.json b/.sqlx/query-0c383ab1d4c5c178f9ded8219a81da145a4ba6198a67de5c7e060803619ad7fc.json new file mode 100644 index 0000000..d0f7393 --- /dev/null +++ b/.sqlx/query-0c383ab1d4c5c178f9ded8219a81da145a4ba6198a67de5c7e060803619ad7fc.json @@ -0,0 +1,94 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n id,\n workspace_id,\n agent_id,\n operation_id,\n protocol,\n mode,\n status,\n cursor_json,\n state_json,\n to_char(expires_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"expires_at!\",\n to_char(last_poll_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_poll_at,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(closed_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as closed_at\n from stream_sessions\n where id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "workspace_id", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "agent_id", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "operation_id", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "protocol", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "mode", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "cursor_json", + "type_info": "Jsonb" + }, + { + "ordinal": 8, + "name": "state_json", + "type_info": "Jsonb" + }, + { + "ordinal": 9, + "name": "expires_at!", + "type_info": "Text" + }, + { + "ordinal": 10, + "name": "last_poll_at", + "type_info": "Text" + }, + { + "ordinal": 11, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 12, + "name": "closed_at", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + true, + false, + false, + false, + false, + true, + false, + null, + null, + null, + null + ] + }, + "hash": "0c383ab1d4c5c178f9ded8219a81da145a4ba6198a67de5c7e060803619ad7fc" +} diff --git a/.sqlx/query-2b0b383bd209c6a94d6a422fbd52bf63338c048238c3ff63c44eddf6a56b8a0b.json b/.sqlx/query-2b0b383bd209c6a94d6a422fbd52bf63338c048238c3ff63c44eddf6a56b8a0b.json new file mode 100644 index 0000000..6e246ae --- /dev/null +++ b/.sqlx/query-2b0b383bd209c6a94d6a422fbd52bf63338c048238c3ff63c44eddf6a56b8a0b.json @@ -0,0 +1,65 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n k.id,\n k.workspace_id,\n k.name,\n k.prefix,\n k.scopes_json,\n k.status,\n to_char(k.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(k.last_used_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_used_at\n from platform_api_keys k\n join workspaces w on w.id = k.workspace_id\n where w.slug = $1\n and k.secret_hash = $2\n and k.status = 'active'\n limit 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "workspace_id", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "prefix", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "scopes_json", + "type_info": "Jsonb" + }, + { + "ordinal": 5, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "last_used_at", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + null, + null + ] + }, + "hash": "2b0b383bd209c6a94d6a422fbd52bf63338c048238c3ff63c44eddf6a56b8a0b" +} diff --git a/.sqlx/query-4206303b16bfbe2abc436d00ad5da3358246a78c0bb283321c9441e7eea68467.json b/.sqlx/query-4206303b16bfbe2abc436d00ad5da3358246a78c0bb283321c9441e7eea68467.json new file mode 100644 index 0000000..316c170 --- /dev/null +++ b/.sqlx/query-4206303b16bfbe2abc436d00ad5da3358246a78c0bb283321c9441e7eea68467.json @@ -0,0 +1,64 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n w.id,\n w.slug,\n w.display_name,\n w.status,\n w.settings_json,\n to_char(w.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(w.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",\n m.role\n from memberships m\n join workspaces w on w.id = m.workspace_id\n where m.user_id = $1\n order by w.slug asc", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "slug", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "display_name", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "settings_json", + "type_info": "Jsonb" + }, + { + "ordinal": 5, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "updated_at!", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "role", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + null, + null, + false + ] + }, + "hash": "4206303b16bfbe2abc436d00ad5da3358246a78c0bb283321c9441e7eea68467" +} diff --git a/.sqlx/query-4e2091cb75e67ac455b0efee795e94b8a18807a59a0f11796fec27519f34817d.json b/.sqlx/query-4e2091cb75e67ac455b0efee795e94b8a18807a59a0f11796fec27519f34817d.json new file mode 100644 index 0000000..6a3e3c5 --- /dev/null +++ b/.sqlx/query-4e2091cb75e67ac455b0efee795e94b8a18807a59a0f11796fec27519f34817d.json @@ -0,0 +1,99 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n id,\n workspace_id,\n agent_id,\n operation_id,\n protocol,\n mode,\n status,\n cursor_json,\n state_json,\n to_char(expires_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"expires_at!\",\n to_char(last_poll_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_poll_at,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(closed_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as closed_at\n from stream_sessions\n where workspace_id = $1\n and ($2::text is null or agent_id = $2)\n and ($3::text is null or operation_id = $3)\n and ($4::text is null or status = $4)\n and ($5::text is null or mode = $5)\n order by created_at desc\n limit $6", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "workspace_id", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "agent_id", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "operation_id", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "protocol", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "mode", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "cursor_json", + "type_info": "Jsonb" + }, + { + "ordinal": 8, + "name": "state_json", + "type_info": "Jsonb" + }, + { + "ordinal": 9, + "name": "expires_at!", + "type_info": "Text" + }, + { + "ordinal": 10, + "name": "last_poll_at", + "type_info": "Text" + }, + { + "ordinal": 11, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 12, + "name": "closed_at", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Text", + "Text", + "Int8" + ] + }, + "nullable": [ + false, + false, + true, + false, + false, + false, + false, + true, + false, + null, + null, + null, + null + ] + }, + "hash": "4e2091cb75e67ac455b0efee795e94b8a18807a59a0f11796fec27519f34817d" +} diff --git a/.sqlx/query-6f55f9867b0fdaeedaa5995462e70e3b2118fb595c8d6e313b5f0271c0f5d537.json b/.sqlx/query-6f55f9867b0fdaeedaa5995462e70e3b2118fb595c8d6e313b5f0271c0f5d537.json new file mode 100644 index 0000000..19beba0 --- /dev/null +++ b/.sqlx/query-6f55f9867b0fdaeedaa5995462e70e3b2118fb595c8d6e313b5f0271c0f5d537.json @@ -0,0 +1,92 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n id,\n workspace_id,\n agent_id,\n operation_id,\n status,\n progress_json,\n result_json,\n error_json,\n to_char(expires_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as expires_at,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",\n to_char(finished_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as finished_at\n from async_jobs\n where workspace_id = $1\n and ($2::text is null or agent_id = $2)\n and ($3::text is null or operation_id = $3)\n and ($4::text is null or status = $4)\n order by updated_at desc\n limit $5", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "workspace_id", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "agent_id", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "operation_id", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "progress_json", + "type_info": "Jsonb" + }, + { + "ordinal": 6, + "name": "result_json", + "type_info": "Jsonb" + }, + { + "ordinal": 7, + "name": "error_json", + "type_info": "Jsonb" + }, + { + "ordinal": 8, + "name": "expires_at", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 10, + "name": "updated_at!", + "type_info": "Text" + }, + { + "ordinal": 11, + "name": "finished_at", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Text", + "Int8" + ] + }, + "nullable": [ + false, + false, + true, + false, + false, + false, + true, + true, + null, + null, + null, + null + ] + }, + "hash": "6f55f9867b0fdaeedaa5995462e70e3b2118fb595c8d6e313b5f0271c0f5d537" +} diff --git a/.sqlx/query-925b63209407fe86b78bf5458b1a17253db129a7a13a6c7bc4db4f439244009c.json b/.sqlx/query-925b63209407fe86b78bf5458b1a17253db129a7a13a6c7bc4db4f439244009c.json new file mode 100644 index 0000000..480271e --- /dev/null +++ b/.sqlx/query-925b63209407fe86b78bf5458b1a17253db129a7a13a6c7bc4db4f439244009c.json @@ -0,0 +1,58 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n id,\n slug,\n display_name,\n status,\n settings_json,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\"\n from workspaces\n where id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "slug", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "display_name", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "settings_json", + "type_info": "Jsonb" + }, + { + "ordinal": 5, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "updated_at!", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + null, + null + ] + }, + "hash": "925b63209407fe86b78bf5458b1a17253db129a7a13a6c7bc4db4f439244009c" +} diff --git a/.sqlx/query-b69d55c4b0352cac007496828e2d08369749bac52d2076c3ba679c4aaa466fc9.json b/.sqlx/query-b69d55c4b0352cac007496828e2d08369749bac52d2076c3ba679c4aaa466fc9.json new file mode 100644 index 0000000..cf8c9da --- /dev/null +++ b/.sqlx/query-b69d55c4b0352cac007496828e2d08369749bac52d2076c3ba679c4aaa466fc9.json @@ -0,0 +1,64 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n id,\n workspace_id,\n email,\n role,\n status,\n token_hash,\n to_char(expires_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"expires_at!\",\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\"\n from invitation_tokens\n where workspace_id = $1\n order by created_at desc", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "workspace_id", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "role", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "token_hash", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "expires_at!", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "created_at!", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + null, + null + ] + }, + "hash": "b69d55c4b0352cac007496828e2d08369749bac52d2076c3ba679c4aaa466fc9" +} diff --git a/.sqlx/query-d485e119dd288962586c35b7585c74c6c3518f3d3995ea21a22cc35e092da147.json b/.sqlx/query-d485e119dd288962586c35b7585c74c6c3518f3d3995ea21a22cc35e092da147.json new file mode 100644 index 0000000..2d870be --- /dev/null +++ b/.sqlx/query-d485e119dd288962586c35b7585c74c6c3518f3d3995ea21a22cc35e092da147.json @@ -0,0 +1,64 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n m.workspace_id,\n m.user_id,\n m.role,\n to_char(m.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n u.email,\n u.display_name,\n u.status,\n to_char(u.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"user_created_at!\"\n from memberships m\n join users u on u.id = m.user_id\n where m.workspace_id = $1\n order by u.email asc", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "user_id", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "role", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "display_name", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "user_created_at!", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + null, + false, + false, + false, + null + ] + }, + "hash": "d485e119dd288962586c35b7585c74c6c3518f3d3995ea21a22cc35e092da147" +} diff --git a/.sqlx/query-e92340e11c1b1d5dcd1441f12588e01fa18acc43e18316ea9789cd60e4967eaf.json b/.sqlx/query-e92340e11c1b1d5dcd1441f12588e01fa18acc43e18316ea9789cd60e4967eaf.json new file mode 100644 index 0000000..49f0f2a --- /dev/null +++ b/.sqlx/query-e92340e11c1b1d5dcd1441f12588e01fa18acc43e18316ea9789cd60e4967eaf.json @@ -0,0 +1,88 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n id,\n workspace_id,\n agent_id,\n operation_id,\n status,\n progress_json,\n result_json,\n error_json,\n to_char(expires_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as expires_at,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",\n to_char(finished_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as finished_at\n from async_jobs\n where id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "workspace_id", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "agent_id", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "operation_id", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "progress_json", + "type_info": "Jsonb" + }, + { + "ordinal": 6, + "name": "result_json", + "type_info": "Jsonb" + }, + { + "ordinal": 7, + "name": "error_json", + "type_info": "Jsonb" + }, + { + "ordinal": 8, + "name": "expires_at", + "type_info": "Text" + }, + { + "ordinal": 9, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 10, + "name": "updated_at!", + "type_info": "Text" + }, + { + "ordinal": 11, + "name": "finished_at", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + true, + false, + false, + false, + true, + true, + null, + null, + null, + null + ] + }, + "hash": "e92340e11c1b1d5dcd1441f12588e01fa18acc43e18316ea9789cd60e4967eaf" +} diff --git a/.sqlx/query-ed3c533baee2cdec1fabc3e3cece29939fee23fdf54dc4848a0951f3dd274f1f.json b/.sqlx/query-ed3c533baee2cdec1fabc3e3cece29939fee23fdf54dc4848a0951f3dd274f1f.json new file mode 100644 index 0000000..0165a56 --- /dev/null +++ b/.sqlx/query-ed3c533baee2cdec1fabc3e3cece29939fee23fdf54dc4848a0951f3dd274f1f.json @@ -0,0 +1,64 @@ +{ + "db_name": "PostgreSQL", + "query": "select\n id,\n workspace_id,\n name,\n prefix,\n scopes_json,\n status,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(last_used_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_used_at\n from platform_api_keys\n where workspace_id = $1\n order by created_at desc", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "workspace_id", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "name", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "prefix", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "scopes_json", + "type_info": "Jsonb" + }, + { + "ordinal": 5, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "created_at!", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "last_used_at", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false, + null, + null + ] + }, + "hash": "ed3c533baee2cdec1fabc3e3cece29939fee23fdf54dc4848a0951f3dd274f1f" +} diff --git a/crates/crank-registry/src/postgres/api_key.rs b/crates/crank-registry/src/postgres/api_key.rs index fcbcd83..7e0eb80 100644 --- a/crates/crank-registry/src/postgres/api_key.rs +++ b/crates/crank-registry/src/postgres/api_key.rs @@ -5,7 +5,7 @@ impl PostgresRegistry { &self, workspace_id: &WorkspaceId, ) -> Result, RegistryError> { - let rows = sqlx::query( + let rows = sqlx::query!( "select id, workspace_id, @@ -13,17 +13,32 @@ impl PostgresRegistry { prefix, scopes_json, status, - to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, + to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", to_char(last_used_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_used_at from platform_api_keys where workspace_id = $1 order by created_at desc", + workspace_id.as_str(), ) - .bind(workspace_id.as_str()) .fetch_all(&self.pool) .await?; - rows.iter().map(map_platform_api_key_record).collect() + rows.into_iter() + .map(|row| { + Ok(PlatformApiKeyRecord { + api_key: PlatformApiKey { + id: PlatformApiKeyId::new(row.id), + workspace_id: WorkspaceId::new(row.workspace_id), + name: row.name, + prefix: row.prefix, + scopes: deserialize_json_value(row.scopes_json)?, + status: deserialize_enum_text(&row.status, "status")?, + created_at: row.created_at, + last_used_at: row.last_used_at, + }, + }) + }) + .collect() } pub async fn get_platform_api_key_by_secret_for_workspace_slug( @@ -31,7 +46,7 @@ impl PostgresRegistry { workspace_slug: &str, secret_hash: &str, ) -> Result, RegistryError> { - let row = sqlx::query( + let row = sqlx::query!( "select k.id, k.workspace_id, @@ -39,7 +54,7 @@ impl PostgresRegistry { k.prefix, k.scopes_json, k.status, - to_char(k.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, + to_char(k.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", to_char(k.last_used_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_used_at from platform_api_keys k join workspaces w on w.id = k.workspace_id @@ -47,13 +62,27 @@ impl PostgresRegistry { and k.secret_hash = $2 and k.status = 'active' limit 1", + workspace_slug, + secret_hash, ) - .bind(workspace_slug) - .bind(secret_hash) .fetch_optional(&self.pool) .await?; - row.as_ref().map(map_platform_api_key_record).transpose() + row.map(|row| { + Ok(PlatformApiKeyRecord { + api_key: PlatformApiKey { + id: PlatformApiKeyId::new(row.id), + workspace_id: WorkspaceId::new(row.workspace_id), + name: row.name, + prefix: row.prefix, + scopes: deserialize_json_value(row.scopes_json)?, + status: deserialize_enum_text(&row.status, "status")?, + created_at: row.created_at, + last_used_at: row.last_used_at, + }, + }) + }) + .transpose() } pub async fn create_platform_api_key( diff --git a/crates/crank-registry/src/postgres/mod.rs b/crates/crank-registry/src/postgres/mod.rs index eda2f59..ef695bf 100644 --- a/crates/crank-registry/src/postgres/mod.rs +++ b/crates/crank-registry/src/postgres/mod.rs @@ -484,52 +484,6 @@ fn assert_immutable_fields( Ok(()) } -fn map_workspace_record(row: &PgRow) -> Result { - Ok(WorkspaceRecord { - workspace: Workspace { - id: WorkspaceId::new(row.try_get::("id")?), - slug: row.try_get("slug")?, - display_name: row.try_get("display_name")?, - status: deserialize_enum_text(&row.try_get::("status")?, "status")?, - settings: row.try_get::, _>("settings_json")?.0, - created_at: row.try_get("created_at")?, - updated_at: row.try_get("updated_at")?, - }, - }) -} - -fn map_workspace_membership_record( - row: &PgRow, -) -> Result { - Ok(WorkspaceMembershipRecord { - workspace: Workspace { - id: WorkspaceId::new(row.try_get::("id")?), - slug: row.try_get("slug")?, - display_name: row.try_get("display_name")?, - status: deserialize_enum_text(&row.try_get::("status")?, "status")?, - settings: row.try_get::, _>("settings_json")?.0, - created_at: row.try_get("created_at")?, - updated_at: row.try_get("updated_at")?, - }, - role: deserialize_enum_text(&row.try_get::("role")?, "role")?, - }) -} - -fn map_membership_record(row: &PgRow) -> Result { - Ok(MembershipRecord { - workspace_id: WorkspaceId::new(row.try_get::("workspace_id")?), - user: User { - id: UserId::new(row.try_get::("user_id")?), - email: row.try_get("email")?, - display_name: row.try_get("display_name")?, - status: deserialize_enum_text(&row.try_get::("status")?, "status")?, - created_at: row.try_get("user_created_at")?, - }, - role: deserialize_enum_text(&row.try_get::("role")?, "role")?, - created_at: row.try_get("created_at")?, - }) -} - fn map_user(row: &PgRow) -> Result { Ok(User { id: UserId::new(row.try_get::("id")?), @@ -556,36 +510,6 @@ fn map_user_update_error(error: sqlx::Error, user_id: &UserId, email: &str) -> R } } -fn map_invitation_record(row: &PgRow) -> Result { - Ok(InvitationRecord { - invitation: InvitationToken { - id: InvitationId::new(row.try_get::("id")?), - workspace_id: WorkspaceId::new(row.try_get::("workspace_id")?), - email: row.try_get("email")?, - role: deserialize_enum_text(&row.try_get::("role")?, "role")?, - status: deserialize_enum_text(&row.try_get::("status")?, "status")?, - token_hash: row.try_get("token_hash")?, - expires_at: row.try_get("expires_at")?, - created_at: row.try_get("created_at")?, - }, - }) -} - -fn map_platform_api_key_record(row: &PgRow) -> Result { - Ok(PlatformApiKeyRecord { - api_key: PlatformApiKey { - id: PlatformApiKeyId::new(row.try_get::("id")?), - workspace_id: WorkspaceId::new(row.try_get::("workspace_id")?), - name: row.try_get("name")?, - prefix: row.try_get("prefix")?, - scopes: deserialize_json_value(row.try_get::, _>("scopes_json")?.0)?, - status: deserialize_enum_text(&row.try_get::("status")?, "status")?, - created_at: row.try_get("created_at")?, - last_used_at: row.try_get("last_used_at")?, - }, - }) -} - fn map_stream_session(row: &PgRow) -> Result { Ok(StreamSession { id: StreamSessionId::new(row.try_get::("id")?), @@ -1124,9 +1048,10 @@ mod tests { use crank_core::{ ApiKeyHeaderAuthConfig, AsyncJobHandle, AuthConfig, AuthKind, AuthProfile, ConfigExport, ExecutionConfig, ExportMode, GeneratedDraft, GeneratedDraftStatus, HttpMethod, JobStatus, - OperationId, OperationStatus, Protocol, RestTarget, RetryPolicy, Samples, SecretId, - StreamSession, StreamSessionId, StreamStatus, Target, ToolDescription, ToolExample, - WorkspaceId, + MembershipRole, OperationId, OperationStatus, PlatformApiKey, PlatformApiKeyId, + PlatformApiKeyScope, PlatformApiKeyStatus, Protocol, RestTarget, RetryPolicy, Samples, + SecretId, StreamSession, StreamSessionId, StreamStatus, Target, ToolDescription, + ToolExample, User, UserId, Workspace, WorkspaceId, }; use crank_mapping::{MappingRule, MappingSet}; use crank_schema::{Schema, SchemaKind}; @@ -1136,12 +1061,14 @@ mod tests { use crate::{ PostgresRegistry, RegistryError, model::{ - AsyncJobFilter, CreateAsyncJobRequest, CreateStreamSessionRequest, - CreateVersionRequest, CreateYamlImportJobRequest, DescriptorKind, DescriptorMetadata, - OperationSampleMetadata, PublishRequest, RegistryOperation, SampleKind, - SaveAuthProfileRequest, SaveDescriptorMetadataRequest, SaveSampleMetadataRequest, - StreamSessionFilter, UpdateAsyncJobStatusRequest, UpdateStreamSessionStateRequest, - YamlImportJobCompletion, YamlImportJobId, YamlImportJobStatus, + AsyncJobFilter, CreateAsyncJobRequest, CreatePlatformApiKeyRequest, + CreateStreamSessionRequest, CreateVersionRequest, CreateWorkspaceRequest, + CreateYamlImportJobRequest, DescriptorKind, DescriptorMetadata, + OperationSampleMetadata, PlatformApiKeyRecord, PublishRequest, RegistryOperation, + SampleKind, SaveAuthProfileRequest, SaveDescriptorMetadataRequest, + SaveSampleMetadataRequest, StreamSessionFilter, UpdateAsyncJobStatusRequest, + UpdateStreamSessionStateRequest, WorkspaceRecord, YamlImportJobCompletion, + YamlImportJobId, YamlImportJobStatus, }, }; @@ -1453,6 +1380,150 @@ mod tests { database.cleanup().await; } + #[tokio::test] + async fn manages_workspace_read_paths() { + let database = TestDatabase::new().await; + let registry = database.registry().await; + let workspace = Workspace { + id: WorkspaceId::new("ws_extra_01"), + slug: "extra".to_owned(), + display_name: "Extra Workspace".to_owned(), + status: crank_core::WorkspaceStatus::Active, + settings: json!({"region":"eu"}), + created_at: "2026-03-25T12:00:00Z".to_owned(), + updated_at: "2026-03-25T12:00:00Z".to_owned(), + }; + let mut user = User { + id: UserId::new("user_extra_01"), + email: "owner@example.com".to_owned(), + display_name: "Owner".to_owned(), + status: crank_core::UserStatus::Active, + created_at: "2026-03-25T11:00:00Z".to_owned(), + }; + + registry + .create_workspace(CreateWorkspaceRequest { + workspace: &workspace, + }) + .await + .unwrap(); + let user_id = registry + .upsert_bootstrap_user(&user.email, &user.display_name, "hashed-password") + .await + .unwrap(); + user.id = user_id; + registry + .ensure_membership(&workspace.id, &user.id, MembershipRole::Owner) + .await + .unwrap(); + + let all_workspaces = registry.list_workspaces().await.unwrap(); + let user_workspaces = registry.list_workspaces_for_user(&user.id).await.unwrap(); + let memberships = registry.list_memberships(&workspace.id).await.unwrap(); + let loaded = registry + .get_workspace(&workspace.id) + .await + .unwrap() + .unwrap(); + + assert!( + all_workspaces + .iter() + .any(|record| record.workspace == workspace) + ); + assert_eq!(user_workspaces.len(), 1); + assert_eq!(user_workspaces[0].workspace, workspace); + assert_eq!(user_workspaces[0].role, MembershipRole::Owner); + assert_eq!(memberships.len(), 1); + assert_eq!(memberships[0].workspace_id, workspace.id); + assert_eq!(memberships[0].user.id, user.id); + assert_eq!(memberships[0].user.email, user.email); + assert_eq!(memberships[0].user.display_name, user.display_name); + assert_eq!(memberships[0].user.status, user.status); + assert_eq!(memberships[0].role, MembershipRole::Owner); + assert_eq!(loaded, WorkspaceRecord { workspace }); + + database.cleanup().await; + } + + #[tokio::test] + async fn manages_platform_api_key_read_paths() { + let database = TestDatabase::new().await; + let registry = database.registry().await; + let workspace = Workspace { + id: WorkspaceId::new("ws_keys_01"), + slug: "keys".to_owned(), + display_name: "Keys Workspace".to_owned(), + status: crank_core::WorkspaceStatus::Active, + settings: json!({}), + created_at: "2026-03-25T12:00:00Z".to_owned(), + updated_at: "2026-03-25T12:00:00Z".to_owned(), + }; + let api_key = PlatformApiKey { + id: PlatformApiKeyId::new("key_01"), + workspace_id: workspace.id.clone(), + name: "Primary".to_owned(), + prefix: "crk_live".to_owned(), + scopes: vec![PlatformApiKeyScope::Read, PlatformApiKeyScope::Write], + status: PlatformApiKeyStatus::Active, + created_at: "2026-03-25T12:01:00Z".to_owned(), + last_used_at: None, + }; + let secret_hash = "secret_hash_01"; + + registry + .create_workspace(CreateWorkspaceRequest { + workspace: &workspace, + }) + .await + .unwrap(); + registry + .create_platform_api_key(CreatePlatformApiKeyRequest { + api_key: &api_key, + secret_hash, + }) + .await + .unwrap(); + + let listed = registry + .list_platform_api_keys(&workspace.id) + .await + .unwrap(); + assert_eq!( + listed, + vec![PlatformApiKeyRecord { + api_key: api_key.clone() + }] + ); + + let resolved = registry + .get_platform_api_key_by_secret_for_workspace_slug(&workspace.slug, secret_hash) + .await + .unwrap() + .unwrap(); + assert_eq!(resolved.api_key, api_key); + + registry + .touch_platform_api_key( + &workspace.id, + &PlatformApiKeyId::new("key_01"), + "2026-03-25T12:05:00Z", + ) + .await + .unwrap(); + + let touched = registry + .list_platform_api_keys(&workspace.id) + .await + .unwrap(); + assert_eq!( + touched[0].api_key.last_used_at.as_deref(), + Some("2026-03-25T12:05:00Z") + ); + + database.cleanup().await; + } + #[tokio::test] async fn manages_stream_sessions_with_transitions_and_cleanup() { let database = TestDatabase::new().await; diff --git a/crates/crank-registry/src/postgres/stream.rs b/crates/crank-registry/src/postgres/stream.rs index a1acdae..05e66c7 100644 --- a/crates/crank-registry/src/postgres/stream.rs +++ b/crates/crank-registry/src/postgres/stream.rs @@ -54,7 +54,7 @@ impl PostgresRegistry { &self, id: &StreamSessionId, ) -> Result, RegistryError> { - let row = sqlx::query( + let row = sqlx::query!( "select id, workspace_id, @@ -65,18 +65,35 @@ impl PostgresRegistry { status, cursor_json, state_json, - to_char(expires_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as expires_at, + to_char(expires_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"expires_at!\", to_char(last_poll_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_poll_at, - to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, + to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", to_char(closed_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as closed_at from stream_sessions where id = $1", + id.as_str(), ) - .bind(id.as_str()) .fetch_optional(&self.pool) .await?; - row.as_ref().map(map_stream_session).transpose() + row.map(|row| { + Ok(StreamSession { + id: StreamSessionId::new(row.id), + workspace_id: WorkspaceId::new(row.workspace_id), + agent_id: row.agent_id.map(AgentId::new), + operation_id: OperationId::new(row.operation_id), + protocol: deserialize_enum_text(&row.protocol, "protocol")?, + mode: deserialize_enum_text(&row.mode, "mode")?, + status: deserialize_enum_text(&row.status, "status")?, + cursor: row.cursor_json, + state: row.state_json, + expires_at: row.expires_at, + last_poll_at: row.last_poll_at, + created_at: row.created_at, + closed_at: row.closed_at, + }) + }) + .transpose() } pub async fn update_stream_session_state( @@ -196,7 +213,7 @@ impl PostgresRegistry { .transpose()?; let limit = i64::from(filter.limit); - let rows = sqlx::query( + let rows = sqlx::query!( "select id, workspace_id, @@ -207,9 +224,9 @@ impl PostgresRegistry { status, cursor_json, state_json, - to_char(expires_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as expires_at, + to_char(expires_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"expires_at!\", to_char(last_poll_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_poll_at, - to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, + to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", to_char(closed_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as closed_at from stream_sessions where workspace_id = $1 @@ -219,20 +236,36 @@ impl PostgresRegistry { and ($5::text is null or mode = $5) order by created_at desc limit $6", + filter.workspace_id.as_str(), + filter.agent_id.map(|value| value.as_str()), + filter.operation_id.map(|value| value.as_str()), + status, + mode, + limit, ) - .bind(filter.workspace_id.as_str()) - .bind(filter.agent_id.map(|value| value.as_str())) - .bind(filter.operation_id.map(|value| value.as_str())) - .bind(status) - .bind(mode) - .bind(limit) .fetch_all(&self.pool) .await?; let items = rows - .iter() - .map(map_stream_session) - .collect::, _>>()?; + .into_iter() + .map(|row| { + Ok(StreamSession { + id: StreamSessionId::new(row.id), + workspace_id: WorkspaceId::new(row.workspace_id), + agent_id: row.agent_id.map(AgentId::new), + operation_id: OperationId::new(row.operation_id), + protocol: deserialize_enum_text(&row.protocol, "protocol")?, + mode: deserialize_enum_text(&row.mode, "mode")?, + status: deserialize_enum_text(&row.status, "status")?, + cursor: row.cursor_json, + state: row.state_json, + expires_at: row.expires_at, + last_poll_at: row.last_poll_at, + created_at: row.created_at, + closed_at: row.closed_at, + }) + }) + .collect::, RegistryError>>()?; let total = items.len() as u64; Ok(Page { items, total }) @@ -295,7 +328,7 @@ impl PostgresRegistry { &self, id: &AsyncJobId, ) -> Result, RegistryError> { - let row = sqlx::query( + let row = sqlx::query!( "select id, workspace_id, @@ -306,17 +339,33 @@ impl PostgresRegistry { result_json, error_json, to_char(expires_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as expires_at, - to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, - to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as updated_at, + to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", + to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\", to_char(finished_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as finished_at from async_jobs where id = $1", + id.as_str(), ) - .bind(id.as_str()) .fetch_optional(&self.pool) .await?; - row.as_ref().map(map_async_job).transpose() + row.map(|row| { + Ok(AsyncJobHandle { + id: AsyncJobId::new(row.id), + workspace_id: WorkspaceId::new(row.workspace_id), + agent_id: row.agent_id.map(AgentId::new), + operation_id: OperationId::new(row.operation_id), + status: deserialize_enum_text(&row.status, "status")?, + progress: row.progress_json, + result: row.result_json, + error: row.error_json, + expires_at: row.expires_at, + created_at: row.created_at, + updated_at: row.updated_at, + finished_at: row.finished_at, + }) + }) + .transpose() } pub async fn update_async_job_status( @@ -424,7 +473,7 @@ impl PostgresRegistry { .transpose()?; let limit = i64::from(filter.limit); - let rows = sqlx::query( + let rows = sqlx::query!( "select id, workspace_id, @@ -435,8 +484,8 @@ impl PostgresRegistry { result_json, error_json, to_char(expires_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as expires_at, - to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, - to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as updated_at, + to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", + to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\", to_char(finished_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as finished_at from async_jobs where workspace_id = $1 @@ -445,19 +494,34 @@ impl PostgresRegistry { and ($4::text is null or status = $4) order by updated_at desc limit $5", + filter.workspace_id.as_str(), + filter.agent_id.map(|value| value.as_str()), + filter.operation_id.map(|value| value.as_str()), + status, + limit, ) - .bind(filter.workspace_id.as_str()) - .bind(filter.agent_id.map(|value| value.as_str())) - .bind(filter.operation_id.map(|value| value.as_str())) - .bind(status) - .bind(limit) .fetch_all(&self.pool) .await?; let items = rows - .iter() - .map(map_async_job) - .collect::, _>>()?; + .into_iter() + .map(|row| { + Ok(AsyncJobHandle { + id: AsyncJobId::new(row.id), + workspace_id: WorkspaceId::new(row.workspace_id), + agent_id: row.agent_id.map(AgentId::new), + operation_id: OperationId::new(row.operation_id), + status: deserialize_enum_text(&row.status, "status")?, + progress: row.progress_json, + result: row.result_json, + error: row.error_json, + expires_at: row.expires_at, + created_at: row.created_at, + updated_at: row.updated_at, + finished_at: row.finished_at, + }) + }) + .collect::, RegistryError>>()?; let total = items.len() as u64; Ok(Page { items, total }) diff --git a/crates/crank-registry/src/postgres/workspace.rs b/crates/crank-registry/src/postgres/workspace.rs index 7e3c4e4..07f926d 100644 --- a/crates/crank-registry/src/postgres/workspace.rs +++ b/crates/crank-registry/src/postgres/workspace.rs @@ -2,74 +2,118 @@ use super::*; impl PostgresRegistry { pub async fn list_workspaces(&self) -> Result, RegistryError> { - let rows = sqlx::query( + let rows = sqlx::query!( "select id, slug, display_name, status, settings_json, - to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, - to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as updated_at + to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", + to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\" from workspaces order by slug asc", ) .fetch_all(&self.pool) .await?; - rows.iter().map(map_workspace_record).collect() + rows.into_iter() + .map(|row| { + Ok(WorkspaceRecord { + workspace: Workspace { + id: WorkspaceId::new(row.id), + slug: row.slug, + display_name: row.display_name, + status: deserialize_enum_text(&row.status, "status")?, + settings: row.settings_json, + created_at: row.created_at, + updated_at: row.updated_at, + }, + }) + }) + .collect() } pub async fn list_workspaces_for_user( &self, user_id: &UserId, ) -> Result, RegistryError> { - let rows = sqlx::query( + let rows = sqlx::query!( "select w.id, w.slug, w.display_name, w.status, w.settings_json, - to_char(w.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, - to_char(w.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as updated_at, + to_char(w.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", + to_char(w.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\", m.role from memberships m join workspaces w on w.id = m.workspace_id where m.user_id = $1 order by w.slug asc", + user_id.as_str(), ) - .bind(user_id.as_str()) .fetch_all(&self.pool) .await?; - rows.iter().map(map_workspace_membership_record).collect() + rows.into_iter() + .map(|row| { + Ok(WorkspaceMembershipRecord { + workspace: Workspace { + id: WorkspaceId::new(row.id), + slug: row.slug, + display_name: row.display_name, + status: deserialize_enum_text(&row.status, "status")?, + settings: row.settings_json, + created_at: row.created_at, + updated_at: row.updated_at, + }, + role: deserialize_enum_text(&row.role, "role")?, + }) + }) + .collect() } pub async fn list_memberships( &self, workspace_id: &WorkspaceId, ) -> Result, RegistryError> { - let rows = sqlx::query( + let rows = sqlx::query!( "select m.workspace_id, m.user_id, m.role, - to_char(m.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, + to_char(m.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", u.email, u.display_name, u.status, - to_char(u.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as user_created_at + to_char(u.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"user_created_at!\" from memberships m join users u on u.id = m.user_id where m.workspace_id = $1 order by u.email asc", + workspace_id.as_str(), ) - .bind(workspace_id.as_str()) .fetch_all(&self.pool) .await?; - rows.iter().map(map_membership_record).collect() + rows.into_iter() + .map(|row| { + Ok(MembershipRecord { + workspace_id: WorkspaceId::new(row.workspace_id), + user: User { + id: UserId::new(row.user_id), + email: row.email, + display_name: row.display_name, + status: deserialize_enum_text(&row.status, "status")?, + created_at: row.user_created_at, + }, + role: deserialize_enum_text(&row.role, "role")?, + created_at: row.created_at, + }) + }) + .collect() } pub async fn update_membership_role( @@ -127,7 +171,7 @@ impl PostgresRegistry { &self, workspace_id: &WorkspaceId, ) -> Result, RegistryError> { - let rows = sqlx::query( + let rows = sqlx::query!( "select id, workspace_id, @@ -135,17 +179,32 @@ impl PostgresRegistry { role, status, token_hash, - to_char(expires_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as expires_at, - to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at + to_char(expires_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"expires_at!\", + to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\" from invitation_tokens where workspace_id = $1 order by created_at desc", + workspace_id.as_str(), ) - .bind(workspace_id.as_str()) .fetch_all(&self.pool) .await?; - rows.iter().map(map_invitation_record).collect() + rows.into_iter() + .map(|row| { + Ok(InvitationRecord { + invitation: InvitationToken { + id: InvitationId::new(row.id), + workspace_id: WorkspaceId::new(row.workspace_id), + email: row.email, + role: deserialize_enum_text(&row.role, "role")?, + status: deserialize_enum_text(&row.status, "status")?, + token_hash: row.token_hash, + expires_at: row.expires_at, + created_at: row.created_at, + }, + }) + }) + .collect() } pub async fn create_invitation( @@ -220,23 +279,36 @@ impl PostgresRegistry { &self, workspace_id: &WorkspaceId, ) -> Result, RegistryError> { - let row = sqlx::query( + let row = sqlx::query!( "select id, slug, display_name, status, settings_json, - to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as created_at, - to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as updated_at + to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\", + to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\" from workspaces where id = $1", + workspace_id.as_str(), ) - .bind(workspace_id.as_str()) .fetch_optional(&self.pool) .await?; - row.as_ref().map(map_workspace_record).transpose() + row.map(|row| { + Ok(WorkspaceRecord { + workspace: Workspace { + id: WorkspaceId::new(row.id), + slug: row.slug, + display_name: row.display_name, + status: deserialize_enum_text(&row.status, "status")?, + settings: row.settings_json, + created_at: row.created_at, + updated_at: row.updated_at, + }, + }) + }) + .transpose() } pub async fn create_workspace(