+6
config.maple
+6
config.maple
···
7
7
8
8
default_theme = 'https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css'
9
9
allow_changing_theme = true
10
+
11
+
// instance version
12
+
version = '2025.01'
13
+
14
+
// set this to '' if your instance is closed source (twt)
15
+
source = 'https://github.com/emmathemartian/beep'
10
16
}
11
17
12
18
http = {
+76
-38
src/main.v
+76
-38
src/main.v
···
7
7
import os
8
8
import webapp { App, Context, StringValidator }
9
9
import beep_sql
10
+
import util
10
11
11
-
fn main() {
12
-
config := webapp.load_config_from(os.args[1])
12
+
pub const version = '25.01.0'
13
13
14
-
println('-> connecting to db...')
15
-
mut db := pg.connect(pg.Config{
16
-
host: config.postgres.host
17
-
dbname: config.postgres.db
18
-
user: config.postgres.user
19
-
password: config.postgres.password
20
-
port: config.postgres.port
21
-
})!
22
-
println('<- connected')
23
-
24
-
defer {
25
-
db.close()
14
+
@[inline]
15
+
fn connect(mut app App) {
16
+
println('-> connecting to database...')
17
+
app.db = pg.connect(pg.Config{
18
+
host: app.config.postgres.host
19
+
dbname: app.config.postgres.db
20
+
user: app.config.postgres.user
21
+
password: app.config.postgres.password
22
+
port: app.config.postgres.port
23
+
}) or {
24
+
panic('failed to connect to database: ${err}')
26
25
}
26
+
}
27
27
28
-
beep_sql.load(mut db)
29
-
30
-
mut app := &App{
31
-
config: config
32
-
db: db
33
-
auth: auth.new(db)
34
-
}
35
-
36
-
// vfmt off
37
-
app.validators.username = StringValidator.new(config.user.username_min_len, config.user.username_max_len, config.user.username_pattern)
38
-
app.validators.password = StringValidator.new(config.user.username_min_len, config.user.username_max_len, config.user.username_pattern)
39
-
app.validators.nickname = StringValidator.new(config.user.nickname_min_len, config.user.nickname_max_len, config.user.nickname_pattern)
40
-
app.validators.user_bio = StringValidator.new(config.user.bio_min_len, config.user.bio_max_len, config.user.bio_pattern)
41
-
app.validators.pronouns = StringValidator.new(config.user.pronouns_min_len, config.user.pronouns_max_len, config.user.pronouns_pattern)
42
-
app.validators.post_title = StringValidator.new(config.post.title_min_len, config.post.title_max_len, config.post.title_pattern)
43
-
app.validators.post_body = StringValidator.new(config.post.body_min_len, config.post.body_max_len, config.post.body_pattern)
44
-
// vfmt on
45
-
46
-
app.mount_static_folder_at(app.config.static_path, '/static')!
47
-
48
-
println('-> initializing database...')
49
-
sql db {
28
+
@[inline]
29
+
fn init_db(mut app App) {
30
+
println('-> initializing database')
31
+
sql app.db {
50
32
create table entity.Site
51
33
create table entity.User
52
34
create table entity.Post
···
54
36
create table entity.LikeCache
55
37
create table entity.Notification
56
38
create table entity.SavedPost
57
-
}!
58
-
println('<- done')
39
+
} or {
40
+
panic('failed to initialize database: ${err}')
41
+
}
42
+
}
43
+
44
+
@[inline]
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)
53
+
}
54
+
55
+
fn main() {
56
+
mut stopwatch := util.Stopwatch.new()
57
+
58
+
config := webapp.load_config_from(os.args[1])
59
+
mut app := &App{ config: config }
60
+
61
+
// connect to database
62
+
util.time_it(
63
+
it: fn [mut app] () {
64
+
connect(mut app)
65
+
}
66
+
name: 'connect to db'
67
+
log: true
68
+
)
69
+
70
+
defer { app.db.close() }
71
+
72
+
// add authenticator
73
+
app.auth = auth.new(app.db)
74
+
75
+
// load sql files kept in beep_sql/
76
+
util.time_it(
77
+
it: fn [mut app] () {
78
+
beep_sql.load(mut app.db)
79
+
}
80
+
name: 'load beep_sql'
81
+
log: true
82
+
)
83
+
84
+
// load validators
85
+
load_validators(mut app)
86
+
87
+
// mount static things
88
+
app.mount_static_folder_at(app.config.static_path, '/static')!
89
+
90
+
// initialize database
91
+
util.time_it(it: fn [mut app] () {
92
+
init_db(mut app)
93
+
}, name: 'init db', log: true)
59
94
60
95
// make the website config, if it does not exist
61
96
app.get_or_create_site_config()
···
63
98
if config.dev_mode {
64
99
println('\033[1;31mNOTE: YOU ARE IN DEV MODE\033[0m')
65
100
}
101
+
102
+
stop := stopwatch.stop()
103
+
println('-> took ${stop} to start app')
66
104
67
105
veb.run[App, Context](mut app, app.config.http.port)
68
106
}
+20
src/templates/about.html
+20
src/templates/about.html
···
1
+
@include 'partial/header.html'
2
+
3
+
<h1>about this instance</h1>
4
+
5
+
<div>
6
+
<p>name: @{app.config.instance.name}</p>
7
+
<p>version: @{app.config.instance.version} (commit: @{app.commit})</p>
8
+
<p>built at @{app.built_at} (<span id="built_at">date n/a</span>)</p>
9
+
<p>built using @{app.v_hash}</p>
10
+
11
+
@if app.config.instance.source != ''
12
+
<p>source: <a href="@{app.config.instance.source}">@{app.config.instance.source}</a></p>
13
+
@end
14
+
</div>
15
+
16
+
<script>
17
+
document.getElementById('built_at').innerText = new Date(@{app.built_at} * 1000).toLocaleString()
18
+
</script>
19
+
20
+
@include 'partial/footer.html'
+44
src/util/stopwatch.v
+44
src/util/stopwatch.v
···
1
+
module util
2
+
3
+
import time
4
+
5
+
@[noinit]
6
+
pub struct Stopwatch {
7
+
pub:
8
+
start time.Time = time.now()
9
+
pub mut:
10
+
stop time.Time
11
+
took ?time.Duration
12
+
}
13
+
14
+
@[inline]
15
+
pub fn Stopwatch.new() Stopwatch {
16
+
return Stopwatch{}
17
+
}
18
+
19
+
@[inline]
20
+
pub fn (mut stopwatch Stopwatch) stop() time.Duration {
21
+
stopwatch.stop = time.now()
22
+
duration := stopwatch.stop - stopwatch.start
23
+
stopwatch.took = duration
24
+
return duration
25
+
}
26
+
27
+
@[params]
28
+
pub struct TimeItParams {
29
+
pub:
30
+
it fn () @[required]
31
+
name string
32
+
log bool
33
+
}
34
+
35
+
@[inline]
36
+
pub fn time_it(params TimeItParams) Stopwatch {
37
+
mut stopwatch := Stopwatch.new()
38
+
params.it()
39
+
took := stopwatch.stop()
40
+
if params.log {
41
+
println('-> (time_it) ${params.name} took ${took}')
42
+
}
43
+
return stopwatch
44
+
}
+4
-1
src/webapp/app.v
+4
-1
src/webapp/app.v
+4
src/webapp/config.v
+4
src/webapp/config.v
···
13
13
welcome string
14
14
default_theme string
15
15
allow_changing_theme bool
16
+
version string
17
+
source string
16
18
}
17
19
http struct {
18
20
pub mut:
···
72
74
config.instance.welcome = loaded_instance.get('welcome').to_str()
73
75
config.instance.default_theme = loaded_instance.get('default_theme').to_str()
74
76
config.instance.allow_changing_theme = loaded_instance.get('allow_changing_theme').to_bool()
77
+
config.instance.version = loaded_instance.get('version').to_str()
78
+
config.instance.source = loaded_instance.get('source').to_str()
75
79
76
80
loaded_http := loaded.get('http')
77
81
config.http.port = loaded_http.get('port').to_int()
+7
src/webapp/pages.v
+7
src/webapp/pages.v
···
206
206
ctx.title = '${app.config.instance.name} - search'
207
207
return $veb.html('../templates/search.html')
208
208
}
209
+
210
+
@['/about']
211
+
fn (mut app App) about(mut ctx Context) veb.Result {
212
+
user := app.whoami(mut ctx) or { User{} }
213
+
ctx.title = '${app.config.instance.name} - about'
214
+
return $veb.html('../templates/about.html')
215
+
}