a mini social media app for small communities
1module database
2
3import time
4import db.pg
5import entity { Post, User, Like, LikeCache }
6import util
7
8// add_post adds a new post to the database, returns true if this succeeded and
9// false otherwise.
10pub fn (app &DatabaseAccess) add_post(post &Post) bool {
11 sql app.db {
12 insert post into Post
13 } or {
14 return false
15 }
16 return true
17}
18
19// get_post_by_id gets a post by its id, returns none if it does not exist.
20pub fn (app &DatabaseAccess) get_post_by_id(id int) ?Post {
21 posts := sql app.db {
22 select from Post where id == id limit 1
23 } or { [] }
24 if posts.len != 1 {
25 return none
26 }
27 return posts[0]
28}
29
30// get_post_by_author_and_timestamp gets a post by its author and timestamp,
31// returns none if it does not exist
32pub fn (app &DatabaseAccess) get_post_by_author_and_timestamp(author_id int, timestamp time.Time) ?Post {
33 posts := sql app.db {
34 select from Post where author_id == author_id && posted_at == timestamp order by posted_at desc limit 1
35 } or { [] }
36 if posts.len == 0 {
37 return none
38 }
39 return posts[0]
40}
41
42// get_posts_with_tag gets a list of the 10 most recent posts with the given tag.
43// this performs sql string operations and probably is not very efficient, use
44// sparingly.
45pub fn (app &DatabaseAccess) get_posts_with_tag(tag string, offset int) []Post {
46 posts := sql app.db {
47 select from Post where body like '%#(${tag})%' order by posted_at desc limit 10 offset offset
48 } or { [] }
49 return posts
50}
51
52// get_pinned_posts returns a list of all pinned posts.
53pub fn (app &DatabaseAccess) get_pinned_posts() []Post {
54 posts := sql app.db {
55 select from Post where pinned == true
56 } or { [] }
57 return posts
58}
59
60// get_recent_posts returns a list of the ten most recent posts.
61pub fn (app &DatabaseAccess) get_recent_posts() []Post {
62 posts := sql app.db {
63 select from Post order by posted_at desc limit 10
64 } or { [] }
65 return posts
66}
67
68// get_popular_posts returns a list of the ten most liked posts.
69// TODO: make this time-gated (i.e, top ten liked posts of the day)
70pub fn (app &DatabaseAccess) get_popular_posts() []Post {
71 cached_likes := app.db.exec('SELECT post_id FROM "LikeCache" ORDER BY likes DESC LIMIT 10') or { [] }
72 posts := cached_likes.map(fn [app] (it pg.Row) Post {
73 return app.get_post_by_id(util.or_throw(it.vals[0]).int()) or {
74 eprintln('cached like ${it} does not have a post related to it (from get_popular_posts)')
75 return Post{}
76 }
77 }).filter(it.id != 0)
78 return posts
79}
80
81// get_posts_from_user returns a list of all posts from a user in descending
82// order by posting date.
83pub fn (app &DatabaseAccess) get_posts_from_user(user_id int, limit int) []Post {
84 posts := sql app.db {
85 select from Post where author_id == user_id order by posted_at desc limit limit
86 } or { [] }
87 return posts
88}
89
90// get_all_posts_from_user returns a list of all posts from a user in descending
91// order by posting date.
92pub fn (app &DatabaseAccess) get_all_posts_from_user(user_id int) []Post {
93 posts := sql app.db {
94 select from Post where author_id == user_id order by posted_at desc
95 } or { [] }
96 return posts
97}
98
99// pin_post pins the given post, returns true if this succeeds and false
100// otherwise.
101pub fn (app &DatabaseAccess) pin_post(post_id int) bool {
102 sql app.db {
103 update Post set pinned = true where id == post_id
104 } or {
105 return false
106 }
107 return true
108}
109
110// update_post updates the given post's title and body with the given title and
111// body, returns true if this succeeds and false otherwise.
112pub fn (app &DatabaseAccess) update_post(post_id int, new_title string, new_body string, new_nsfw bool) bool {
113 sql app.db {
114 update Post set body = new_body, title = new_title, nsfw = new_nsfw where id == post_id
115 } or {
116 return false
117 }
118 return true
119}
120
121// delete_post deletes the given post and all likes associated with it, returns
122// true if this succeeds and false otherwise.
123pub fn (app &DatabaseAccess) delete_post(id int) bool {
124 sql app.db {
125 delete from Post where id == id
126 delete from Like where post_id == id
127 delete from LikeCache where post_id == id
128 } or {
129 return false
130 }
131 return true
132}
133
134////// searching //////
135
136// PostSearchResult represents a search result for a post.
137pub struct PostSearchResult {
138pub mut:
139 post Post
140 author User
141}
142
143@[inline]
144pub fn PostSearchResult.from_post(app &DatabaseAccess, post &Post) PostSearchResult {
145 return PostSearchResult{
146 post: post
147 author: app.get_user_by_id(post.author_id) or { app.get_unknown_user() }
148 }
149}
150
151@[inline]
152pub fn PostSearchResult.from_post_list(app &DatabaseAccess, posts []Post) []PostSearchResult {
153 mut results := []PostSearchResult{
154 cap: posts.len,
155 len: posts.len
156 }
157 for index, post in posts {
158 results[index] = PostSearchResult.from_post(app, post)
159 }
160 return results
161}
162
163// search_for_posts searches for posts matching the given query.
164// todo: levenshtein distance, query options/filters (user:beep, !excluded-text,
165// etc)
166pub fn (app &DatabaseAccess) search_for_posts(query string, limit int, offset int) []PostSearchResult {
167 queried_posts := app.db.exec_param_many_result('SELECT * FROM search_for_posts($1, $2, $3)', [query, limit.str(), offset.str()]) or {
168 eprintln('search_for_posts error in app.db.error: ${err}')
169 pg.Result{}
170 }
171 posts := queried_posts.rows.map(fn [queried_posts] (it pg.Row) Post {
172 return Post.from_row(queried_posts, it)
173 })
174 return PostSearchResult.from_post_list(app, posts)
175}
176
177// get_post_count gets the number of posts in the database.
178pub fn (app &DatabaseAccess) get_post_count() int {
179 n := app.db.exec('SELECT COUNT(id) FROM "Post"') or {
180 eprintln('get_post_count error in app.db.error: ${err}')
181 []
182 }
183 return if n.len == 0 { 0 } else { util.or_throw(n[0].vals[0]).int() }
184}