v/examples/xvweb/todo/main.v

146 lines
4.0 KiB
V

// Simple TODO app using x.vweb
// Run from this directory with `v run main.v`
// You can also enable vwebs livereload feature with
// `v watch -d vweb_livereload run main.v`
module main
import x.vweb
import db.sqlite
import os
import time
struct Todo {
pub mut:
// `id` is the primary field. The attribute `sql: serial` acts like AUTO INCREMENT in sql.
// You can use this attribute if you want a unique id for each row.
id int @[primary; sql: serial]
name string
completed bool
created time.Time
updated time.Time
}
pub struct Context {
vweb.Context
pub mut:
// we can use this field to check whether we just created a TODO in our html templates
created_todo bool
}
pub struct App {
vweb.StaticHandler
pub:
// we can access the SQLITE database directly via `app.db`
db sqlite.DB
}
// This method will only handle GET requests to the index page
@[get]
pub fn (app &App) index(mut ctx Context) vweb.Result {
todos := sql app.db {
select from Todo
} or { return ctx.server_error('could not fetch todos from database!') }
return $vweb.html()
}
// This method will only handle POST requests to the index page
@['/'; post]
pub fn (app &App) create_todo(mut ctx Context, name string) vweb.Result {
// We can receive form input fields as arguments in a route!
// we could also access the name field by doing `name := ctx.form['name']`
// validate input field
if name == '' {
// set a form error
ctx.form_error = 'You must fill in all the fields!'
// send a HTTP 400 response code indicating that the form fields are incorrect
ctx.res.set_status(.bad_request)
// render the home page
return app.index(mut ctx)
}
// create a new todo
todo := Todo{
name: name
created: time.now()
updated: time.now()
}
// insert the todo into our database
sql app.db {
insert todo into Todo
} or { return ctx.server_error('could not insert a new TODO in the database') }
ctx.created_todo = true
// render the home page
return app.index(mut ctx)
}
@['/todo/:id/complete'; post]
pub fn (app &App) complete_todo(mut ctx Context, id int) vweb.Result {
// first check if there exist a TODO record with `id`
todos := sql app.db {
select from Todo where id == id
} or { return ctx.server_error("could not fetch TODO's") }
if todos.len == 0 {
// return HTTP 404 when the TODO does not exist
ctx.res.set_status(.not_found)
return ctx.text('There is no TODO item with id=${id}')
}
// update the TODO field
sql app.db {
update Todo set completed = true, updated = time.now() where id == id
} or { return ctx.server_error('could not update TODO') }
// redirect client to the home page and tell the browser to sent a GET request
return ctx.redirect('/', typ: .see_other)
}
@['/todo/:id/delete'; post]
pub fn (app &App) delete_todo(mut ctx Context, id int) vweb.Result {
// first check if there exist a TODO record with `id`
todos := sql app.db {
select from Todo where id == id
} or { return ctx.server_error("could not fetch TODO's") }
if todos.len == 0 {
// return HTTP 404 when the TODO does not exist
ctx.res.set_status(.not_found)
return ctx.text('There is no TODO item with id=${id}')
}
// prevent hackers from deleting TODO's that are not completed ;)
to_be_deleted := todos[0]
if !to_be_deleted.completed {
return ctx.request_error('You must first complete a TODO before you can delete it!')
}
// delete the todo
sql app.db {
delete from Todo where id == id
} or { return ctx.server_error('could not delete TODO') }
// redirect client to the home page and tell the browser to sent a GET request
return ctx.redirect('/', typ: .see_other)
}
fn main() {
os.chdir(os.dir(@FILE))!
// create a new App instance with a connection to the database
mut app := &App{
db: sqlite.connect('todo.db')!
}
// mount the assets folder at `/assets/`
app.handle_static('assets', false)!
// create the table in our database, if it doesn't exist
sql app.db {
create table Todo
}!
// start our app at port 8080
vweb.run[App, Context](mut app, 8080)
}