OXYGENHUB
◆ v0.9.4 oxygenhub / crates / hub / src / db / releases.rs
4.2 KiB · Rust · last changed Postgres, step 2b: flags compared as TRUE and FALSE, counted with CASE 19 days ago source blame history raw
1
//! Releases and their assets (hub spec §9).
2
3
use super::{now_ms, Pool};
4
use sqlx::FromRow;
5
6
#[derive(Clone, Debug, FromRow)]
7
pub struct Release {
8
    pub id: i64,
9
    pub repo_id: i64,
10
    pub tag: String,
11
    pub title: String,
12
    pub notes: String,
13
    pub prerelease: bool,
14
    pub author_id: Option<i64>,
15
    pub author: String,
16
    pub created_ms: i64,
17
}
18
19
#[derive(Clone, Debug, FromRow)]
20
pub struct Asset {
21
    pub id: i64,
22
    pub release_id: i64,
23
    pub name: String,
24
    pub size: i64,
25
    pub hash: String,
26
    pub content_type: String,
27
    pub downloads: i64,
28
    pub created_ms: i64,
29
}
30
31
pub async fn list(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<Release>> {
32
    sqlx::query_as("SELECT * FROM releases WHERE repo_id = $1 ORDER BY created_ms DESC, id DESC").bind(repo_id).fetch_all(pool).await
33
}
34
35
pub async fn get(pool: &Pool, repo_id: i64, tag: &str) -> sqlx::Result<Option<Release>> {
36
    sqlx::query_as("SELECT * FROM releases WHERE repo_id = $1 AND tag = $2").bind(repo_id).bind(tag).fetch_optional(pool).await
37
}
38
39
/// The newest release that is not a pre-release: the "latest" marker.
40
pub async fn latest(pool: &Pool, repo_id: i64) -> sqlx::Result<Option<Release>> {
41
    sqlx::query_as("SELECT * FROM releases WHERE repo_id = $1 AND prerelease = FALSE ORDER BY created_ms DESC, id DESC LIMIT 1")
42
        .bind(repo_id)
43
        .fetch_optional(pool)
44
        .await
45
}
46
47
pub async fn create(
48
    pool: &Pool,
49
    repo_id: i64,
50
    tag: &str,
51
    title: &str,
52
    notes: &str,
53
    prerelease: bool,
54
    author: Option<(i64, &str)>,
55
) -> sqlx::Result<Release> {
56
    sqlx::query_as("INSERT INTO releases (repo_id, tag, title, notes, prerelease, author_id, author, created_ms) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *")
57
        .bind(repo_id)
58
        .bind(tag)
59
        .bind(title)
60
        .bind(notes)
61
        .bind(prerelease)
62
        .bind(author.map(|a| a.0))
63
        .bind(author.map(|a| a.1).unwrap_or(""))
64
        .bind(now_ms())
65
        .fetch_one(pool)
66
        .await
67
}
68
69
pub async fn edit(pool: &Pool, id: i64, title: &str, notes: &str, prerelease: bool) -> sqlx::Result<()> {
70
    sqlx::query("UPDATE releases SET title = $1, notes = $2, prerelease = $3 WHERE id = $4")
71
        .bind(title)
72
        .bind(notes)
73
        .bind(prerelease)
74
        .bind(id)
75
        .execute(pool)
76
        .await?;
77
    Ok(())
78
}
79
80
pub async fn delete(pool: &Pool, id: i64) -> sqlx::Result<()> {
81
    sqlx::query("DELETE FROM releases WHERE id = $1").bind(id).execute(pool).await?;
82
    Ok(())
83
}
84
85
pub async fn assets(pool: &Pool, release_id: i64) -> sqlx::Result<Vec<Asset>> {
86
    sqlx::query_as("SELECT * FROM release_assets WHERE release_id = $1 ORDER BY name").bind(release_id).fetch_all(pool).await
87
}
88
89
pub async fn asset(pool: &Pool, release_id: i64, name: &str) -> sqlx::Result<Option<Asset>> {
90
    sqlx::query_as("SELECT * FROM release_assets WHERE release_id = $1 AND name = $2")
91
        .bind(release_id)
92
        .bind(name)
93
        .fetch_optional(pool)
94
        .await
95
}
96
97
pub async fn add_asset(pool: &Pool, release_id: i64, name: &str, size: i64, hash: &str, content_type: &str) -> sqlx::Result<Asset> {
98
    sqlx::query_as(
99
        "INSERT INTO release_assets (release_id, name, size, hash, content_type, created_ms) VALUES ($1, $2, $3, $4, $5, $6) \
100
         ON CONFLICT (release_id, name) DO UPDATE SET size = excluded.size, hash = excluded.hash, content_type = excluded.content_type, created_ms = excluded.created_ms RETURNING *",
101
    )
102
    .bind(release_id)
103
    .bind(name)
104
    .bind(size)
105
    .bind(hash)
106
    .bind(content_type)
107
    .bind(now_ms())
108
    .fetch_one(pool)
109
    .await
110
}
111
112
pub async fn delete_asset(pool: &Pool, id: i64) -> sqlx::Result<()> {
113
    sqlx::query("DELETE FROM release_assets WHERE id = $1").bind(id).execute(pool).await?;
114
    Ok(())
115
}
116
117
pub async fn count_download(pool: &Pool, id: i64) -> sqlx::Result<()> {
118
    sqlx::query("UPDATE release_assets SET downloads = downloads + 1 WHERE id = $1").bind(id).execute(pool).await?;
119
    Ok(())
120
}
121
122
/// Whether any asset of any release still names this content.
123
pub async fn hash_in_use(pool: &Pool, hash: &str) -> sqlx::Result<bool> {
124
    let n: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM release_assets WHERE hash = $1").bind(hash).fetch_one(pool).await?;
125
    Ok(n > 0)
126
}