summaryrefslogtreecommitdiff
path: root/makima/src/db/repository.rs
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src/db/repository.rs')
-rw-r--r--makima/src/db/repository.rs117
1 files changed, 112 insertions, 5 deletions
diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs
index 5d8ba82..12d5e4d 100644
--- a/makima/src/db/repository.rs
+++ b/makima/src/db/repository.rs
@@ -5785,7 +5785,12 @@ pub async fn clear_pending_directive_steps(
// Directive Document CRUD
// =============================================================================
-/// List all documents under a directive, ordered by creation time.
+/// List all contracts under a directive in queue order.
+///
+/// Ordered by `position` (lower = earlier), with `created_at` as a stable
+/// tie-break. Position is the queue order in the unified directive UI;
+/// only one contract is active at a time, and the next-up contract is
+/// the lowest-position non-shipped row.
pub async fn list_directive_documents(
pool: &PgPool,
directive_id: Uuid,
@@ -5794,7 +5799,7 @@ pub async fn list_directive_documents(
r#"
SELECT * FROM directive_documents
WHERE directive_id = $1
- ORDER BY created_at
+ ORDER BY position ASC, created_at ASC
"#,
)
.bind(directive_id)
@@ -5815,7 +5820,14 @@ pub async fn get_directive_document(
.await
}
-/// Create a new directive document. Status defaults to 'draft'.
+/// Create a new directive document (contract). Status defaults to 'draft'.
+///
+/// The new row's `position` is computed server-side as
+/// `MAX(position) + 1` over the directive's existing contracts, so it
+/// lands at the bottom of the queue. Callers that want to insert in the
+/// middle should call `reorder_directive_document_position` afterwards.
+/// `merge_mode` defaults to 'shared' on creation; flip later via
+/// `update_directive_document`.
pub async fn create_directive_document(
pool: &PgPool,
directive_id: Uuid,
@@ -5824,8 +5836,14 @@ pub async fn create_directive_document(
) -> Result<DirectiveDocument, sqlx::Error> {
sqlx::query_as::<_, DirectiveDocument>(
r#"
- INSERT INTO directive_documents (directive_id, title, body, status)
- VALUES ($1, $2, $3, 'draft')
+ INSERT INTO directive_documents (directive_id, title, body, status, position)
+ VALUES (
+ $1, $2, $3, 'draft',
+ COALESCE(
+ (SELECT MAX(position) + 1 FROM directive_documents WHERE directive_id = $1),
+ 0
+ )
+ )
RETURNING *
"#,
)
@@ -5848,6 +5866,7 @@ pub async fn update_directive_document(
document_id: Uuid,
title: Option<&str>,
body: Option<&str>,
+ merge_mode: Option<&str>,
) -> Result<Option<DirectiveDocument>, sqlx::Error> {
let current = sqlx::query_as::<_, DirectiveDocument>(
r#"SELECT * FROM directive_documents WHERE id = $1"#,
@@ -5863,6 +5882,7 @@ pub async fn update_directive_document(
let new_title = title.unwrap_or(&current.title);
let new_body = body.unwrap_or(&current.body);
+ let new_merge_mode = merge_mode.unwrap_or(&current.merge_mode);
let body_changed = new_body != current.body;
// Reactivation rule: editing the body of a shipped doc flips it back
@@ -5883,6 +5903,7 @@ pub async fn update_directive_document(
body = $3,
status = $4,
shipped_at = CASE WHEN $5 THEN NULL ELSE shipped_at END,
+ merge_mode = $6,
version = version + 1,
updated_at = NOW()
WHERE id = $1
@@ -5894,12 +5915,98 @@ pub async fn update_directive_document(
.bind(new_body)
.bind(new_status)
.bind(reactivate_from_shipped)
+ .bind(new_merge_mode)
.fetch_optional(pool)
.await?;
Ok(result)
}
+/// Move a contract to a new queue position within its directive.
+///
+/// Implementation: a single SQL CTE that bumps siblings out of the way
+/// based on whether we're moving forward (later) or backward (earlier).
+/// Returns the updated contract row.
+pub async fn reorder_directive_document_position(
+ pool: &PgPool,
+ document_id: Uuid,
+ new_position: i32,
+) -> Result<Option<DirectiveDocument>, sqlx::Error> {
+ let mut tx = pool.begin().await?;
+
+ let current = sqlx::query_as::<_, DirectiveDocument>(
+ r#"SELECT * FROM directive_documents WHERE id = $1"#,
+ )
+ .bind(document_id)
+ .fetch_optional(&mut *tx)
+ .await?;
+
+ let current = match current {
+ Some(c) => c,
+ None => return Ok(None),
+ };
+
+ if current.position == new_position {
+ tx.commit().await?;
+ return Ok(Some(current));
+ }
+
+ // Shift siblings to make room. Moving forward (new > old) drags the
+ // intermediate range back by one; moving backward pushes it forward.
+ if new_position > current.position {
+ sqlx::query(
+ r#"
+ UPDATE directive_documents
+ SET position = position - 1
+ WHERE directive_id = $1
+ AND id <> $2
+ AND position > $3
+ AND position <= $4
+ "#,
+ )
+ .bind(current.directive_id)
+ .bind(document_id)
+ .bind(current.position)
+ .bind(new_position)
+ .execute(&mut *tx)
+ .await?;
+ } else {
+ sqlx::query(
+ r#"
+ UPDATE directive_documents
+ SET position = position + 1
+ WHERE directive_id = $1
+ AND id <> $2
+ AND position >= $3
+ AND position < $4
+ "#,
+ )
+ .bind(current.directive_id)
+ .bind(document_id)
+ .bind(new_position)
+ .bind(current.position)
+ .execute(&mut *tx)
+ .await?;
+ }
+
+ let result = sqlx::query_as::<_, DirectiveDocument>(
+ r#"
+ UPDATE directive_documents
+ SET position = $2,
+ updated_at = NOW()
+ WHERE id = $1
+ RETURNING *
+ "#,
+ )
+ .bind(document_id)
+ .bind(new_position)
+ .fetch_optional(&mut *tx)
+ .await?;
+
+ tx.commit().await?;
+ Ok(result)
+}
+
/// Mark a directive document as shipped (PR raised). Sets pr_url, optional
/// pr_branch, status = 'shipped', shipped_at = NOW(), and bumps version.
pub async fn mark_directive_document_shipped(