module main import vweb import db.sqlite import time import net.http import arrays import maps import math.stats const http_port = 8080 enum Status { besteld = 0 bakken = 1 klaar = 2 afgegeven = 3 } pub fn (s Status) str() string { return match s { .besteld { 'Pannenkoek besteld' } .bakken { 'Pannenkoek aan het bakken' } .klaar { 'Pannenkoek klaar' } .afgegeven { 'Pannenkoek afgegeven' } } } struct Person { id int @[primary; sql: serial] status int name string remark string order_time time.Time delivery_time time.Time } pub fn (p Person) str() string { return 'Person[id:${p.id}, name: ${p.name}, status:${p.status}, time:${p.order_time}, end:${p.delivery_time}]' } pub fn (p Person) order_time_humanized() string { return p.order_time.relative() } pub fn (p Person) remark() string { return if p.remark.len > 0 { '(${p.remark})' } else { '' } } pub fn (p Person) status_str() string { unsafe { s := Status(p.status) return s.str() } } // === Database === pub fn create_db_connection() !sqlite.DB { return sqlite.connect('pancakes.db')! } fn (mut app App) get_people() ![]Person { status_filter := int(Status.afgegeven) people := sql app.db { select from Person where status < status_filter }! return people } fn (mut app App) get_finished_count() !int { people := sql app.db { select from Person where status == 3 }! return people.len } struct PerHour { t time.Time label string amount int percentage int color string } fn (mut app App) get_all() ![]Person { return sql app.db { select from Person order by id desc }! } fn (mut app App) get_last_delivered() ![]Person { return sql app.db { select from Person order by delivery_time desc limit 1 }! } fn (mut app App) get_ordered_per_hour() ![]PerHour { people := sql app.db { select from Person }! grouped := arrays.group_by(people, fn (p Person) string { return '${p.order_time.hour}:${int(p.order_time.minute / 30) * 30} ${p.order_time.day}/${p.order_time.month}' }) max_per_hour := arrays.max(grouped.values().map(it.len)) or { 1 } + 10 mut grouped_arr := maps.to_array(grouped, fn [max_per_hour] (k string, v []Person) PerHour { return PerHour{ t: v[0].order_time label: k amount: v.len percentage: int(v.len * 100 / max_per_hour) color: (if v[0].order_time.hour % 2 == 0 { 'green' } else { 'red' }) } }) grouped_arr.sort(a.t < b.t) return grouped_arr } fn (mut app App) get_finished_per_hour() ![]PerHour { people := sql app.db { select from Person where status == 3 }! grouped := arrays.group_by(people, fn (p Person) string { return '${p.order_time.hour}:${int(p.order_time.minute / 30) * 30} ${p.order_time.day}/${p.order_time.month}' }) max_per_hour := arrays.max(grouped.values().map(it.len)) or { 1 } + 10 mut grouped_arr := maps.to_array(grouped, fn [max_per_hour] (k string, v []Person) PerHour { return PerHour{ t: v[0].order_time label: k amount: v.len percentage: int(v.len * 100 / max_per_hour) color: (if v[0].order_time.hour % 2 == 0 { 'green' } else { 'red' }) } }) grouped_arr.sort(a.t < b.t) return grouped_arr } fn (mut app App) get_ppu() !f64 { mut people := sql app.db { select from Person where status == 3 }! if people.len == 0 { return 0 } people.sort(a.order_time < b.order_time) time_range := people.last().order_time - people.first().order_time return people.len / time_range.hours() } fn (mut app App) get_mean_time_between_pannenkoeken() !time.Duration { time_zero := time.Time{ unix: 0 } mut people := sql app.db { select from Person where (status == 3 && delivery_time > time_zero) order by delivery_time desc limit 10 }! return stats.mean(arrays.window(people, size: 2).map(it[0].delivery_time - it[1].delivery_time)) } fn (mut app App) get_last_done_person() ![]Person { people := sql app.db { select from Person where status == 3 order by delivery_time desc limit 1 }! return people } fn (mut app App) get_next_person() ![]Person { people := sql app.db { select from Person where status < 3 order by id limit 1 }! return people } fn (mut app App) do_status_update(user_id int) !Person { people := sql app.db { select from Person where id == user_id }! person := people.first() sql app.db { update Person set status = person.status + 1 where id == person.id }! if person.status == 2 { sql app.db { update Person set delivery_time = time.now() where id == person.id }! } return person } fn (mut app App) do_add_person(name string, remark string) ! { people := sql app.db { select from Person where name == name && status < 3 }! if people.len == 0 { p := Person{ status: 0 order_time: time.now() name: name remark: remark } sql app.db { insert p into Person }! } } // === WEB === struct App { vweb.Context mut: db sqlite.DB } pub fn (mut app App) before_request() { println('[Vweb] ${app.Context.req.method} ${app.Context.req.url}') } fn main() { println('Start 🥞 webserver') mut db := create_db_connection() or { panic(err) } sql db { create table Person } or { panic('error on create table: ${err}') } // db.close() or { panic(err) } vweb.run(&App{ db: db }, http_port) } @['/'; get] pub fn (mut app App) home() vweb.Result { people := app.get_people() or { app.set_status(400, '') return app.text('${err}') } person_finished_count := app.get_finished_count() or { app.set_status(400, '') return app.text('${err}') } finished_per_hour := app.get_finished_per_hour() or { app.set_status(400, '') return app.text('${err}') } ordered_per_hour := app.get_ordered_per_hour() or { app.set_status(400, '') return app.text('${err}') } // pannenkoek per uur ppu := app.get_ppu() or { app.set_status(400, '') return app.text('${err}') } all_people := app.get_all() or { app.set_status(400, '') return app.text('${err}') } mean_time := app.get_mean_time_between_pannenkoeken() or { app.set_status(400, '') return app.text('${err}') } mut last_delivered := app.get_last_delivered() or { app.set_status(400, '') return app.text('${err}') } if last_delivered.len == 0 { last_delivered << Person{ delivery_time: time.now() } } // Time to pannenkoek time_since_last := (time.now() - last_delivered.first().delivery_time) ttp := time.unix(i64(mean_time.seconds() - time_since_last.seconds())) - time.unix(0) return $vweb.html() } @['/banner'; get] pub fn (mut app App) banner() vweb.Result { last_done := app.get_last_done_person() or { app.set_status(400, '') return app.text('${err}') } next_person := app.get_next_person() or { app.set_status(400, '') return app.text('${err}') } now := time.now() return $vweb.html() } @['/status_update'; post] pub fn (mut app App) status_update() vweb.Result { if person := app.do_status_update(app.form['id'].int()) { if person.status == 1 { spawn fn () { http.post('http://10.1.0.224:8080/blink', '') or {} }() spawn fn (name string) { http.post('http://10.1.2.3', 'ScrollingText >>> ${name} <<< Enjoy! ') or {} http.post('http://10.1.2.3', 'Option text_trailingWhitespace 1') or {} }(person.name) } } return app.redirect('/') } @['/add_person'; post] pub fn (mut app App) add_person() vweb.Result { name := app.form['name'] if name.len == 0 { return app.redirect('/') } app.do_add_person(app.form['name'], app.form['remark']) or { app.set_status(400, '') return app.text('${err}') } return app.redirect('/') }