a mini social media app for small communities

add motd and further expand on themes also *even* more cleanup (big surprise there!)

+1 -1
config.maple
··· 49 49 bio_max_len = 200 50 50 bio_pattern = '(.|\s)*' 51 51 52 - default_theme = '' 52 + default_theme = 'https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css' 53 53 allow_changing_theme = true 54 54 }
+13 -10
doc/themes.md
··· 17 17 > is not even close to comprehensive. these are just my personal picks that i 18 18 > found to be comfy with beep! 19 19 20 - | name | source | css theme url | 21 - |-------------------------------|-----------------------------------------|-------------------------------------------------------------| 22 - | sakura | <https://github.com/oxalorg/sakura> | https://cdn.jsdelivr.net/npm/sakura.css/css/sakura.css | 23 - | water.css (auto) | <https://github.com/kognise/water.css> | https://cdn.jsdelivr.net/npm/water.css@2/out/water.min.css | 24 - | water.css (dark) | <https://github.com/kognise/water.css> | https://cdn.jsdelivr.net/npm/water.css@2/out/dark.min.css | 25 - | water.css (light) | <https://github.com/kognise/water.css> | https://cdn.jsdelivr.net/npm/water.css@2/out/light.min.css | 26 - | kacit | <https://github.com/Kimeiga/kacit> | https://cdn.rawgit.com/Kimeiga/kacit/b3f813ed/kacit.min.css | 20 + | name | source | css theme url | 21 + |------------|------------------------------------------|-------------------------------------------------------------------------| 22 + | sakura | <https://github.com/oxalorg/sakura> | https://cdn.jsdelivr.net/npm/sakura.css/css/sakura.css | 23 + | water.css | <https://github.com/kognise/water.css> | https://cdn.jsdelivr.net/npm/water.css@2/out/water.min.css | 24 + | kacit | <https://github.com/Kimeiga/kacit> | https://cdn.rawgit.com/Kimeiga/kacit/b3f813ed/kacit.min.css | 25 + | pico | <https://github.com/picocss/pico> | https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css | 26 + | simple.css | <https://github.com/kevquirk/simple.css> | https://unpkg.com/simpledotcss/simple.min.css | 27 27 28 28 > here is a big list of drop-in themes: 29 29 > <https://github.com/dohliam/dropin-minimal-css> ··· 38 38 39 39 ## built-in 40 40 41 - | name | based on (if applicable) | css theme url | 42 - |---------------------------|---------------------------------|-------------------------------| 43 - | catppuccin-macchiato-pink | water.css + catpuccin macchiato | catppuccin-macchiato-pink.css | 41 + | name | based on (if applicable) | css theme url | 42 + |-----------------------------|---------------------------------|---------------------------------| 43 + | catppuccin-macchiato-pink | water.css + catpuccin macchiato | catppuccin-macchiato-pink.css | 44 + | catppuccin-macchiato-green | water.css + catpuccin macchiato | catppuccin-macchiato-green.css | 45 + | catppuccin-macchiato-yellow | water.css + catpuccin macchiato | catppuccin-macchiato-yellow.css | 46 + | hackerman | simple.css | hackerman.css | 44 47 45 48 > beep also features some built-in themes, some of which are based on the themes 46 49 > present in the "it just works" list!
+3 -2
doc/todo.md
··· 4 4 5 5 ## in-progress 6 6 7 + - [ ] post:mentioning ('tagging') other users in posts 8 + 7 9 ## planing 8 10 9 - - [ ] site:message of the day (admins can add a welcome message displayed on index.html) 10 - - [ ] post:mentioning ('tagging') other users in posts 11 11 - [ ] post:replies 12 12 - [ ] post:tags ('hashtags') 13 13 - [ ] post:images (should have a config.maple toggle to enable/disable) ··· 27 27 - [x] post:likes/dislikes 28 28 - [ ] ~~site:stylesheet (and a toggle for html-only mode)~~ 29 29 - replaced with per-user optional stylesheets 30 + - [x] site:message of the day (admins can add a welcome message displayed on index.html)
+27 -1
src/api.v
··· 2 2 3 3 import veb 4 4 import auth 5 - import entity { User, Post, Like, LikeCache } 5 + import entity { Site, User, Post, Like, LikeCache } 6 6 7 7 ////// Users ////// 8 8 ··· 436 436 return ctx.ok('disliked post') 437 437 } 438 438 } 439 + 440 + ////// Site ////// 441 + 442 + @['/api/site/set_motd'; post] 443 + fn (mut app App) api_site_set_motd(mut ctx Context, motd string) veb.Result { 444 + user := app.whoami(mut ctx) or { 445 + ctx.error('not logged in!') 446 + return ctx.redirect('/login') 447 + } 448 + 449 + if user.admin { 450 + sql app.db { 451 + update Site set motd = motd where id == 1 452 + } or { 453 + ctx.error('failed to set motd') 454 + eprintln('failed to set motd: ${motd}') 455 + return ctx.redirect('/') 456 + } 457 + println('set motd to: ${motd}') 458 + return ctx.redirect('/') 459 + } else { 460 + ctx.error('insufficient permissions!') 461 + eprintln('insufficient perms to set motd to: ${motd} (${user.id})') 462 + return ctx.redirect('/') 463 + } 464 + }
+25 -1
src/app.v
··· 3 3 import veb 4 4 import db.pg 5 5 import auth 6 - import entity { User, Post, Like, LikeCache } 6 + import entity { Site, User, Post, Like, LikeCache } 7 7 8 8 pub struct App { 9 9 veb.StaticHandler ··· 220 220 221 221 return likes 222 222 } 223 + 224 + pub fn (app &App) get_or_create_site_config() Site { 225 + configs := sql app.db { 226 + select from entity.Site 227 + } or { [] } 228 + if configs.len == 0 { 229 + // make the site config 230 + site_config := entity.Site{ } 231 + sql app.db { 232 + insert site_config into entity.Site 233 + } or { 234 + panic('failed to create site config (${err})') 235 + } 236 + } else if configs.len > 1 { 237 + // this should never happen 238 + panic('there are multiple site configs') 239 + } 240 + return configs[0] 241 + } 242 + 243 + pub fn (app &App) get_motd() string { 244 + site := app.get_or_create_site_config() 245 + return site.motd 246 + }
-1
src/entity/post.v
··· 2 2 3 3 import time 4 4 5 - @[json: 'post'] 6 5 pub struct Post { 7 6 pub mut: 8 7 id int @[primary; sql: serial]
+7
src/entity/site.v
··· 1 + module entity 2 + 3 + pub struct Site { 4 + pub mut: 5 + id int @[primary; sql: serial] 6 + motd string 7 + }
+7 -1
src/entity/user.v
··· 2 2 3 3 import time 4 4 5 - @[json: 'user'] 6 5 pub struct User { 7 6 pub mut: 8 7 id int @[primary; sql: serial] ··· 32 31 pub fn (user User) get_theme() string { 33 32 return user.theme or { '' } 34 33 } 34 + 35 + @[inline] 36 + pub fn (user User) to_str_without_sensitive_data() string { 37 + return user.str() 38 + .replace(user.password, '*'.repeat(16)) 39 + .replace(user.password_salt, '*'.repeat(16)) 40 + }
+4
src/main.v
··· 8 8 9 9 fn init_db(db pg.DB) ! { 10 10 sql db { 11 + create table entity.Site 11 12 create table entity.User 12 13 create table entity.Post 13 14 create table entity.Like ··· 47 48 48 49 app.mount_static_folder_at(app.config.static_path, '/static')! 49 50 init_db(db)! 51 + 52 + // make the website config, if it does not exist 53 + app.get_or_create_site_config() 50 54 51 55 if config.dev_mode { 52 56 println('\033[1;31mNOTE: YOU ARE IN DEV MODE\033[0m')
+1
src/pages.v
··· 8 8 user := app.whoami(mut ctx) or { User{} } 9 9 recent_posts := app.get_recent_posts() 10 10 pinned_posts := app.get_pinned_posts() 11 + motd := app.get_motd() 11 12 return $veb.html() 12 13 } 13 14
+4
src/static/style.css
··· 7 7 margin: 0; 8 8 } 9 9 10 + .post + .post { 11 + margin-top: 6px; 12 + } 13 + 10 14 pre { 11 15 white-space: pre-wrap; 12 16 }
+5
src/static/themes/catppuccin-macchiato-green.css
··· 1 + @import url('/static/themes/catppuccin-macchiato.css'); 2 + 3 + :root { 4 + --accent: var(--ctp-macchiato-green); 5 + }
+1 -59
src/static/themes/catppuccin-macchiato-pink.css
··· 1 - @import url('https://cdn.jsdelivr.net/npm/water.css@2/out/dark.min.css'); 2 - @import url('https://unpkg.com/@catppuccin/palette/css/catppuccin.css'); 1 + @import url('/static/themes/catppuccin-macchiato.css'); 3 2 4 3 :root { 5 4 --accent: var(--ctp-macchiato-pink); 6 - 7 - --background-body: var(--ctp-macchiato-base); 8 - --background: var(--ctp-macchiato-base); 9 - --background-alt: var(--ctp-macchiato-crust); 10 - --selection: var(--ctp-macchiato-overlay-2); 11 - --text-main: var(--ctp-macchiato-text); 12 - --text-bright: var(--ctp-macchiato-overlay-1); 13 - --text-muted: var(--ctp-macchiato-subtext-0); 14 - --links: var(--ctp-macchiato-blue); 15 - --focus: var(--accent); 16 - --border: var(--accent); 17 - --code: var(--ctp-macchiato-text); 18 - --button-hover: var(--ctp-macchiato-overlay-3); 19 - --scrollbar-thumb: var(--accent); 20 - --form-placeholder: var(--ctp-macchiato-overlay-1); 21 - --form-text: var(--ctp-macchiato-text); 22 - --variable: var(--ctp-macchiato-pink); 23 - --highlight: var(--ctp-macchiato-blue); 24 - --select-arrow: var(--accent); 25 - } 26 - 27 - input { 28 - border: 1px solid var(--border); 29 - margin-right: none; 30 - width: calc(100% - 2px); 31 - padding-right: 10px; 32 - padding-left: 10px; 33 - } 34 - 35 - input[type="text"], 36 - input[type="number"], 37 - input[type="url"] { 38 - width: calc(100% - 22px); 39 - } 40 - 41 - input[type="button"], 42 - input[type="submit"] { 43 - width: 100%; 44 - } 45 - 46 - textarea { 47 - border: 1px solid var(--border); 48 - width: 100%; 49 - padding-right: 10px; 50 - padding-left: 10px; 51 - margin-right: none; 52 - } 53 - 54 - .post { 55 - border: 1px solid var(--border); 56 - width: calc(100% - 20px); 57 - border-radius: 6px; 58 - padding: 10px; 59 - } 60 - 61 - .post.post-full #id { 62 - display: none; 63 5 }
+5
src/static/themes/catppuccin-macchiato-yellow.css
··· 1 + @import url('/static/themes/catppuccin-macchiato.css'); 2 + 3 + :root { 4 + --accent: var(--ctp-macchiato-yellow); 5 + }
+61
src/static/themes/catppuccin-macchiato.css
··· 1 + @import url('https://cdn.jsdelivr.net/npm/water.css@2/out/dark.min.css'); 2 + @import url('https://unpkg.com/@catppuccin/palette/css/catppuccin.css'); 3 + 4 + :root { 5 + --background-body: var(--ctp-macchiato-base); 6 + --background: var(--ctp-macchiato-mantle); 7 + --background-alt: var(--ctp-macchiato-crust); 8 + --selection: var(--ctp-macchiato-overlay-2); 9 + --text-main: var(--ctp-macchiato-text); 10 + --text-bright: var(--ctp-macchiato-overlay-1); 11 + --text-muted: var(--ctp-macchiato-subtext-0); 12 + --links: var(--accent); 13 + --focus: var(--accent); 14 + --border: var(--accent); 15 + --code: var(--ctp-macchiato-text); 16 + --button-hover: var(--ctp-macchiato-mantle); 17 + --scrollbar-thumb: var(--accent); 18 + --form-placeholder: var(--ctp-macchiato-overlay-1); 19 + --form-text: var(--ctp-macchiato-text); 20 + --variable: var(--ctp-macchiato-pink); 21 + --highlight: var(--ctp-macchiato-blue); 22 + --select-arrow: var(--accent); 23 + } 24 + 25 + input { 26 + border: 1px solid var(--border); 27 + margin-right: none; 28 + width: calc(100% - 2px); 29 + padding-right: 10px; 30 + padding-left: 10px; 31 + } 32 + 33 + input[type="text"], 34 + input[type="number"], 35 + input[type="url"] { 36 + width: calc(100% - 22px); 37 + } 38 + 39 + input[type="button"], 40 + input[type="submit"] { 41 + width: 100%; 42 + } 43 + 44 + textarea { 45 + border: 1px solid var(--border); 46 + width: 100%; 47 + padding-right: 10px; 48 + padding-left: 10px; 49 + margin-right: none; 50 + } 51 + 52 + .post { 53 + border: 1px solid var(--border); 54 + width: calc(100% - 20px); 55 + border-radius: 6px; 56 + padding: 10px; 57 + } 58 + 59 + .post.post-full #id { 60 + display: none; 61 + }
+15
src/static/themes/hackerman.css
··· 1 + @import url('https://unpkg.com/simpledotcss/simple.min.css'); 2 + 3 + :root { 4 + color-scheme: dark; 5 + --bg: black; 6 + --accent-bg: black; 7 + --text: green; 8 + --text-light: light-green; 9 + --accent: green; 10 + --accent-hover: light-green 11 + --accent-text: black; 12 + --code: green; 13 + --preformatted: green; 14 + --disabled: #333; 15 + }
+15 -1
src/templates/admin.html
··· 7 7 @else 8 8 9 9 <h1>admin dashboard</h1> 10 + 10 11 <p>logged in as:</p> 11 - <pre>@user</pre> 12 + <details> 13 + <summary>show user details (<strong>SENSITIVE DATA IS CENSORED BUT FOR THE SAKE OF GOOD PRACTICE, DO NOT SHOW THIS DATA TO ANYONE</strong>)</summary> 14 + <pre>@user.to_str_without_sensitive_data()</pre> 15 + </details> 16 + 17 + <div> 18 + <h2>site settings:</h2> 19 + <form action="/api/site/set_motd" method="post"> 20 + <label for="motd">message of the day (motd): </label> 21 + <input type="text" name="motd" id="motd" value="@app.get_motd()"> 22 + <input type="submit" value="save"> 23 + </form> 24 + </div> 25 + 12 26 <div> 13 27 <h2>user list:</h2> 14 28 <ul>
+2 -2
src/templates/index.html
··· 2 2 3 3 <h1>@app.config.instance.welcome</h1> 4 4 5 - @if ctx.is_logged_in() 6 - <p>logged in as: @{user.get_name()}</p> 5 + @if motd != '' 6 + <p><em>@motd</em></p> 7 7 @end 8 8 9 9 <div>