a mini social media app for small communities

select optimizations

Changed files
+35 -46
doc
src
+1 -1
build.maple
··· 64 task:run.real = { 65 description = 'Run beep using config.real.maple' 66 category = 'run' 67 - run = '${v} -d veb_livereload run ${v_main} config.real.maple' 68 } 69 70 task:cloc = {
··· 64 task:run.real = { 65 description = 'Run beep using config.real.maple' 66 category = 'run' 67 + run = '${v} run ${v_main} config.real.maple' 68 } 69 70 task:cloc = {
+1 -1
doc/todo.md
··· 23 created-before:<date> 24 is:admin 25 ``` 26 - - [ ] misc:replace `SEARCH *` with `SEARCH <column>` 27 28 ## planing 29 ··· 65 discord, and other common links. we want those ones to look fancy! 66 - [x] post:saving (add the post to a list of saved posts that a user can view later) 67 - [x] site:message of the day (admins can add a welcome message displayed on index.html) 68 69 ## graveyard 70
··· 23 created-before:<date> 24 is:admin 25 ``` 26 27 ## planing 28 ··· 64 discord, and other common links. we want those ones to look fancy! 65 - [x] post:saving (add the post to a list of saved posts that a user can view later) 66 - [x] site:message of the day (admins can add a welcome message displayed on index.html) 67 + - [x] misc:replace `SELECT *` with `SELECT <column>` 68 69 ## graveyard 70
+5 -9
src/database/like.v
··· 1 module database 2 3 import entity { Like, LikeCache } 4 5 // add_like adds a like to the database, returns true if this succeeds and false 6 // otherwise. ··· 18 // get_net_likes_for_post returns the net likes of the given post. 19 pub fn (app &DatabaseAccess) get_net_likes_for_post(post_id int) int { 20 // check cache 21 - cache := sql app.db { 22 - select from LikeCache where post_id == post_id limit 1 23 - } or { [] } 24 25 mut likes := 0 26 27 if cache.len != 1 { 28 println('calculating net likes for post: ${post_id}') 29 // calculate 30 - db_likes := sql app.db { 31 - select from Like where post_id == post_id 32 - } or { [] } 33 - 34 for like in db_likes { 35 - if like.is_like { 36 likes++ 37 } else { 38 likes-- ··· 51 return likes 52 } 53 } else { 54 - likes = cache.first().likes 55 } 56 57 return likes
··· 1 module database 2 3 import entity { Like, LikeCache } 4 + import util 5 6 // add_like adds a like to the database, returns true if this succeeds and false 7 // otherwise. ··· 19 // get_net_likes_for_post returns the net likes of the given post. 20 pub fn (app &DatabaseAccess) get_net_likes_for_post(post_id int) int { 21 // check cache 22 + cache := app.db.exec_param('SELECT likes FROM "LikeCache" WHERE post_id = $1 LIMIT 1', post_id.str()) or { [] } 23 24 mut likes := 0 25 26 if cache.len != 1 { 27 println('calculating net likes for post: ${post_id}') 28 // calculate 29 + db_likes := app.db.exec_param('SELECT is_like FROM "Like" WHERE post_id = $1', post_id.str()) or { [] } 30 for like in db_likes { 31 + if util.or_throw(like.vals[0]).bool() { 32 likes++ 33 } else { 34 likes-- ··· 47 return likes 48 } 49 } else { 50 + likes = util.or_throw(cache.first().vals[0]).int() 51 } 52 53 return likes
+1 -3
src/database/notification.v
··· 47 // get_notification_count gets the amount of notifications a user has, with a 48 // given limit. 49 pub fn (app &DatabaseAccess) get_notification_count(user_id int, limit int) int { 50 - notifications := sql app.db { 51 - select from Notification where user_id == user_id limit limit 52 - } or { [] } 53 return notifications.len 54 } 55
··· 47 // get_notification_count gets the amount of notifications a user has, with a 48 // given limit. 49 pub fn (app &DatabaseAccess) get_notification_count(user_id int, limit int) int { 50 + notifications := app.db.exec_param2('SELECT id FROM "Notification" WHERE user_id = $1 LIMIT $2', user_id.str(), limit.str()) or { [] } 51 return notifications.len 52 } 53
+5 -5
src/database/post.v
··· 1 module database 2 3 import time 4 import entity { Post, User, Like, LikeCache } 5 6 // add_post adds a new post to the database, returns true if this succeeded and 7 // false otherwise. ··· 66 // get_popular_posts returns a list of the ten most liked posts. 67 // TODO: make this time-gated (i.e, top ten liked posts of the day) 68 pub fn (app &DatabaseAccess) get_popular_posts() []Post { 69 - cached_likes := sql app.db { 70 - select from LikeCache order by likes desc limit 10 71 - } or { [] } 72 - posts := cached_likes.map(fn [app] (it LikeCache) Post { 73 - return app.get_post_by_id(it.post_id) or { 74 eprintln('cached like ${it} does not have a post related to it (from get_popular_posts)') 75 return Post{} 76 }
··· 1 module database 2 3 import time 4 + import db.pg 5 import entity { Post, User, Like, LikeCache } 6 + import util 7 8 // add_post adds a new post to the database, returns true if this succeeded and 9 // false otherwise. ··· 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) 70 pub 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 }
+7 -6
src/database/saved_post.v
··· 1 module database 2 3 import entity { SavedPost, Post } 4 5 // get_saved_posts_for gets all SavedPost objects for a given user. 6 pub fn (app &DatabaseAccess) get_saved_posts_for(user_id int) []SavedPost { ··· 13 // get_saved_posts_as_post_for gets all saved posts for a given user converted 14 // to Post objects. 15 pub fn (app &DatabaseAccess) get_saved_posts_as_post_for(user_id int) []Post { 16 - saved_posts := sql app.db { 17 - select from SavedPost where user_id == user_id && saved == true 18 - } or { [] } 19 - posts := saved_posts.map(fn [app] (it SavedPost) Post { 20 - return app.get_post_by_id(it.post_id) or { 21 // if the post does not exist, we will remove it now 22 sql app.db { 23 - delete from SavedPost where id == it.id 24 } or { 25 eprintln('get_saved_posts_as_post_for: failed to remove non-existent post from saved post: ${it}') 26 }
··· 1 module database 2 3 + import db.pg 4 import entity { SavedPost, Post } 5 + import util 6 7 // get_saved_posts_for gets all SavedPost objects for a given user. 8 pub fn (app &DatabaseAccess) get_saved_posts_for(user_id int) []SavedPost { ··· 15 // get_saved_posts_as_post_for gets all saved posts for a given user converted 16 // to Post objects. 17 pub fn (app &DatabaseAccess) get_saved_posts_as_post_for(user_id int) []Post { 18 + saved_posts := app.db.exec_param('SELECT id, post_id FROM "SavedPost" WHERE user_id = $1 AND saved = TRUE', user_id.str()) or { [] } 19 + posts := saved_posts.map(fn [app] (it pg.Row) Post { 20 + return app.get_post_by_id(util.or_throw(it.vals[1]).int()) or { 21 // if the post does not exist, we will remove it now 22 + id := util.or_throw(it.vals[0]).int() 23 sql app.db { 24 + delete from SavedPost where id == id 25 } or { 26 eprintln('get_saved_posts_as_post_for: failed to remove non-existent post from saved post: ${it}') 27 }
+14 -20
src/database/user.v
··· 1 module database 2 3 import entity { User, Notification, Like, LikeCache, Post } 4 5 // new_user creates a new user and returns their struct after creation. 6 pub fn (app &DatabaseAccess) new_user(user User) ?User { ··· 134 135 // does_user_like_post returns true if a user likes the given post. 136 pub fn (app &DatabaseAccess) does_user_like_post(user_id int, post_id int) bool { 137 - likes := sql app.db { 138 - select from Like where user_id == user_id && post_id == post_id 139 - } or { [] } 140 if likes.len > 1 { 141 // something is very wrong lol 142 - eprintln('a user somehow got two or more likes on the same post (user: ${user_id}, post: ${post_id})') 143 } else if likes.len == 0 { 144 return false 145 } 146 - return likes.first().is_like 147 } 148 149 // does_user_dislike_post returns true if a user dislikes the given post. 150 pub fn (app &DatabaseAccess) does_user_dislike_post(user_id int, post_id int) bool { 151 - likes := sql app.db { 152 - select from Like where user_id == user_id && post_id == post_id 153 - } or { [] } 154 if likes.len > 1 { 155 // something is very wrong lol 156 - eprintln('a user somehow got two or more likes on the same post (user: ${user_id}, post: ${post_id})') 157 } else if likes.len == 0 { 158 return false 159 } 160 - return !likes.first().is_like 161 } 162 163 // does_user_like_or_dislike_post returns true if a user likes *or* dislikes the 164 // given post. 165 pub fn (app &DatabaseAccess) does_user_like_or_dislike_post(user_id int, post_id int) bool { 166 - likes := sql app.db { 167 - select from Like where user_id == user_id && post_id == post_id 168 - } or { [] } 169 if likes.len > 1 { 170 // something is very wrong lol 171 - eprintln('a user somehow got two or more likes on the same post (user: ${user_id}, post: ${post_id})') 172 } 173 return likes.len == 1 174 } ··· 185 } 186 187 // delete posts and their likes 188 - posts_from_this_user := sql app.db { 189 - select from Post where author_id == user_id 190 - } or { [] } 191 192 for post in posts_from_this_user { 193 sql app.db { 194 - delete from Like where post_id == post.id 195 - delete from LikeCache where post_id == post.id 196 } or { 197 - eprintln('failed to delete like cache for post during user deletion: ${post.id}') 198 } 199 } 200
··· 1 module database 2 3 import entity { User, Notification, Like, LikeCache, Post } 4 + import util 5 6 // new_user creates a new user and returns their struct after creation. 7 pub fn (app &DatabaseAccess) new_user(user User) ?User { ··· 135 136 // does_user_like_post returns true if a user likes the given post. 137 pub fn (app &DatabaseAccess) does_user_like_post(user_id int, post_id int) bool { 138 + 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 { [] } 139 if likes.len > 1 { 140 // something is very wrong lol 141 + eprintln('does_user_like_post: a user somehow got two or more likes on the same post (user: ${user_id}, post: ${post_id})') 142 } else if likes.len == 0 { 143 return false 144 } 145 + return util.or_throw(likes.first().vals[1]).bool() 146 } 147 148 // does_user_dislike_post returns true if a user dislikes the given post. 149 pub fn (app &DatabaseAccess) does_user_dislike_post(user_id int, post_id int) bool { 150 + 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 { [] } 151 if likes.len > 1 { 152 // something is very wrong lol 153 + eprintln('does_user_dislike_post: a user somehow got two or more likes on the same post (user: ${user_id}, post: ${post_id})') 154 } else if likes.len == 0 { 155 return false 156 } 157 + return !util.or_throw(likes.first().vals[1]).bool() 158 } 159 160 // does_user_like_or_dislike_post returns true if a user likes *or* dislikes the 161 // given post. 162 pub fn (app &DatabaseAccess) does_user_like_or_dislike_post(user_id int, post_id int) bool { 163 + likes := app.db.exec_param2('SELECT id 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_or_dislike_post: a user somehow got two or more likes on the same post (user: ${user_id}, post: ${post_id})') 167 } 168 return likes.len == 1 169 } ··· 180 } 181 182 // delete posts and their likes 183 + posts_from_this_user := app.db.exec_param('SELECT id FROM "Post" WHERE author_id = $1', user_id.str()) or { [] } 184 185 for post in posts_from_this_user { 186 + id := util.or_throw(post.vals[0]).int() 187 sql app.db { 188 + delete from Like where post_id == id 189 + delete from LikeCache where post_id == id 190 } or { 191 + eprintln('failed to delete like cache for post during user deletion: ${id}') 192 } 193 } 194
+1 -1
src/entity/likes.v
··· 13 pub struct LikeCache { 14 pub mut: 15 id int @[primary; sql: serial] 16 - post_id int 17 likes int 18 }
··· 13 pub struct LikeCache { 14 pub mut: 15 id int @[primary; sql: serial] 16 + post_id int @[unique] 17 likes int 18 }