about summary refs log tree commit diff
path: root/src/db.rs
blob: c2659729370654d333f5e4113916f1a7cd96fa55 (plain) (blame)
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
// Copyright (C) 2018  Vincent Ambo <mail@tazj.in>
//
// Converse is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see
// <http://www.gnu.org/licenses/>.

//! This module implements the database connection actor.

use actix::prelude::*;
use diesel;
use diesel::prelude::*;
use diesel::r2d2::{Pool, ConnectionManager};
use models::*;
use errors::{ConverseError, Result};

/// The DB actor itself. Several of these will be run in parallel by
/// `SyncArbiter`.
pub struct DbExecutor(pub Pool<ConnectionManager<PgConnection>>);

impl Actor for DbExecutor {
    type Context = SyncContext<Self>;
}

/// Message used to request a list of threads.
/// TODO: This should support page numbers.
pub struct ListThreads;

impl Message for ListThreads {
    type Result = Result<Vec<Thread>>;
}

impl Handler<ListThreads> for DbExecutor {
    type Result = <ListThreads as Message>::Result;

    fn handle(&mut self, _: ListThreads, _: &mut Self::Context) -> Self::Result {
        use schema::threads::dsl::*;

        let conn = self.0.get()?;
        let results = threads
            .order(posted.desc())
            .load::<Thread>(&conn)?;
        Ok(results)
    }
}

/// Message used to fetch a specific thread. Returns the thread and
/// its posts.
pub struct GetThread(pub i32);

impl Message for GetThread {
    type Result = Result<(Thread, Vec<Post>)>;
}

impl Handler<GetThread> for DbExecutor {
    type Result = <GetThread as Message>::Result;

    fn handle(&mut self, msg: GetThread, _: &mut Self::Context) -> Self::Result {
        use schema::threads::dsl::*;

        let conn = self.0.get()?;
        let thread_result: Thread = threads
            .find(msg.0).first(&conn)?;

        let post_list = Post::belonging_to(&thread_result).load::<Post>(&conn)?;

        Ok((thread_result, post_list))
    }
}

/// Message used to create a new thread
pub struct CreateThread {
    pub new_thread: NewThread,
    pub body: String,
}

impl Message for CreateThread {
    type Result = Result<Thread>;
}

impl Handler<CreateThread> for DbExecutor {
    type Result = <CreateThread as Message>::Result;

    fn handle(&mut self, msg: CreateThread, _: &mut Self::Context) -> Self::Result {
        use schema::threads;
        use schema::posts;

        let conn = self.0.get()?;

        conn.transaction::<Thread, ConverseError, _>(|| {
            // First insert the thread structure itself
            let thread: Thread = diesel::insert_into(threads::table)
                .values(&msg.new_thread)
                .get_result(&conn)?;

            // ... then create the first post in the thread.
            let new_post = NewPost {
                thread_id: thread.id,
                body: msg.body,
                author_name: msg.new_thread.author_name.clone(),
                author_email: msg.new_thread.author_email.clone(),
            };

            diesel::insert_into(posts::table)
                .values(&new_post)
                .execute(&conn)?;

            Ok(thread)
        })
    }
}

/// Message used to create a new reply
pub struct CreatePost(pub NewPost);

impl Message for CreatePost {
    type Result = Result<Post>;
}

impl Handler<CreatePost> for DbExecutor {
    type Result = <CreatePost as Message>::Result;

    fn handle(&mut self, msg: CreatePost, _: &mut Self::Context) -> Self::Result {
        use schema::posts;

        let conn = self.0.get()?;

        Ok(diesel::insert_into(posts::table)
           .values(&msg.0)
           .get_result(&conn)?)
    }
}