a mini social media app for small communities

post searching implemented

Changed files
+63 -6
doc
src
database
entity
util
+4
doc/resources.md
··· 9 ## database design 10 11 - https://stackoverflow.com/questions/59505855/liked-posts-design-specifics
··· 9 ## database design 10 11 - https://stackoverflow.com/questions/59505855/liked-posts-design-specifics 12 + 13 + ## sql 14 + 15 + - https://stackoverflow.com/questions/11144394/order-sql-by-strongest-like
+15 -6
src/database/post.v
··· 163 // search_for_post searches for posts matching the given query. 164 // todo: query options/filters, such as user:beep, !excluded-text, etc 165 pub fn (app &DatabaseAccess) search_for_posts(query string, limit int, offset int) []PostSearchResult { 166 - println('searching, q=${query},l=${limit},o=${offset}') 167 - //todo: app.db.q_strings('select from Post where title like \'%${query}%\' or body like \'%${query}%\' order by posted_at desc limit limit offset offset') 168 - posts := sql app.db { 169 - select from Post where title like '%${query}%' order by posted_at desc limit limit offset offset 170 - } or { [] } 171 - println('search results: ${posts.len}') 172 return PostSearchResult.from_post_list(app, posts) 173 }
··· 163 // search_for_post searches for posts matching the given query. 164 // todo: query options/filters, such as user:beep, !excluded-text, etc 165 pub fn (app &DatabaseAccess) search_for_posts(query string, limit int, offset int) []PostSearchResult { 166 + sql_query := "\ 167 + SELECT *, CASE 168 + WHEN title LIKE '%${query}%' THEN 1 169 + WHEN body LIKE '%${query}%' THEN 2 170 + END AS priority 171 + FROM \"Post\" 172 + WHERE title LIKE '%${query}%' OR body LIKE '%${query}%' 173 + ORDER BY priority ASC LIMIT ${limit} OFFSET ${offset}" 174 + 175 + queried_posts := app.db.q_strings(sql_query) or { 176 + eprintln('search_for_posts error in app.db.q_strings: ${err}') 177 + [] 178 + } 179 + 180 + posts := queried_posts.map(|it| Post.from_row(it)) 181 return PostSearchResult.from_post_list(app, posts) 182 }
+23
src/entity/post.v
··· 1 module entity 2 3 import time 4 5 pub struct Post { 6 pub mut: ··· 15 16 posted_at time.Time = time.now() 17 }
··· 1 module entity 2 3 + import db.pg 4 import time 5 + import util 6 7 pub struct Post { 8 pub mut: ··· 17 18 posted_at time.Time = time.now() 19 } 20 + 21 + // Post.from_row creates a post from the given database row. 22 + // see src/database/post.v#search_for_posts for usage. 23 + @[inline] 24 + pub fn Post.from_row(row pg.Row) Post { 25 + // this throws a cgen error when put in Post{} 26 + //todo: report this 27 + posted_at := time.parse(util.or_throw[string](row.vals[6])) or { panic(err) } 28 + 29 + return Post{ 30 + id: util.or_throw[string](row.vals[0]).int() 31 + author_id: util.or_throw[string](row.vals[1]).int() 32 + replying_to: if row.vals[2] == none { ?int(none) } else { 33 + util.map_or_throw[string, int](row.vals[2], |it| it.int()) 34 + } 35 + title: util.or_throw[string](row.vals[3]) 36 + body: util.or_throw[string](row.vals[4]) 37 + pinned: util.map_or_throw[string, bool](row.vals[5], |it| it.bool()) 38 + posted_at: posted_at 39 + } 40 + }
+21
src/util/none.v
···
··· 1 + module util 2 + 3 + @[inline] 4 + pub fn map_or[T, R](val ?T, mapper fn (T) R, or_else R) R { 5 + return if val == none { or_else } else { mapper(val) } 6 + } 7 + 8 + @[inline] 9 + pub fn map_or_throw[T, R](val ?T, mapper fn (T) R) R { 10 + return if val == none { panic('value was none: ${val}') } else { mapper(val) } 11 + } 12 + 13 + @[inline] 14 + pub fn map_or_opt[T, R](val ?T, mapper fn (T) ?R, or_else ?R) ?R { 15 + return if val == none { or_else } else { mapper(val) } 16 + } 17 + 18 + @[inline] 19 + pub fn or_throw[T](val ?T) T { 20 + return if val == none { panic('value was none: ${val}') } else { val } 21 + }