1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use crate::db::models::proposals::ChallengeType;
use crate::db::schema::challenges;
use diesel::backend::Backend;
use diesel::sql_types::{BigInt, Integer, Text};
use diesel::types::FromSql;
use diesel::{ExpressionMethods, Insertable, Queryable};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct ChallengeHighlights {
    #[serde(default)]
    pub sponsor: String,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct Challenge {
    #[serde(alias = "internalId")]
    // this is used only to retain the original insert order
    pub internal_id: i32,
    pub id: i32,
    #[serde(alias = "challengeType")]
    pub challenge_type: ChallengeType,
    pub title: String,
    pub description: String,
    #[serde(alias = "rewardsTotal")]
    pub rewards_total: i64,
    #[serde(alias = "proposersRewards")]
    pub proposers_rewards: i64,
    #[serde(alias = "fundId")]
    pub fund_id: i32,
    #[serde(alias = "challengeUrl")]
    pub challenge_url: String,
    pub highlights: Option<ChallengeHighlights>,
}

impl<DB: Backend> Queryable<challenges::SqlType, DB> for Challenge
where
    i32: FromSql<Integer, DB>,
    i64: FromSql<BigInt, DB>,
    String: FromSql<Text, DB>,
{
    type Row = (
        // 0 -> internal_id
        i32,
        // 1 -> id
        i32,
        // 2 -> challenge_type
        String,
        // 3 -> title
        String,
        // 4 -> description
        String,
        // 5 -> rewards_total
        i64,
        // 6 -> proposers_rewards
        i64,
        // 7 -> fund_id
        i32,
        // 8 -> fund_url
        String,
        // 9 -> challenge_highlights
        Option<String>,
    );

    fn build(row: Self::Row) -> Self {
        Challenge {
            internal_id: row.0,
            id: row.1,
            challenge_type: row.2.parse().unwrap(),
            title: row.3,
            description: row.4,
            rewards_total: row.5,
            proposers_rewards: row.6,
            fund_id: row.7,
            challenge_url: row.8,
            // It should be ensured that the content is valid json
            highlights: row.9.and_then(|v| serde_json::from_str(&v).ok()),
        }
    }
}

impl Insertable<challenges::table> for Challenge {
    #[allow(clippy::type_complexity)]
    type Values = (
        diesel::dsl::Eq<challenges::id, i32>,
        diesel::dsl::Eq<challenges::challenge_type, String>,
        diesel::dsl::Eq<challenges::title, String>,
        diesel::dsl::Eq<challenges::description, String>,
        diesel::dsl::Eq<challenges::rewards_total, i64>,
        diesel::dsl::Eq<challenges::proposers_rewards, i64>,
        diesel::dsl::Eq<challenges::fund_id, i32>,
        diesel::dsl::Eq<challenges::challenge_url, String>,
        diesel::dsl::Eq<challenges::highlights, Option<String>>,
    );

    fn values(self) -> Self::Values {
        (
            challenges::id.eq(self.id),
            challenges::challenge_type.eq(self.challenge_type.to_string()),
            challenges::title.eq(self.title),
            challenges::description.eq(self.description),
            challenges::rewards_total.eq(self.rewards_total),
            challenges::proposers_rewards.eq(self.proposers_rewards),
            challenges::fund_id.eq(self.fund_id),
            challenges::challenge_url.eq(self.challenge_url),
            // This should always be a valid json
            challenges::highlights.eq(serde_json::to_string(&self.highlights).ok()),
        )
    }
}

#[cfg(test)]
pub mod test {
    use super::*;
    use crate::db::{DbConnection, DbConnectionPool};
    use diesel::RunQueryDsl;

    pub fn get_test_challenge_with_fund_id(fund_id: i32) -> Challenge {
        const CHALLENGE_ID: i32 = 9001;
        const REWARDS_TOTAL: i64 = 100500;
        Challenge {
            internal_id: 1,
            id: CHALLENGE_ID,
            challenge_type: ChallengeType::CommunityChoice,
            title: "challenge title".to_string(),
            description: "challenge description".to_string(),
            rewards_total: REWARDS_TOTAL,
            proposers_rewards: REWARDS_TOTAL,
            fund_id,
            challenge_url: "http://example.com/".to_string(),
            highlights: None,
        }
    }

    pub fn populate_db_with_challenge(challenge: &Challenge, pool: &DbConnectionPool) {
        let connection = pool.get().unwrap();
        populate_db_with_challenge_conn(challenge, &connection);
    }

    pub fn populate_db_with_challenge_conn(challenge: &Challenge, connection: &DbConnection) {
        diesel::insert_into(challenges::table)
            .values(challenge.clone().values())
            .execute(connection)
            .unwrap();
    }
}