| 1 | |
|
| 2 | |
|
| 3 | | use super::{now_ms, Pool}; |
| 4 | | use hub_api::Visibility; |
| 5 | | use sqlx::FromRow; |
| 6 | |
|
| 7 | | #[derive(Clone, Debug, Default, FromRow)] |
| 8 | | pub struct RepoRow { |
| 9 | | pub id: i64, |
| 10 | | pub owner: String, |
| 11 | | pub name: String, |
| 12 | | pub description: String, |
| 13 | | pub default_bookmark: String, |
| 14 | | pub visibility: String, |
| 15 | | pub created_ms: i64, |
| 16 | | pub updated_ms: i64, |
| 17 | | #[sqlx(default)] |
| 18 | | pub git_mirror: bool, |
| 19 | | #[sqlx(default)] |
| 20 | | pub git_mirror_state: String, |
| 21 | | |
| 22 | | #[sqlx(default)] |
| 23 | | pub forked_from: Option<i64>, |
| 24 | | |
| 25 | | #[sqlx(default)] |
| 26 | | pub topics: String, |
| 27 | | |
| 28 | | #[sqlx(default)] |
| 29 | | pub flow: String, |
| 30 | | |
| 31 | | |
| 32 | | #[sqlx(default)] |
| 33 | | pub flow_priority: String, |
| 34 | | |
| 35 | | |
| 36 | | #[sqlx(default)] |
| 37 | | pub diffusion: String, |
| 38 | | |
| 39 | | |
| 40 | | #[sqlx(default)] |
| 41 | | pub diffusion_land: String, |
| 42 | | |
| 43 | | |
| 44 | | #[sqlx(default)] |
| 45 | | pub backup: String, |
| 46 | | |
| 47 | | #[sqlx(default)] |
| 48 | | pub archived: bool, |
| 49 | | #[sqlx(default)] |
| 50 | | pub archived_reason: String, |
| 51 | | |
| 52 | | #[sqlx(default)] |
| 53 | | pub agents_decide_gates: bool, |
| 54 | | |
| 55 | | |
| 56 | | #[sqlx(default)] |
| 57 | | pub flow_keep_running: bool, |
| 58 | | |
| 59 | | |
| 60 | | #[sqlx(default)] |
| 61 | | pub flow_skip: String, |
| 62 | | |
| 63 | | |
| 64 | | #[sqlx(default)] |
| 65 | | pub flow_reuse: bool, |
| 66 | | |
| 67 | | |
| 68 | | #[sqlx(default)] |
| 69 | | pub push_protection: bool, |
| 70 | | |
| 71 | | |
| 72 | | #[sqlx(default)] |
| 73 | | pub kind: String, |
| 74 | | |
| 75 | | #[sqlx(default)] |
| 76 | | pub title: String, |
| 77 | | |
| 78 | | #[sqlx(default)] |
| 79 | | pub open_issues: i64, |
| 80 | | #[sqlx(default)] |
| 81 | | pub open_proposals: i64, |
| 82 | | } |
| 83 | |
|
| 84 | | impl RepoRow { |
| 85 | 1610× | pub fn visibility(&self) -> Visibility { |
| 86 | 1610× | Visibility::parse(&self.visibility).unwrap_or(Visibility::Private) |
| 87 | 1610× | } |
| 88 | |
|
| 89 | 59× | pub fn topic_list(&self) -> Vec<&str> { |
| 90 | 59× | self.topics.split_whitespace().collect() |
| 91 | 59× | } |
| 92 | |
|
| 93 | 371× | pub fn slug(&self) -> String { |
| 94 | 371× | format!("{}/{}", self.owner, self.name) |
| 95 | 371× | } |
| 96 | |
|
| 97 | | |
| 98 | 4178× | pub fn is_drop(&self) -> bool { |
| 99 | 4178× | self.kind == DROP |
| 100 | 4178× | } |
| 101 | | } |
| 102 | |
|
| 103 | |
|
| 104 | | pub const DROP: &str = "drop"; |
| 105 | |
|
| 106 | | #[derive(Clone, Debug, FromRow)] |
| 107 | | pub struct PushRow { |
| 108 | | pub id: i64, |
| 109 | | pub at_ms: i64, |
| 110 | | pub reference: String, |
| 111 | | pub ok: bool, |
| 112 | | pub reason: String, |
| 113 | | pub old_state: String, |
| 114 | | pub new_state: String, |
| 115 | | pub heads: i64, |
| 116 | | pub changes: i64, |
| 117 | | pub pusher: String, |
| 118 | | } |
| 119 | |
|
| 120 | |
|
| 121 | | #[derive(Clone, Debug)] |
| 122 | | pub struct PushOutcome { |
| 123 | | pub reference: String, |
| 124 | | pub ok: bool, |
| 125 | | pub reason: String, |
| 126 | | pub old_state: String, |
| 127 | | pub new_state: String, |
| 128 | | pub heads: i64, |
| 129 | | pub changes: i64, |
| 130 | | } |
| 131 | |
|
| 132 | |
|
| 133 | |
|
| 134 | 542× | pub async fn list(pool: &Pool) -> sqlx::Result<Vec<RepoRow>> { |
| 135 | 542× | sqlx::query_as("SELECT * FROM repositories WHERE kind = 'repo' ORDER BY updated_ms DESC, owner, name").fetch_all(pool).await |
| 136 | 541× | } |
| 137 | |
|
| 138 | |
|
| 139 | |
|
| 140 | |
|
| 141 | 30× | pub async fn list_with_drops(pool: &Pool) -> sqlx::Result<Vec<RepoRow>> { |
| 142 | 30× | sqlx::query_as("SELECT * FROM repositories ORDER BY updated_ms DESC, owner, name").fetch_all(pool).await |
| 143 | 30× | } |
| 144 | |
|
| 145 | 572× | pub async fn get_by_id(pool: &Pool, id: i64) -> sqlx::Result<Option<RepoRow>> { |
| 146 | 572× | sqlx::query_as("SELECT * FROM repositories WHERE id = $1").bind(id).fetch_optional(pool).await |
| 147 | 572× | } |
| 148 | |
|
| 149 | 80× | pub async fn list_by_owner(pool: &Pool, owner: &str) -> sqlx::Result<Vec<RepoRow>> { |
| 150 | 80× | sqlx::query_as("SELECT * FROM repositories WHERE owner = $1 AND kind = 'repo' ORDER BY updated_ms DESC, name") |
| 151 | 80× | .bind(owner) |
| 152 | 80× | .fetch_all(pool) |
| 153 | 80× | .await |
| 154 | 80× | } |
| 155 | |
|
| 156 | |
|
| 157 | |
|
| 158 | |
|
| 159 | |
|
| 160 | |
|
| 161 | 7× | pub async fn create_drop( |
| 162 | 7× | pool: &Pool, |
| 163 | 7× | owner: &str, |
| 164 | 7× | id: &str, |
| 165 | 7× | title: &str, |
| 166 | 7× | description: &str, |
| 167 | 7× | visibility: Visibility, |
| 168 | 7× | ) -> sqlx::Result<RepoRow> { |
| 169 | 7× | let now = now_ms(); |
| 170 | 7× | sqlx::query_as( |
| 171 | 7× | "INSERT INTO repositories (owner, name, description, visibility, created_ms, updated_ms, kind, title, flow) \ |
| 172 | 7× | VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'off') RETURNING *", |
| 173 | 7× | ) |
| 174 | 7× | .bind(owner) |
| 175 | 7× | .bind(format!("{}/{id}", crate::repos::names::DROPS)) |
| 176 | 7× | .bind(description) |
| 177 | 7× | .bind(visibility.as_str()) |
| 178 | 7× | .bind(now) |
| 179 | 7× | .bind(now) |
| 180 | 7× | .bind(DROP) |
| 181 | 7× | .bind(title) |
| 182 | 7× | .fetch_one(pool) |
| 183 | 7× | .await |
| 184 | 7× | } |
| 185 | |
|
| 186 | |
|
| 187 | | #[derive(Clone, Debug, FromRow)] |
| 188 | | pub struct DropComment { |
| 189 | | pub id: i64, |
| 190 | | pub repo_id: i64, |
| 191 | | pub author_id: Option<i64>, |
| 192 | | pub author: String, |
| 193 | | pub body: String, |
| 194 | | pub created_ms: i64, |
| 195 | | } |
| 196 | |
|
| 197 | 2× | pub async fn add_drop_comment(pool: &Pool, repo_id: i64, author_id: i64, author: &str, body: &str) -> sqlx::Result<i64> { |
| 198 | 2× | sqlx::query_scalar("INSERT INTO drop_comments (repo_id, author_id, author, body, created_ms) VALUES ($1, $2, $3, $4, $5) RETURNING id") |
| 199 | 2× | .bind(repo_id) |
| 200 | 2× | .bind(author_id) |
| 201 | 2× | .bind(author) |
| 202 | 2× | .bind(body) |
| 203 | 2× | .bind(now_ms()) |
| 204 | 2× | .fetch_one(pool) |
| 205 | 2× | .await |
| 206 | 2× | } |
| 207 | |
|
| 208 | |
|
| 209 | 27× | pub async fn drop_comments(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<DropComment>> { |
| 210 | 27× | sqlx::query_as("SELECT * FROM drop_comments WHERE repo_id = $1 ORDER BY created_ms, id").bind(repo_id).fetch_all(pool).await |
| 211 | 27× | } |
| 212 | |
|
| 213 | 3× | pub async fn drop_comment(pool: &Pool, repo_id: i64, id: i64) -> sqlx::Result<Option<DropComment>> { |
| 214 | 3× | sqlx::query_as("SELECT * FROM drop_comments WHERE repo_id = $1 AND id = $2").bind(repo_id).bind(id).fetch_optional(pool).await |
| 215 | 3× | } |
| 216 | |
|
| 217 | 2× | pub async fn delete_drop_comment(pool: &Pool, repo_id: i64, id: i64) -> sqlx::Result<()> { |
| 218 | 2× | sqlx::query("DELETE FROM drop_comments WHERE repo_id = $1 AND id = $2").bind(repo_id).bind(id).execute(pool).await?; |
| 219 | 2× | Ok(()) |
| 220 | 2× | } |
| 221 | |
|
| 222 | |
|
| 223 | 12× | pub async fn drops_of(pool: &Pool, owner: &str) -> sqlx::Result<Vec<RepoRow>> { |
| 224 | 12× | sqlx::query_as("SELECT * FROM repositories WHERE kind = 'drop' AND owner = $1 ORDER BY updated_ms DESC, id DESC") |
| 225 | 12× | .bind(owner) |
| 226 | 12× | .fetch_all(pool) |
| 227 | 12× | .await |
| 228 | 12× | } |
| 229 | |
|
| 230 | |
|
| 231 | |
|
| 232 | 24× | pub async fn drop_by_id(pool: &Pool, id: &str) -> sqlx::Result<Option<RepoRow>> { |
| 233 | 24× | sqlx::query_as("SELECT * FROM repositories WHERE kind = 'drop' AND name = $1") |
| 234 | 24× | .bind(format!("{}/{id}", crate::repos::names::DROPS)) |
| 235 | 24× | .fetch_optional(pool) |
| 236 | 24× | .await |
| 237 | 24× | } |
| 238 | |
|
| 239 | |
|
| 240 | 2× | pub async fn set_drop(pool: &Pool, row_id: i64, title: &str, description: &str, visibility: Visibility) -> sqlx::Result<()> { |
| 241 | 2× | sqlx::query("UPDATE repositories SET title = $1, description = $2, visibility = $3, updated_ms = $4 WHERE id = $5 AND kind = 'drop'") |
| 242 | 2× | .bind(title) |
| 243 | 2× | .bind(description) |
| 244 | 2× | .bind(visibility.as_str()) |
| 245 | 2× | .bind(now_ms()) |
| 246 | 2× | .bind(row_id) |
| 247 | 2× | .execute(pool) |
| 248 | 2× | .await?; |
| 249 | 2× | Ok(()) |
| 250 | 2× | } |
| 251 | |
|
| 252 | |
|
| 253 | 4935× | pub async fn get(pool: &Pool, owner: &str, name: &str) -> sqlx::Result<Option<RepoRow>> { |
| 254 | 4935× | sqlx::query_as( |
| 255 | 4935× | "SELECT r.*, \ |
| 256 | 4935× | (SELECT COUNT(*) FROM issues i WHERE i.repo_id = r.id AND i.state = 'open') AS open_issues, \ |
| 257 | 4935× | (SELECT COUNT(*) FROM proposals p WHERE p.repo_id = r.id AND p.state IN ('open', 'draft')) AS open_proposals \ |
| 258 | 4935× | FROM repositories r WHERE r.owner = $1 AND r.name = $2", |
| 259 | 4935× | ) |
| 260 | 4935× | .bind(owner) |
| 261 | 4935× | .bind(name) |
| 262 | 4935× | .fetch_optional(pool) |
| 263 | 4935× | .await |
| 264 | 4935× | } |
| 265 | |
|
| 266 | |
|
| 267 | |
|
| 268 | |
|
| 269 | 199× | pub async fn create(pool: &Pool, owner: &str, name: &str, description: &str, visibility: Visibility) -> sqlx::Result<RepoRow> { |
| 270 | 199× | let now = now_ms(); |
| 271 | 199× | sqlx::query_as( |
| 272 | 199× | "INSERT INTO repositories (owner, name, description, visibility, created_ms, updated_ms, required_approvals) \ |
| 273 | 199× | VALUES ($1, $2, $3, $4, $5, $6, (SELECT CASE WHEN COUNT(*) >= 2 THEN 1 ELSE 0 END FROM org_members m JOIN organisations o ON o.id = m.org_id WHERE o.name = $7)) RETURNING *", |
| 274 | 199× | ) |
| 275 | 199× | .bind(owner) |
| 276 | 199× | .bind(name) |
| 277 | 199× | .bind(description) |
| 278 | 199× | .bind(visibility.as_str()) |
| 279 | 199× | .bind(now) |
| 280 | 199× | .bind(now) |
| 281 | 199× | .bind(owner) |
| 282 | 199× | .fetch_one(pool) |
| 283 | 199× | .await |
| 284 | 199× | } |
| 285 | |
|
| 286 | |
|
| 287 | 31× | pub async fn moved_to(pool: &Pool, owner: &str, name: &str) -> sqlx::Result<Option<RepoRow>> { |
| 288 | 31× | let id: Option<i64> = sqlx::query_scalar("SELECT repo_id FROM repo_redirects WHERE old_owner = $1 AND old_name = $2") |
| 289 | 31× | .bind(owner) |
| 290 | 31× | .bind(name) |
| 291 | 31× | .fetch_optional(pool) |
| 292 | 31× | .await?; |
| 293 | 31× | match id { |
| 294 | 30× | Some(id) => get_by_id(pool, id).await, |
| 295 | 1× | None => Ok(None), |
| 296 | | } |
| 297 | 31× | } |
| 298 | |
|
| 299 | |
|
| 300 | |
|
| 301 | |
|
| 302 | 1× | pub async fn transfer(pool: &Pool, id: i64, old_owner: &str, name: &str, new_owner: &str) -> sqlx::Result<()> { |
| 303 | 1× | sqlx::query("UPDATE repositories SET owner = $1, updated_ms = $2 WHERE id = $3") |
| 304 | 1× | .bind(new_owner) |
| 305 | 1× | .bind(now_ms()) |
| 306 | 1× | .bind(id) |
| 307 | 1× | .execute(pool) |
| 308 | 1× | .await?; |
| 309 | 1× | sqlx::query("DELETE FROM repo_redirects WHERE old_owner = $1 AND old_name = $2").bind(new_owner).bind(name).execute(pool).await?; |
| 310 | 1× | sqlx::query("INSERT INTO repo_redirects (old_owner, old_name, repo_id, moved_ms) VALUES ($1, $2, $3, $4) ON CONFLICT (old_owner, old_name) DO UPDATE SET repo_id = excluded.repo_id, moved_ms = excluded.moved_ms") |
| 311 | 1× | .bind(old_owner) |
| 312 | 1× | .bind(name) |
| 313 | 1× | .bind(id) |
| 314 | 1× | .bind(now_ms()) |
| 315 | 1× | .execute(pool) |
| 316 | 1× | .await?; |
| 317 | 1× | Ok(()) |
| 318 | 1× | } |
| 319 | |
|
| 320 | |
|
| 321 | 2× | pub async fn set_archived(pool: &Pool, id: i64, archived: bool, reason: &str) -> sqlx::Result<()> { |
| 322 | 2× | sqlx::query("UPDATE repositories SET archived = $1, archived_reason = $2, updated_ms = $3 WHERE id = $4") |
| 323 | 2× | .bind(archived) |
| 324 | 2× | .bind(if archived { reason } else { "" }) |
| 325 | 2× | .bind(now_ms()) |
| 326 | 2× | .bind(id) |
| 327 | 2× | .execute(pool) |
| 328 | 2× | .await?; |
| 329 | 2× | Ok(()) |
| 330 | 2× | } |
| 331 | |
|
| 332 | 6× | pub async fn delete(pool: &Pool, id: i64) -> sqlx::Result<()> { |
| 333 | 6× | sqlx::query("DELETE FROM repositories WHERE id = $1").bind(id).execute(pool).await?; |
| 334 | 6× | Ok(()) |
| 335 | 6× | } |
| 336 | |
|
| 337 | 2× | pub async fn set_description(pool: &Pool, id: i64, description: &str) -> sqlx::Result<()> { |
| 338 | 2× | sqlx::query("UPDATE repositories SET description = $1 WHERE id = $2").bind(description).bind(id).execute(pool).await?; |
| 339 | 2× | Ok(()) |
| 340 | 2× | } |
| 341 | |
|
| 342 | 3× | pub async fn set_default_bookmark(pool: &Pool, id: i64, bookmark: &str) -> sqlx::Result<()> { |
| 343 | 3× | sqlx::query("UPDATE repositories SET default_bookmark = $1 WHERE id = $2").bind(bookmark).bind(id).execute(pool).await?; |
| 344 | 3× | Ok(()) |
| 345 | 3× | } |
| 346 | |
|
| 347 | 2× | pub async fn set_visibility(pool: &Pool, id: i64, visibility: Visibility) -> sqlx::Result<()> { |
| 348 | 2× | sqlx::query("UPDATE repositories SET visibility = $1 WHERE id = $2").bind(visibility.as_str()).bind(id).execute(pool).await?; |
| 349 | 2× | Ok(()) |
| 350 | 2× | } |
| 351 | |
|
| 352 | |
|
| 353 | 44× | pub async fn record_push(pool: &Pool, id: i64, pusher: Option<(i64, &str)>, results: &[PushOutcome]) -> sqlx::Result<()> { |
| 354 | 44× | record_push_via(pool, id, pusher, "", results).await |
| 355 | 44× | } |
| 356 | |
|
| 357 | |
|
| 358 | 298× | pub async fn record_push_via(pool: &Pool, id: i64, pusher: Option<(i64, &str)>, via: &str, results: &[PushOutcome]) -> sqlx::Result<()> { |
| 359 | 298× | let now = now_ms(); |
| 360 | 298× | let mut tx = super::begin_write(pool).await?; |
| 361 | 317× | for r in results { |
| 362 | 317× | sqlx::query("INSERT INTO pushes (repo_id, at_ms, reference, ok, reason, old_state, new_state, heads, changes, account_id, pusher, via) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)") |
| 363 | 317× | .bind(id) |
| 364 | 317× | .bind(now) |
| 365 | 317× | .bind(&r.reference) |
| 366 | 317× | .bind(r.ok) |
| 367 | 317× | .bind(&r.reason) |
| 368 | 317× | .bind(&r.old_state) |
| 369 | 317× | .bind(&r.new_state) |
| 370 | 317× | .bind(r.heads) |
| 371 | 317× | .bind(r.changes) |
| 372 | 317× | .bind(pusher.map(|p| p.0).filter(|id| *id > 0)) |
| 373 | 317× | .bind(pusher.map(|p| p.1).unwrap_or("")) |
| 374 | 317× | .bind(via) |
| 375 | 317× | .execute(&mut *tx) |
| 376 | 317× | .await?; |
| 377 | | } |
| 378 | 298× | if results.iter().any(|r| r.ok) { |
| 379 | 281× | sqlx::query("UPDATE repositories SET updated_ms = $1 WHERE id = $2").bind(now).bind(id).execute(&mut *tx).await?; |
| 380 | 17× | } |
| 381 | 298× | tx.commit().await |
| 382 | 298× | } |
| 383 | |
|
| 384 | 11× | pub async fn pushes(pool: &Pool, id: i64, limit: i64) -> sqlx::Result<Vec<PushRow>> { |
| 385 | 11× | sqlx::query_as("SELECT id, at_ms, reference, ok, reason, old_state, new_state, heads, changes, pusher FROM pushes WHERE repo_id = $1 ORDER BY at_ms DESC, id DESC LIMIT $2") |
| 386 | 11× | .bind(id) |
| 387 | 11× | .bind(limit) |
| 388 | 11× | .fetch_all(pool) |
| 389 | 11× | .await |
| 390 | 11× | } |
| 391 | |
|
| 392 | |
|
| 393 | |
|
| 394 | |
|
| 395 | 9× | pub fn parse_topics(input: &str) -> Result<String, String> { |
| 396 | 9× | let mut out: Vec<String> = Vec::new(); |
| 397 | 36× | for t in input.split([',', ' ', '\n', '\t']).map(|t| t.trim().to_ascii_lowercase()).filter(|t| !t.is_empty()) { |
| 398 | 97× | if t.len() > 35 || !t.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') || t.starts_with('-') { |
| 399 | 4× | return Err(format!("\"{t}\" is not a topic: use letters, digits and dashes, up to 35 characters")); |
| 400 | 27× | } |
| 401 | 27× | if !out.contains(&t) { |
| 402 | 26× | out.push(t); |
| 403 | 26× | } |
| 404 | | } |
| 405 | 5× | if out.len() > 20 { |
| 406 | 1× | return Err("At most 20 topics.".into()); |
| 407 | 4× | } |
| 408 | 4× | Ok(out.join(" ")) |
| 409 | 9× | } |
| 410 | |
|
| 411 | 2× | pub async fn set_topics(pool: &Pool, id: i64, topics: &str) -> sqlx::Result<()> { |
| 412 | 2× | sqlx::query("UPDATE repositories SET topics = $1 WHERE id = $2").bind(topics).bind(id).execute(pool).await?; |
| 413 | 2× | Ok(()) |
| 414 | 2× | } |
| 415 | |
|
| 416 | |
|
| 417 | 8× | pub async fn toggle_star(pool: &Pool, account_id: i64, repo_id: i64) -> sqlx::Result<bool> { |
| 418 | 8× | let gone = sqlx::query("DELETE FROM stars WHERE account_id = $1 AND repo_id = $2").bind(account_id).bind(repo_id).execute(pool).await?; |
| 419 | 8× | if gone.rows_affected() > 0 { |
| 420 | 2× | return Ok(false); |
| 421 | 6× | } |
| 422 | 6× | sqlx::query("INSERT INTO stars (account_id, repo_id, at_ms) VALUES ($1, $2, $3)") |
| 423 | 6× | .bind(account_id) |
| 424 | 6× | .bind(repo_id) |
| 425 | 6× | .bind(now_ms()) |
| 426 | 6× | .execute(pool) |
| 427 | 6× | .await?; |
| 428 | 6× | Ok(true) |
| 429 | 8× | } |
| 430 | |
|
| 431 | 43× | pub async fn starred(pool: &Pool, account_id: i64, repo_id: i64) -> sqlx::Result<bool> { |
| 432 | 43× | let n: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM stars WHERE account_id = $1 AND repo_id = $2") |
| 433 | 43× | .bind(account_id) |
| 434 | 43× | .bind(repo_id) |
| 435 | 43× | .fetch_one(pool) |
| 436 | 43× | .await?; |
| 437 | 43× | Ok(n > 0) |
| 438 | 43× | } |
| 439 | |
|
| 440 | |
|
| 441 | 78× | pub async fn counts(pool: &Pool, repo_id: i64) -> sqlx::Result<(i64, i64)> { |
| 442 | 78× | let stars: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM stars WHERE repo_id = $1").bind(repo_id).fetch_one(pool).await?; |
| 443 | 78× | let watchers = super::notify::watchers(pool, repo_id).await?.len() as i64; |
| 444 | 78× | Ok((stars, watchers)) |
| 445 | 78× | } |
| 446 | |
|
| 447 | |
|
| 448 | 17× | pub async fn star_counts(pool: &Pool) -> sqlx::Result<std::collections::HashMap<i64, i64>> { |
| 449 | 17× | let rows: Vec<(i64, i64)> = sqlx::query_as("SELECT repo_id, COUNT(*) FROM stars GROUP BY repo_id").fetch_all(pool).await?; |
| 450 | 17× | Ok(rows.into_iter().collect()) |
| 451 | 17× | } |
| 452 | |
|
| 453 | |
|
| 454 | 2× | pub async fn set_forked_from(pool: &Pool, id: i64, from: i64) -> sqlx::Result<()> { |
| 455 | 2× | sqlx::query("UPDATE repositories SET forked_from = $1 WHERE id = $2").bind(from).bind(id).execute(pool).await?; |
| 456 | 2× | Ok(()) |
| 457 | 2× | } |
| 458 | |
|
| 459 | |
|
| 460 | 52× | pub async fn forks_of(pool: &Pool, id: i64) -> sqlx::Result<Vec<RepoRow>> { |
| 461 | 52× | sqlx::query_as("SELECT * FROM repositories WHERE forked_from = $1 AND kind = 'repo' ORDER BY updated_ms DESC") |
| 462 | 52× | .bind(id) |
| 463 | 52× | .fetch_all(pool) |
| 464 | 52× | .await |
| 465 | 52× | } |
| 466 | |
|
| 467 | |
|
| 468 | 8× | pub async fn set_flow(pool: &Pool, id: i64, setting: &str) -> sqlx::Result<()> { |
| 469 | 8× | sqlx::query("UPDATE repositories SET flow = $1 WHERE id = $2").bind(setting).bind(id).execute(pool).await?; |
| 470 | 8× | Ok(()) |
| 471 | 8× | } |
| 472 | |
|
| 473 | 4× | pub async fn set_flow_priority(pool: &Pool, id: i64, priority: &str) -> sqlx::Result<()> { |
| 474 | 4× | sqlx::query("UPDATE repositories SET flow_priority = $1 WHERE id = $2").bind(priority).bind(id).execute(pool).await?; |
| 475 | 4× | Ok(()) |
| 476 | 4× | } |
| 477 | |
|
| 478 | |
|
| 479 | 2× | pub async fn set_agents_decide_gates(pool: &Pool, id: i64, on: bool) -> sqlx::Result<()> { |
| 480 | 2× | sqlx::query("UPDATE repositories SET agents_decide_gates = $1 WHERE id = $2").bind(on).bind(id).execute(pool).await?; |
| 481 | 2× | Ok(()) |
| 482 | 2× | } |
| 483 | |
|
| 484 | |
|
| 485 | |
|
| 486 | 1× | pub async fn set_flow_reuse(pool: &Pool, id: i64, on: bool) -> sqlx::Result<()> { |
| 487 | 1× | sqlx::query("UPDATE repositories SET flow_reuse = $1 WHERE id = $2").bind(on).bind(id).execute(pool).await?; |
| 488 | 1× | Ok(()) |
| 489 | 1× | } |
| 490 | |
|
| 491 | 3× | pub async fn set_flow_skip(pool: &Pool, id: i64, skip: &str) -> sqlx::Result<()> { |
| 492 | 3× | sqlx::query("UPDATE repositories SET flow_skip = $1 WHERE id = $2").bind(skip).bind(id).execute(pool).await?; |
| 493 | 3× | Ok(()) |
| 494 | 3× | } |
| 495 | |
|
| 496 | 3× | pub async fn set_flow_keep_running(pool: &Pool, id: i64, on: bool) -> sqlx::Result<()> { |
| 497 | 3× | sqlx::query("UPDATE repositories SET flow_keep_running = $1 WHERE id = $2").bind(on).bind(id).execute(pool).await?; |
| 498 | 3× | Ok(()) |
| 499 | 3× | } |
| 500 | |
|
| 501 | 4× | pub async fn set_backup(pool: &Pool, id: i64, setting: &str) -> sqlx::Result<()> { |
| 502 | 4× | sqlx::query("UPDATE repositories SET backup = $1 WHERE id = $2").bind(setting).bind(id).execute(pool).await?; |
| 503 | 4× | Ok(()) |
| 504 | 4× | } |
| 505 | |
|
| 506 | 2× | pub async fn set_diffusion_land(pool: &Pool, id: i64, setting: &str) -> sqlx::Result<()> { |
| 507 | 2× | sqlx::query("UPDATE repositories SET diffusion_land = $1 WHERE id = $2").bind(setting).bind(id).execute(pool).await?; |
| 508 | 2× | Ok(()) |
| 509 | 2× | } |
| 510 | |
|
| 511 | 9× | pub async fn set_diffusion(pool: &Pool, id: i64, setting: &str) -> sqlx::Result<()> { |
| 512 | 9× | sqlx::query("UPDATE repositories SET diffusion = $1 WHERE id = $2").bind(setting).bind(id).execute(pool).await?; |
| 513 | 9× | Ok(()) |
| 514 | 9× | } |
| 515 | |
|
| 516 | 2× | pub async fn set_git_mirror(pool: &Pool, id: i64, on: bool) -> sqlx::Result<()> { |
| 517 | 2× | sqlx::query("UPDATE repositories SET git_mirror = $1 WHERE id = $2").bind(on).bind(id).execute(pool).await?; |
| 518 | 2× | Ok(()) |
| 519 | 2× | } |
| 520 | |
|
| 521 | 2× | pub async fn set_git_mirror_state(pool: &Pool, id: i64, state: &str) -> sqlx::Result<()> { |
| 522 | 2× | sqlx::query("UPDATE repositories SET git_mirror_state = $1 WHERE id = $2").bind(state).bind(id).execute(pool).await?; |
| 523 | 2× | Ok(()) |
| 524 | 2× | } |
| 525 | |
|
| 526 | | #[cfg(test)] |
| 527 | | mod topic_tests { |
| 528 | | use super::parse_topics; |
| 529 | |
|
| 530 | | #[test] |
| 531 | 1× | fn topics_are_normalised_and_checked() { |
| 532 | 1× | assert_eq!(parse_topics("Rust, vcs retro-computing,rust").unwrap(), "rust vcs retro-computing"); |
| 533 | 1× | assert_eq!(parse_topics("").unwrap(), ""); |
| 534 | 1× | assert!(parse_topics("c++").is_err()); |
| 535 | 1× | assert!(parse_topics("-lead").is_err()); |
| 536 | 1× | assert!(parse_topics(&"x".repeat(36)).is_err()); |
| 537 | 21× | assert!(parse_topics(&(0..21).map(|i| format!("t{i}")).collect::<Vec<_>>().join(" ")).is_err()); |
| 538 | 1× | } |
| 539 | | } |