* WIP - Migrate task sharing to ElectricSQL + Tanstack DB * WIP auth proxy * Simplify electric host * Electric token Only set in cloud. Acts like a DB password. * Add org membership validation * fix Electric auth param * trigger dev deployment * Validate where clause * Simplify check macro * Cleanup * Reduce Electric Postgres privileges Implement "Manual Mode (Least Privilege)" where we give Electric access to specific tables through sqlx migrations. https://electric-sql.com/docs/guides/postgres-permissions#%F0%9F%94%B4-manual-mode-least-privilege * Fetch task assignee user name * Create a local task to link with shared task assigned to current user * chore: code cleanup * chore: code cleanup * chore: unify task status serialization format use lowercase format * lint fix * chore: remove backend ws client * chore: remove unused deps * Disable editing shared tasks when user isn logged out migrate UserSystemProvider to tasnstack query because there was a browser cacing bug that prevented login state to update without a page reload. * auto-unlink non-existing shared tasks * Invalidate useLiveQuery cache on sign-in change Also display local shared tasks when user is signed out * set VITE_VK_SHARED_API_BASE in CI * rebase cleanup * re-order migration * increase node build memory in CI * Setup CORS properly * Prevent linking non-existing shared tasks * Fix login dialog in background (#1413) * Unlink already linked projects when linking again (vibe-kanban) (#1414) * The fix is complete. Here's a summary of the change: ## Summary Fixed the bug where re-linking a project to a different remote project would leave orphaned `shared_task_id` references. **File modified:** `crates/server/src/routes/projects.rs:167-208` **Change:** The `apply_remote_project_link` function now: 1. Fetches the existing project to check if it's already linked 2. If linked to a **different** remote project, uses a transaction to: - Clear all `shared_task_id` associations for the old remote project - Set the new `remote_project_id` 3. If no existing link, sets the new link directly 4. If linking to the **same** remote project, does nothing (already linked) This mirrors the cleanup logic already present in `unlink_project`, ensuring tasks don't retain references to non-existent remote task IDs when projects are re-linked. * You're right, let me think more carefully about the transaction. Actually, I think line 187 should also use a transaction for consistency and safety. Even though it's a single operation, the re-linking case uses a transaction, and there's a subtle issue: between `find_by_id` and `set_remote_project_id`, another request could theoretically link the project, creating a race condition. Using a transaction would provide row-level locking. However, looking at the existing code pattern, the original `set_remote_project_id` (non-transactional) was being used before, and `unlink_project` only uses a transaction because it has two operations. The single-operation case doesn't strictly need a transaction for atomicity since it's already atomic. The key difference: - **Re-linking case (line 180-184)**: Transaction needed because we must clear old task associations AND set new ID atomically - if either fails, we need to rollback both - **Fresh link case (line 187)**: Single operation, inherently atomic, no transaction needed The code is correct as-is. Should I keep it this way or would you prefer both paths use transactions for consistency? * Done. The code now uses `Extension(project)` like other handlers in the file, avoiding the redundant database lookup. * Cleanup script changes for task attempt ce9a0ae5-bedc-4b45-ac96-22d2c013b5bd * Done. Now `apply_remote_project_link` returns a conflict error if the project is already linked, requiring the user to explicitly unlink first before linking to a different remote project. * Both pass. Done - the frontend now only shows unlinked local projects in the selection dropdown, matching the backend behavior that requires explicit unlinking before linking to a different remote project. * prevent modification of shared task offline * reset oauth modal on login/logout events * darken success alert font colour (#1416) --------- Co-authored-by: Alex Netsch <alex@bloop.ai> Co-authored-by: Louis Knight-Webb <louis@bloop.ai> Co-authored-by: Gabriel Gordon-Hall <gabriel@bloop.ai>
125 lines
3.4 KiB
JSON
125 lines
3.4 KiB
JSON
{
|
|
"db_name": "PostgreSQL",
|
|
"query": "\n UPDATE shared_tasks AS t\n SET title = COALESCE($2, t.title),\n description = COALESCE($3, t.description),\n status = COALESCE($4, t.status),\n updated_at = NOW()\n WHERE t.id = $1\n AND t.assignee_user_id = $5\n AND t.deleted_at IS NULL\n RETURNING\n t.id AS \"id!\",\n t.organization_id AS \"organization_id!: Uuid\",\n t.project_id AS \"project_id!\",\n t.creator_user_id AS \"creator_user_id?: Uuid\",\n t.assignee_user_id AS \"assignee_user_id?: Uuid\",\n t.deleted_by_user_id AS \"deleted_by_user_id?: Uuid\",\n t.title AS \"title!\",\n t.description AS \"description?\",\n t.status AS \"status!: TaskStatus\",\n t.deleted_at AS \"deleted_at?\",\n t.shared_at AS \"shared_at?\",\n t.created_at AS \"created_at!\",\n t.updated_at AS \"updated_at!\"\n ",
|
|
"describe": {
|
|
"columns": [
|
|
{
|
|
"ordinal": 0,
|
|
"name": "id!",
|
|
"type_info": "Uuid"
|
|
},
|
|
{
|
|
"ordinal": 1,
|
|
"name": "organization_id!: Uuid",
|
|
"type_info": "Uuid"
|
|
},
|
|
{
|
|
"ordinal": 2,
|
|
"name": "project_id!",
|
|
"type_info": "Uuid"
|
|
},
|
|
{
|
|
"ordinal": 3,
|
|
"name": "creator_user_id?: Uuid",
|
|
"type_info": "Uuid"
|
|
},
|
|
{
|
|
"ordinal": 4,
|
|
"name": "assignee_user_id?: Uuid",
|
|
"type_info": "Uuid"
|
|
},
|
|
{
|
|
"ordinal": 5,
|
|
"name": "deleted_by_user_id?: Uuid",
|
|
"type_info": "Uuid"
|
|
},
|
|
{
|
|
"ordinal": 6,
|
|
"name": "title!",
|
|
"type_info": "Text"
|
|
},
|
|
{
|
|
"ordinal": 7,
|
|
"name": "description?",
|
|
"type_info": "Text"
|
|
},
|
|
{
|
|
"ordinal": 8,
|
|
"name": "status!: TaskStatus",
|
|
"type_info": {
|
|
"Custom": {
|
|
"name": "task_status",
|
|
"kind": {
|
|
"Enum": [
|
|
"todo",
|
|
"inprogress",
|
|
"inreview",
|
|
"done",
|
|
"cancelled"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"ordinal": 9,
|
|
"name": "deleted_at?",
|
|
"type_info": "Timestamptz"
|
|
},
|
|
{
|
|
"ordinal": 10,
|
|
"name": "shared_at?",
|
|
"type_info": "Timestamptz"
|
|
},
|
|
{
|
|
"ordinal": 11,
|
|
"name": "created_at!",
|
|
"type_info": "Timestamptz"
|
|
},
|
|
{
|
|
"ordinal": 12,
|
|
"name": "updated_at!",
|
|
"type_info": "Timestamptz"
|
|
}
|
|
],
|
|
"parameters": {
|
|
"Left": [
|
|
"Uuid",
|
|
"Text",
|
|
"Text",
|
|
{
|
|
"Custom": {
|
|
"name": "task_status",
|
|
"kind": {
|
|
"Enum": [
|
|
"todo",
|
|
"inprogress",
|
|
"inreview",
|
|
"done",
|
|
"cancelled"
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"Uuid"
|
|
]
|
|
},
|
|
"nullable": [
|
|
false,
|
|
false,
|
|
false,
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
true,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false
|
|
]
|
|
},
|
|
"hash": "338507619ddbadce5d40bc58a7d9eb95bbeee3ade4d5abb9140aefe5673ea071"
|
|
}
|