+1
-1
config.maple
+1
-1
config.maple
+1
doc/database_spec.md
+1
doc/database_spec.md
···
18
18
| `password_salt` | string | salt for this user's password |
19
19
| `muted` | bool | controls whether or not this user can make posts |
20
20
| `admin` | bool | controls whether or not this user is an admin |
21
+
| `automated` | bool | controls whether or not this user is automated |
21
22
| `theme` | ?string | controls per-user css themes |
22
23
| `bio` | string | bio for this user |
23
24
| `pronouns` | string | pronouns for this user |
+12
src/database/user.v
+12
src/database/user.v
···
65
65
return true
66
66
}
67
67
68
+
// set_automated sets the given user's automated status, returns true if this
69
+
// succeeded and false otherwise.
70
+
pub fn (app &DatabaseAccess) set_automated(user_id int, automated bool) bool {
71
+
sql app.db {
72
+
update User set automated = automated where id == user_id
73
+
} or {
74
+
eprintln('failed to update automated status for ${user_id}')
75
+
return false
76
+
}
77
+
return true
78
+
}
79
+
68
80
// set_theme sets the given user's theme url, returns true if this succeeded and
69
81
// false otherwise.
70
82
pub fn (app &DatabaseAccess) set_theme(user_id int, theme ?string) bool {
+3
-2
src/entity/user.v
+3
-2
src/entity/user.v
+7
-7
src/main.v
+7
-7
src/main.v
···
43
43
44
44
@[inline]
45
45
fn load_validators(mut app App) {
46
-
app.validators.username = StringValidator.new(app.config.user.username_min_len, app.config.user.username_max_len, app.config.user.username_pattern)
47
-
app.validators.password = StringValidator.new(app.config.user.username_min_len, app.config.user.username_max_len, app.config.user.username_pattern)
48
-
app.validators.nickname = StringValidator.new(app.config.user.nickname_min_len, app.config.user.nickname_max_len, app.config.user.nickname_pattern)
49
-
app.validators.user_bio = StringValidator.new(app.config.user.bio_min_len, app.config.user.bio_max_len, app.config.user.bio_pattern)
50
-
app.validators.pronouns = StringValidator.new(app.config.user.pronouns_min_len, app.config.user.pronouns_max_len, app.config.user.pronouns_pattern)
51
-
app.validators.post_title = StringValidator.new(app.config.post.title_min_len, app.config.post.title_max_len, app.config.post.title_pattern)
52
-
app.validators.post_body = StringValidator.new(app.config.post.body_min_len, app.config.post.body_max_len, app.config.post.body_pattern)
46
+
app.validators.username = StringValidator.new(app.config.user.username_min_len,app.config.user.username_max_len,app.config.user.username_pattern)
47
+
app.validators.password = StringValidator.new(app.config.user.password_min_len,app.config.user.password_max_len,app.config.user.password_pattern)
48
+
app.validators.nickname = StringValidator.new(app.config.user.nickname_min_len,app.config.user.nickname_max_len,app.config.user.nickname_pattern)
49
+
app.validators.user_bio = StringValidator.new(app.config.user.bio_min_len,app.config.user.bio_max_len,app.config.user.bio_pattern)
50
+
app.validators.pronouns = StringValidator.new(app.config.user.pronouns_min_len,app.config.user.pronouns_max_len,app.config.user.pronouns_pattern)
51
+
app.validators.post_title = StringValidator.new(app.config.post.title_min_len,app.config.post.title_max_len,app.config.post.title_pattern)
52
+
app.validators.post_body = StringValidator.new(app.config.post.body_min_len,app.config.post.body_max_len,app.config.post.body_pattern)
53
53
}
54
54
55
55
fn main() {
+23
src/templates/settings.html
+23
src/templates/settings.html
···
94
94
95
95
<hr>
96
96
97
+
<form action="/api/user/set_automated" method="post">
98
+
<label for="is_automated">is automated:</label>
99
+
<input
100
+
type="checkbox"
101
+
name="is_automated"
102
+
id="is_automated"
103
+
value="true"
104
+
@if user.automated
105
+
checked aria-checked
106
+
@end
107
+
>
108
+
<input type="submit" value="save">
109
+
<p>automated accounts are primarily intended to tell users that this account makes posts automatically.</p>
110
+
<p>it will also hide most front-end interactions since the user of this account likely will not be using those very often.</p>
111
+
</form>
112
+
113
+
<hr>
114
+
97
115
<details>
98
116
<summary>dangerous settings (click to reveal)</summary>
99
117
118
+
<br>
119
+
100
120
<details>
101
121
<summary>change password (click to reveal)</summary>
102
122
<form action="/api/user/set_password" method="post">
···
112
132
required aria-required
113
133
autocomplete="off" aria-autocomplete="off"
114
134
>
135
+
<br>
115
136
<label for="new_password">new password:</label>
116
137
<input
117
138
type="password"
···
126
147
<input type="submit" value="save">
127
148
</form>
128
149
</details>
150
+
151
+
<br>
129
152
130
153
<details>
131
154
<summary>account deletion (click to reveal)</summary>
+11
-6
src/templates/user.html
+11
-6
src/templates/user.html
···
8
8
(@viewing.pronouns)
9
9
@end
10
10
11
-
@if viewing.muted && viewing.admin
12
-
[muted admin, somehow]
13
-
@else if viewing.muted
11
+
@if viewing.muted
14
12
[muted]
15
-
@else if viewing.admin
13
+
@end
14
+
15
+
@if viewing.automated
16
+
[automated]
17
+
@end
18
+
19
+
@if viewing.admin
16
20
[admin]
17
21
@end
18
22
</h1>
19
23
20
24
@if app.logged_in_as(mut ctx, viewing.id)
21
-
<script src="/static/js/text_area_counter.js"></script>
22
-
23
25
<p>this is you!</p>
24
26
27
+
@if !user.automated
28
+
<script src="/static/js/text_area_counter.js"></script>
25
29
<div>
26
30
<form action="/api/post/new_post" method="post">
27
31
<h2>new post:</h2>
···
63
67
</script>
64
68
</div>
65
69
<hr>
70
+
@end
66
71
@end
67
72
68
73
@if viewing.bio != ''
+69
-48
src/webapp/api.v
+69
-48
src/webapp/api.v
···
49
49
)
50
50
token := app.auth.add_token(x.id, ctx.ip()) or {
51
51
eprintln(err)
52
-
ctx.error('could not create token for user with id ${x.id}')
52
+
ctx.error('api_user_register: could not create token for user with id ${x.id}')
53
53
return ctx.redirect('/')
54
54
}
55
55
ctx.set_cookie(
···
60
60
path: '/'
61
61
)
62
62
} else {
63
-
eprintln('could not log into newly-created user: ${user}')
63
+
eprintln('api_user_register: could not log into newly-created user: ${user}')
64
64
ctx.error('could not log into newly-created user.')
65
65
}
66
66
···
110
110
return ctx.redirect('/settings')
111
111
}
112
112
113
-
// invalidate tokens
113
+
hashed_new_password := auth.hash_password_with_salt(new_password, user.password_salt)
114
+
if !app.set_password(user.id, hashed_new_password) {
115
+
ctx.error('failed to update password')
116
+
return ctx.redirect('/settings')
117
+
}
118
+
119
+
// invalidate tokens and log out
114
120
app.auth.delete_tokens_for_user(user.id) or {
115
121
eprintln('failed to yeet tokens during password deletion for ${user.id} (${err})')
116
122
return ctx.redirect('/settings')
117
123
}
118
-
119
-
hashed_new_password := auth.hash_password_with_salt(new_password, user.password_salt)
120
-
121
-
if !app.set_password(user.id, hashed_new_password) {
122
-
ctx.error('failed to update password')
123
-
}
124
+
ctx.set_cookie(
125
+
name: 'token'
126
+
value: ''
127
+
same_site: .same_site_none_mode
128
+
secure: true
129
+
path: '/'
130
+
)
124
131
125
132
return ctx.redirect('/login')
126
133
}
···
257
264
}
258
265
}
259
266
267
+
@['/api/user/set_automated'; post]
268
+
fn (mut app App) api_user_set_automated(mut ctx Context, is_automated bool) veb.Result {
269
+
user := app.whoami(mut ctx) or {
270
+
ctx.error('you are not logged in!')
271
+
return ctx.redirect('/login')
272
+
}
273
+
274
+
if !app.set_automated(user.id, is_automated) {
275
+
ctx.error('failed to set automated status.')
276
+
}
277
+
278
+
return ctx.redirect('/me')
279
+
}
280
+
260
281
@['/api/user/set_theme'; post]
261
282
fn (mut app App) api_user_set_theme(mut ctx Context, url string) veb.Result {
262
283
if !app.config.instance.allow_changing_theme {
···
330
351
return ctx.text(user.get_name())
331
352
}
332
353
333
-
/// user/notification ///
334
-
335
-
@['/api/user/notification/clear']
336
-
fn (mut app App) api_user_notification_clear(mut ctx Context, id int) veb.Result {
337
-
user := app.whoami(mut ctx) or {
338
-
ctx.error('you are not logged in!')
339
-
return ctx.redirect('/login')
340
-
}
341
-
342
-
if notification := app.get_notification_by_id(id) {
343
-
if notification.user_id != user.id {
344
-
ctx.error('no such notification for user')
345
-
return ctx.redirect('/inbox')
346
-
} else {
347
-
if !app.delete_notification(id) {
348
-
ctx.error('failed to delete notification')
349
-
return ctx.redirect('/inbox')
350
-
}
351
-
}
352
-
} else {
353
-
ctx.error('no such notification for user')
354
-
}
355
-
356
-
return ctx.redirect('/inbox')
357
-
}
358
-
359
-
@['/api/user/notification/clear_all']
360
-
fn (mut app App) api_user_notification_clear_all(mut ctx Context) veb.Result {
361
-
user := app.whoami(mut ctx) or {
362
-
ctx.error('you are not logged in!')
363
-
return ctx.redirect('/login')
364
-
}
365
-
if !app.delete_notifications_for_user(user.id) {
366
-
ctx.error('failed to delete notifications')
367
-
return ctx.redirect('/inbox')
368
-
}
369
-
return ctx.redirect('/inbox')
370
-
}
371
-
372
354
@['/api/user/delete']
373
355
fn (mut app App) api_user_delete(mut ctx Context, id int) veb.Result {
374
356
user := app.whoami(mut ctx) or {
···
413
395
}
414
396
users := app.search_for_users(query, limit, offset)
415
397
return ctx.json[[]User](users)
398
+
}
399
+
400
+
/// user/notification ///
401
+
402
+
@['/api/user/notification/clear']
403
+
fn (mut app App) api_user_notification_clear(mut ctx Context, id int) veb.Result {
404
+
user := app.whoami(mut ctx) or {
405
+
ctx.error('you are not logged in!')
406
+
return ctx.redirect('/login')
407
+
}
408
+
409
+
if notification := app.get_notification_by_id(id) {
410
+
if notification.user_id != user.id {
411
+
ctx.error('no such notification for user')
412
+
return ctx.redirect('/inbox')
413
+
} else {
414
+
if !app.delete_notification(id) {
415
+
ctx.error('failed to delete notification')
416
+
return ctx.redirect('/inbox')
417
+
}
418
+
}
419
+
} else {
420
+
ctx.error('no such notification for user')
421
+
}
422
+
423
+
return ctx.redirect('/inbox')
424
+
}
425
+
426
+
@['/api/user/notification/clear_all']
427
+
fn (mut app App) api_user_notification_clear_all(mut ctx Context) veb.Result {
428
+
user := app.whoami(mut ctx) or {
429
+
ctx.error('you are not logged in!')
430
+
return ctx.redirect('/login')
431
+
}
432
+
if !app.delete_notifications_for_user(user.id) {
433
+
ctx.error('failed to delete notifications')
434
+
return ctx.redirect('/inbox')
435
+
}
436
+
return ctx.redirect('/inbox')
416
437
}
417
438
418
439
////// post //////