a mini social media app for small communities
at main 4.0 kB view raw
1module webapp 2 3import veb 4import db.pg 5import regex 6import auth 7import entity { LikeCache, Like, Post, Site, User, Notification } 8import database { DatabaseAccess } 9 10pub struct App { 11 veb.StaticHandler 12 DatabaseAccess 13pub: 14 config Config 15 buildinfo BuildInfo 16 built_at string = @BUILD_TIMESTAMP 17 v_hash string = @VHASH 18pub mut: 19 auth auth.Auth[pg.DB] 20 validators struct { 21 pub mut: 22 username StringValidator 23 password StringValidator 24 nickname StringValidator 25 pronouns StringValidator 26 user_bio StringValidator 27 post_title StringValidator 28 post_body StringValidator 29 } 30} 31 32// get_user_by_token returns a user by their token, returns none if the user was 33// not found. 34pub fn (app &App) get_user_by_token(token string) ?User { 35 user_token := app.auth.find_token(token) or { 36 eprintln('no such user corresponding to token') 37 return none 38 } 39 return app.get_user_by_id(user_token.user_id) 40} 41 42// whoami returns the current logged in user, or none if the user is not logged 43// in. 44pub fn (app &App) whoami(mut ctx Context) ?User { 45 token := ctx.get_cookie('token') or { return none }.trim_space() 46 if token == '' { 47 return none 48 } 49 if user := app.get_user_by_token(token) { 50 if user.username == '' || user.id == 0 { 51 eprintln('a user had a token for the blank user') 52 // Clear token 53 ctx.set_cookie( 54 name: 'token' 55 value: '' 56 same_site: .same_site_none_mode 57 secure: true 58 path: '/' 59 ) 60 return none 61 } 62 return user 63 } else { 64 eprintln('a user had a token for a non-existent user (this token may have been expired and left in cookies)') 65 // Clear token 66 ctx.set_cookie( 67 name: 'token' 68 value: '' 69 same_site: .same_site_none_mode 70 secure: true 71 path: '/' 72 ) 73 return none 74 } 75} 76 77// logged_in_as returns true if the user is logged in as the provided user id. 78pub fn (app &App) logged_in_as(mut ctx Context, id int) bool { 79 if !ctx.is_logged_in() { 80 return false 81 } 82 return app.whoami(mut ctx) or { return false }.id == id 83} 84 85// get_motd returns the site's message of the day. 86@[inline] 87pub fn (app &App) get_motd() string { 88 site := app.get_or_create_site_config() 89 return site.motd 90} 91 92// get_notification_count_for_frontend returns the notification count for a 93// given user, formatted for usage on the frontend. 94pub fn (app &App) get_notification_count_for_frontend(user_id int, limit int) string { 95 count := app.get_notification_count(user_id, limit) 96 if count == 0 { 97 return '' 98 } else if count > limit { 99 return ' (${count}+)' 100 } else { 101 return ' (${count})' 102 } 103} 104 105// process_post_mentions parses a post's body to send notifications for mentions 106// or replies. 107pub fn (app &App) process_post_mentions(post &Post) { 108 author := app.get_user_by_id(post.author_id) or { 109 eprintln('process_post_mentioned called on a post with a non-existent author: ${post}') 110 return 111 } 112 author_name := author.get_name() 113 114 // used so we do not send more than one notification per post 115 mut notified_users := []int{} 116 117 // notify who we replied to, if applicable 118 if post.replying_to != none { 119 if x := app.get_post_by_id(post.replying_to) { 120 app.send_notification_to(x.author_id, '${author_name} replied to your post!', '${author_name} replied to *(${x.id})') 121 } 122 } 123 124 // find mentions 125 mut re := regex.regex_opt('@\\(${app.config.user.username_pattern}\\)') or { 126 eprintln('failed to compile regex for process_post_mentions (err: ${err})') 127 return 128 } 129 matches := re.find_all(post.body) 130 for i := 0 ; i < matches.len ; i += 2 { 131 mat := post.body[matches[i]..matches[i+1]] 132 // skip escaped mentions 133 if matches[i] != 0 && post.body[matches[i] - 1] == `\\` { 134 continue 135 } 136 137 println('found mentioned user: ${mat}') 138 username := mat#[2..-1] 139 user := app.get_user_by_name(username) or { 140 continue 141 } 142 143 if user.id in notified_users || user.id == author.id { 144 continue 145 } 146 notified_users << user.id 147 148 app.send_notification_to( 149 user.id, 150 '${author_name} mentioned you!', 151 'you have been mentioned in this post: *(${post.id})' 152 ) 153 } 154}