+19
doc/resources.md
+19
doc/resources.md
···
9
9
## database design
10
10
11
11
- https://stackoverflow.com/questions/59505855/liked-posts-design-specifics
12
+
- my programmer brain automatically assumed "oh i can just store a list
13
+
in the user table!" turns out, that is a bad implementation.
14
+
- i do have scalability concerns with the current implementation, but i
15
+
can address those in the near future.
12
16
13
17
## sql
14
18
19
+
postgresql documentation: https://www.postgresql.org/docs/
20
+
15
21
- https://stackoverflow.com/questions/11144394/order-sql-by-strongest-like
22
+
- helped me develop the initial search system, which is subject to be
23
+
overhauled, but for now, this helped a lot.
24
+
25
+
## sql security
26
+
27
+

28
+
29
+
source: xkcd, <https://xkcd.com/327/>
30
+
31
+
- sql injections
32
+
- https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html#other-examples-of-safe-prepared-statements
33
+
- https://cheatsheetseries.owasp.org/cheatsheets/Query_Parameterization_Cheat_Sheet.html#using-net-built-in-feature
34
+
- https://www.slideshare.net/slideshow/sql-injection-myths-and-fallacies/3729931#3
+1
doc/todo.md
+1
doc/todo.md
···
36
36
- could be used so that a github webhook can send a message when a new commit is pushed to beep!
37
37
- [ ] site:log new accounts, account deletions, etc etc in an admin-accessible site log
38
38
- this should be set up to only log things when an admin enables it in the site config, so as to only log when necessary
39
+
- [ ] site:implement a database keep-alive system
39
40
40
41
## ideas
41
42
+17
src/beep_sql/beep_sql.v
+17
src/beep_sql/beep_sql.v
···
1
+
module beep_sql
2
+
3
+
import os
4
+
import db.pg
5
+
6
+
fn load_procedures(mut db pg.DB) {
7
+
os.walk('src/beep_sql/procedures/', fn [mut db] (it string) {
8
+
println('-> loading procedure: ${it}')
9
+
db.exec(os.read_file(it) or { panic(err) }) or { panic(err) }
10
+
})
11
+
}
12
+
13
+
pub fn load(mut db pg.DB) {
14
+
println('-> loading sql code')
15
+
load_procedures(mut db)
16
+
println('<- done')
17
+
}
+12
src/beep_sql/procedures/search_posts.sql
+12
src/beep_sql/procedures/search_posts.sql
···
1
+
CREATE OR REPLACE FUNCTION search_for_posts (IN Query TEXT, IN Count INT, IN Index INT)
2
+
RETURNS SETOF "Post"
3
+
AS $$
4
+
SELECT *
5
+
FROM "Post"
6
+
WHERE title LIKE CONCAT('%', Query, '%') OR body LIKE CONCAT('%', Query, '%')
7
+
ORDER BY (CASE
8
+
WHEN title LIKE CONCAT('%', Query, '%') THEN 1
9
+
WHEN body LIKE CONCAT('%', Query, '%') THEN 2
10
+
END)
11
+
LIMIT Count OFFSET Index;
12
+
$$ LANGUAGE SQL;
+12
src/beep_sql/procedures/search_users.sql
+12
src/beep_sql/procedures/search_users.sql
···
1
+
CREATE OR REPLACE FUNCTION search_for_users (IN Query TEXT, IN Count INT, IN Index INT)
2
+
RETURNS SETOF "User"
3
+
AS $$
4
+
SELECT *
5
+
FROM "User"
6
+
WHERE username LIKE CONCAT('%', Query, '%') OR nickname LIKE CONCAT('%', Query, '%')
7
+
ORDER BY (CASE
8
+
WHEN username LIKE CONCAT('%', Query, '%') THEN 1
9
+
WHEN nickname LIKE CONCAT('%', Query, '%') THEN 2
10
+
END)
11
+
LIMIT Count OFFSET Index;
12
+
$$ LANGUAGE SQL;
+4
-14
src/database/post.v
+4
-14
src/database/post.v
···
161
161
}
162
162
163
163
// search_for_posts searches for posts matching the given query.
164
-
// todo: query options/filters, such as user:beep, !excluded-text, etc
164
+
// todo: levenshtein distance, query options/filters (user:beep, !excluded-text,
165
+
// etc)
165
166
pub fn (app &DatabaseAccess) search_for_posts(query string, limit int, offset int) []PostSearchResult {
166
-
//TODO: SANATIZE
167
-
sql_query := "\
168
-
SELECT *, CASE
169
-
WHEN title LIKE '%${query}%' THEN 1
170
-
WHEN body LIKE '%${query}%' THEN 2
171
-
END AS priority
172
-
FROM \"Post\"
173
-
WHERE title LIKE '%${query}%' OR body LIKE '%${query}%'
174
-
ORDER BY priority ASC LIMIT ${limit} OFFSET ${offset}"
175
-
176
-
queried_posts := app.db.q_strings(sql_query) or {
177
-
eprintln('search_for_posts error in app.db.q_strings: ${err}')
167
+
queried_posts := app.db.exec_param_many('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}')
178
169
[]
179
170
}
180
-
181
171
posts := queried_posts.map(|it| Post.from_row(it))
182
172
return PostSearchResult.from_post_list(app, posts)
183
173
}
+2
-13
src/database/user.v
+2
-13
src/database/user.v
···
210
210
// search_for_users searches for posts matching the given query.
211
211
// todo: query options/filters, such as created-after:<date>, created-before:<date>, etc
212
212
pub fn (app &DatabaseAccess) search_for_users(query string, limit int, offset int) []User {
213
-
//TODO: SANATIZE
214
-
sql_query := "\
215
-
SELECT *, CASE
216
-
WHEN username LIKE '%${query}%' THEN 1
217
-
WHEN nickname LIKE '%${query}%' THEN 2
218
-
END AS priority
219
-
FROM \"User\"
220
-
WHERE username LIKE '%${query}%' OR nickname LIKE '%${query}%'
221
-
ORDER BY priority ASC LIMIT ${limit} OFFSET ${offset}"
222
-
223
-
queried_users := app.db.q_strings(sql_query) or {
224
-
eprintln('search_for_users error in app.db.q_strings: ${err}')
213
+
queried_users := app.db.exec_param_many('SELECT * FROM search_for_users($1, $2, $3)', [query, limit.str(), offset.str()]) or {
214
+
eprintln('search_for_users error in app.db.error: ${err}')
225
215
[]
226
216
}
227
-
228
217
users := queried_users.map(|it| User.from_row(it))
229
218
return users
230
219
}
+3
src/main.v
+3
src/main.v
···
6
6
import entity
7
7
import os
8
8
import webapp { App, Context, StringValidator }
9
+
import beep_sql
9
10
10
11
fn main() {
11
12
config := webapp.load_config_from(os.args[1])
···
23
24
defer {
24
25
db.close()
25
26
}
27
+
28
+
beep_sql.load(mut db)
26
29
27
30
mut app := &App{
28
31
config: config