a mini social media app for small communities
1module database
2
3import entity { User, Notification, Like, LikeCache, Post }
4import util
5import db.pg
6
7// new_user creates a new user and returns their struct after creation.
8pub fn (app &DatabaseAccess) new_user(user User) ?User {
9 sql app.db {
10 insert user into User
11 } or {
12 eprintln('failed to insert user ${user}')
13 return none
14 }
15
16 println('reg: ${user.username}')
17
18 return app.get_user_by_name(user.username)
19}
20
21// set_username sets the given user's username, returns true if this succeeded
22// and false otherwise.
23pub fn (app &DatabaseAccess) set_username(user_id int, new_username string) bool {
24 sql app.db {
25 update User set username = new_username where id == user_id
26 } or {
27 eprintln('failed to update username for ${user_id}')
28 return false
29 }
30 return true
31}
32
33// set_password sets the given user's password, returns true if this succeeded
34// and false otherwise.
35pub fn (app &DatabaseAccess) set_password(user_id int, hashed_new_password string) bool {
36 sql app.db {
37 update User set password = hashed_new_password where id == user_id
38 } or {
39 eprintln('failed to update password for ${user_id}')
40 return false
41 }
42 return true
43}
44
45// set_nickname sets the given user's nickname, returns true if this succeeded
46// and false otherwise.
47pub fn (app &DatabaseAccess) set_nickname(user_id int, new_nickname ?string) bool {
48 sql app.db {
49 update User set nickname = new_nickname where id == user_id
50 } or {
51 eprintln('failed to update nickname for ${user_id}')
52 return false
53 }
54 return true
55}
56
57// set_muted sets the given user's muted status, returns true if this succeeded
58// and false otherwise.
59pub fn (app &DatabaseAccess) set_muted(user_id int, muted bool) bool {
60 sql app.db {
61 update User set muted = muted where id == user_id
62 } or {
63 eprintln('failed to update muted status for ${user_id}')
64 return false
65 }
66 return true
67}
68
69// set_automated sets the given user's automated status, returns true if this
70// succeeded and false otherwise.
71pub fn (app &DatabaseAccess) set_automated(user_id int, automated bool) bool {
72 sql app.db {
73 update User set automated = automated where id == user_id
74 } or {
75 eprintln('failed to update automated status for ${user_id}')
76 return false
77 }
78 return true
79}
80
81// set_theme sets the given user's theme url, returns true if this succeeded and
82// false otherwise.
83pub fn (app &DatabaseAccess) set_theme(user_id int, theme ?string) bool {
84 sql app.db {
85 update User set theme = theme where id == user_id
86 } or {
87 eprintln('failed to update theme url for ${user_id}')
88 return false
89 }
90 return true
91}
92
93// set_css sets the given user's custom CSS, returns true if this succeeded and
94// false otherwise.
95pub fn (app &DatabaseAccess) set_css(user_id int, css ?string) bool {
96 sql app.db {
97 update User set css = css where id == user_id
98 } or {
99 eprintln('failed to update css for ${user_id}')
100 return false
101 }
102 return true
103}
104
105// set_pronouns sets the given user's pronouns, returns true if this succeeded
106// and false otherwise.
107pub fn (app &DatabaseAccess) set_pronouns(user_id int, pronouns string) bool {
108 sql app.db {
109 update User set pronouns = pronouns where id == user_id
110 } or {
111 eprintln('failed to update pronouns for ${user_id}')
112 return false
113 }
114 return true
115}
116
117// set_bio sets the given user's bio, returns true if this succeeded and false
118// otherwise.
119pub fn (app &DatabaseAccess) set_bio(user_id int, bio string) bool {
120 sql app.db {
121 update User set bio = bio where id == user_id
122 } or {
123 eprintln('failed to update bio for ${user_id}')
124 return false
125 }
126 return true
127}
128
129// get_user_by_name gets a user by their username, returns none if the user was
130// not found.
131pub fn (app &DatabaseAccess) get_user_by_name(username string) ?User {
132 users := sql app.db {
133 select from User where username == username
134 } or { [] }
135 if users.len != 1 {
136 return none
137 }
138 return users[0]
139}
140
141// get_user_by_id gets a user by their id, returns none if the user was not
142// found.
143pub fn (app &DatabaseAccess) get_user_by_id(id int) ?User {
144 users := sql app.db {
145 select from User where id == id
146 } or { [] }
147 if users.len != 1 {
148 return none
149 }
150 return users[0]
151}
152
153// get_users returns all users.
154pub fn (app &DatabaseAccess) get_users() []User {
155 users := sql app.db {
156 select from User
157 } or { [] }
158 return users
159}
160
161// does_user_like_post returns true if a user likes the given post.
162pub fn (app &DatabaseAccess) does_user_like_post(user_id int, post_id int) bool {
163 likes := app.db.exec_param2('SELECT id, is_like FROM "Like" WHERE user_id = $1 AND post_id = $2', user_id.str(), post_id.str()) or { [] }
164 if likes.len > 1 {
165 // something is very wrong lol
166 eprintln('does_user_like_post: a user somehow got two or more likes on the same post (user: ${user_id}, post: ${post_id})')
167 } else if likes.len == 0 {
168 return false
169 }
170 return util.or_throw(likes.first().vals[1]).bool()
171}
172
173// does_user_dislike_post returns true if a user dislikes the given post.
174pub fn (app &DatabaseAccess) does_user_dislike_post(user_id int, post_id int) bool {
175 likes := app.db.exec_param2('SELECT id, is_like FROM "Like" WHERE user_id = $1 AND post_id = $2', user_id.str(), post_id.str()) or { [] }
176 if likes.len > 1 {
177 // something is very wrong lol
178 eprintln('does_user_dislike_post: a user somehow got two or more likes on the same post (user: ${user_id}, post: ${post_id})')
179 } else if likes.len == 0 {
180 return false
181 }
182 return !util.or_throw(likes.first().vals[1]).bool()
183}
184
185// does_user_like_or_dislike_post returns true if a user likes *or* dislikes the
186// given post.
187pub fn (app &DatabaseAccess) does_user_like_or_dislike_post(user_id int, post_id int) bool {
188 likes := app.db.exec_param2('SELECT id FROM "Like" WHERE user_id = $1 AND post_id = $2', user_id.str(), post_id.str()) or { [] }
189 if likes.len > 1 {
190 // something is very wrong lol
191 eprintln('does_user_like_or_dislike_post: a user somehow got two or more likes on the same post (user: ${user_id}, post: ${post_id})')
192 }
193 return likes.len == 1
194}
195
196// delete_user deletes the given user and their data, returns true if this
197// succeeded and false otherwise.
198pub fn (app &DatabaseAccess) delete_user(user_id int) bool {
199 sql app.db {
200 delete from User where id == user_id
201 delete from Like where user_id == user_id
202 delete from Notification where user_id == user_id
203 } or {
204 return false
205 }
206
207 // delete posts and their likes
208 posts_from_this_user := app.db.exec_param('SELECT id FROM "Post" WHERE author_id = $1', user_id.str()) or { [] }
209
210 for post in posts_from_this_user {
211 id := util.or_throw(post.vals[0]).int()
212 sql app.db {
213 delete from Like where post_id == id
214 delete from LikeCache where post_id == id
215 } or {
216 eprintln('failed to delete like cache for post during user deletion: ${id}')
217 }
218 }
219
220 sql app.db {
221 delete from Post where author_id == user_id
222 } or {
223 eprintln('failed to delete posts by deleting user: ${user_id}')
224 }
225
226 return true
227}
228
229// search_for_users searches for posts matching the given query.
230// todo: query options/filters, such as created-after:<date>, created-before:<date>, etc
231pub fn (app &DatabaseAccess) search_for_users(query string, limit int, offset int) []User {
232 queried_users := app.db.exec_param_many_result('SELECT * FROM search_for_users($1, $2, $3)', [query, limit.str(), offset.str()]) or {
233 eprintln('search_for_users error in app.db.error: ${err}')
234 pg.Result{}
235 }
236 users := queried_users.rows.map(fn [queried_users] (it pg.Row) User {
237 return User.from_row(queried_users, it)
238 })
239 return users
240}
241
242// get_user_count gets the number of registered users in the database.
243pub fn (app &DatabaseAccess) get_user_count() int {
244 n := app.db.exec('SELECT COUNT(id) FROM "User"') or {
245 eprintln('get_user_count error in app.db.error: ${err}')
246 []
247 }
248 return if n.len == 0 { 0 } else { util.or_throw(n[0].vals[0]).int() }
249}