v2 / examples / veb / todo / main.v
145 lines · 123 sloc · 3.94 KB · ae1b9ed5711e171ed196208bd484db4b775c407b
Raw
1// Simple TODO app using veb
2// Run from this directory with `v run main.v`
3// You can also enable vebs livereload feature with
4// `v watch -d veb_livereload run main.v`
5module main
6
7import veb
8import db.sqlite
9import os
10import time
11
12struct Todo {
13pub mut:
14 // `id` is the primary field. The attribute `sql: serial` acts like AUTO INCREMENT in sql.
15 // You can use this attribute if you want a unique id for each row.
16 id int @[primary; sql: serial]
17 name string
18 completed bool
19 created time.Time
20 updated time.Time
21}
22
23pub struct Context {
24 veb.Context
25pub mut:
26 // we can use this field to check whether we just created a TODO in our html templates
27 created_todo bool
28}
29
30pub struct App {
31 veb.StaticHandler
32pub:
33 // we can access the SQLITE database directly via `app.db`
34 db sqlite.DB
35}
36
37// This method will only handle GET requests to the index page
38@[get]
39pub fn (app &App) index(mut ctx Context) veb.Result {
40 todos := sql app.db {
41 select from Todo
42 } or { return ctx.server_error('could not fetch todos from database!') }
43 return $veb.html()
44}
45
46// This method will only handle POST requests to the index page
47@['/'; post]
48pub fn (app &App) create_todo(mut ctx Context, name string) veb.Result {
49 // We can receive form input fields as arguments in a route!
50 // we could also access the name field by doing `name := ctx.form['name']`
51
52 // validate input field
53 if name == '' {
54 // set a form error
55 ctx.form_error = 'You must fill in all the fields!'
56 // send a HTTP 400 response code indicating that the form fields are incorrect
57 ctx.res.set_status(.bad_request)
58 // render the home page
59 return app.index(mut ctx)
60 }
61
62 // create a new todo
63 todo := Todo{
64 name: name
65 created: time.now()
66 updated: time.now()
67 }
68
69 // insert the todo into our database
70 sql app.db {
71 insert todo into Todo
72 } or { return ctx.server_error('could not insert a new TODO in the database') }
73
74 ctx.created_todo = true
75
76 // render the home page
77 return app.index(mut ctx)
78}
79
80@['/todo/:id/complete'; post]
81pub fn (app &App) complete_todo(mut ctx Context, id int) veb.Result {
82 // first check if there exist a TODO record with `id`
83 todos := sql app.db {
84 select from Todo where id == id
85 } or { return ctx.server_error("could not fetch TODO's") }
86 if todos.len == 0 {
87 // return HTTP 404 when the TODO does not exist
88 ctx.res.set_status(.not_found)
89 return ctx.text('There is no TODO item with id=${id}')
90 }
91
92 // update the TODO field
93 sql app.db {
94 update Todo set completed = true, updated = time.now() where id == id
95 } or { return ctx.server_error('could not update TODO') }
96
97 // redirect client to the home page and tell the browser to sent a GET request
98 return ctx.redirect('/', typ: .see_other)
99}
100
101@['/todo/:id/delete'; post]
102pub fn (app &App) delete_todo(mut ctx Context, id int) veb.Result {
103 // first check if there exist a TODO record with `id`
104 todos := sql app.db {
105 select from Todo where id == id
106 } or { return ctx.server_error("could not fetch TODO's") }
107 if todos.len == 0 {
108 // return HTTP 404 when the TODO does not exist
109 ctx.res.set_status(.not_found)
110 return ctx.text('There is no TODO item with id=${id}')
111 }
112
113 // prevent hackers from deleting TODO's that are not completed ;)
114 to_be_deleted := todos[0]
115 if !to_be_deleted.completed {
116 return ctx.request_error('You must first complete a TODO before you can delete it!')
117 }
118
119 // delete the todo
120 sql app.db {
121 delete from Todo where id == id
122 } or { return ctx.server_error('could not delete TODO') }
123
124 // redirect client to the home page and tell the browser to sent a GET request
125 return ctx.redirect('/', typ: .see_other)
126}
127
128fn main() {
129 os.chdir(os.dir(@FILE))!
130 // create a new App instance with a connection to the database
131 mut app := &App{
132 db: sqlite.connect('todo.db')!
133 }
134
135 // mount the assets folder at `/assets/`
136 app.handle_static('assets', false)!
137
138 // create the table in our database, if it doesn't exist
139 sql app.db {
140 create table Todo
141 }!
142
143 // start our app at port 8080
144 veb.run[App, Context](mut app, 8080)
145}
146