From 2d7406a8cd7c47559d7f81b6e7f8b6985f5c572e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hitalo=20de=20Jesus=20do=20Ros=C3=A1rio=20Souza?= <63821277+enghitalo@users.noreply.github.com> Date: Sun, 24 Jul 2022 07:02:57 -0300 Subject: [PATCH] examples: v back-end example for vweb (#15141) --- cmd/tools/modules/testing/common.v | 2 + cmd/tools/vbuild-examples.v | 1 + examples/vweb_orm_jwt/.editorconfig | 9 ++ examples/vweb_orm_jwt/.gitattributes | 4 + examples/vweb_orm_jwt/.gitignore | 10 ++ examples/vweb_orm_jwt/src/auth_controllers.v | 19 ++++ examples/vweb_orm_jwt/src/auth_dto.v | 8 ++ examples/vweb_orm_jwt/src/auth_services.v | 86 +++++++++++++++++ .../src/databases/config_datases_mysql.v | 18 ++++ examples/vweb_orm_jwt/src/main.v | 30 ++++++ examples/vweb_orm_jwt/src/user_controllers.v | 72 ++++++++++++++ examples/vweb_orm_jwt/src/user_entities.v | 16 ++++ examples/vweb_orm_jwt/src/user_services.v | 96 +++++++++++++++++++ examples/vweb_orm_jwt/src/v.mod | 7 ++ 14 files changed, 378 insertions(+) create mode 100644 examples/vweb_orm_jwt/.editorconfig create mode 100644 examples/vweb_orm_jwt/.gitattributes create mode 100644 examples/vweb_orm_jwt/.gitignore create mode 100644 examples/vweb_orm_jwt/src/auth_controllers.v create mode 100644 examples/vweb_orm_jwt/src/auth_dto.v create mode 100644 examples/vweb_orm_jwt/src/auth_services.v create mode 100644 examples/vweb_orm_jwt/src/databases/config_datases_mysql.v create mode 100644 examples/vweb_orm_jwt/src/main.v create mode 100644 examples/vweb_orm_jwt/src/user_controllers.v create mode 100644 examples/vweb_orm_jwt/src/user_entities.v create mode 100644 examples/vweb_orm_jwt/src/user_services.v create mode 100644 examples/vweb_orm_jwt/src/v.mod diff --git a/cmd/tools/modules/testing/common.v b/cmd/tools/modules/testing/common.v index ed3dfa8c2..b89e0793e 100644 --- a/cmd/tools/modules/testing/common.v +++ b/cmd/tools/modules/testing/common.v @@ -155,6 +155,7 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession { skip_files << 'examples/database/mysql.v' skip_files << 'examples/database/orm.v' skip_files << 'examples/database/psql/customer.v' + skip_files << 'examples/vweb_orm_jwt' // requires mysql } $if windows { skip_files << 'examples/database/mysql.v' @@ -163,6 +164,7 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession { skip_files << 'examples/websocket/ping.v' // requires OpenSSL skip_files << 'examples/websocket/client-server/client.v' // requires OpenSSL skip_files << 'examples/websocket/client-server/server.v' // requires OpenSSL + skip_files << 'examples/vweb_orm_jwt' // requires mysql $if tinyc { skip_files << 'examples/database/orm.v' // try fix it } diff --git a/cmd/tools/vbuild-examples.v b/cmd/tools/vbuild-examples.v index b11b21f77..8f7bafb1f 100644 --- a/cmd/tools/vbuild-examples.v +++ b/cmd/tools/vbuild-examples.v @@ -7,6 +7,7 @@ const vroot = @VMODROOT const efolders = [ 'examples/viewer', + 'examples/vweb_orm_jwt', ] fn main() { diff --git a/examples/vweb_orm_jwt/.editorconfig b/examples/vweb_orm_jwt/.editorconfig new file mode 100644 index 000000000..517d63e22 --- /dev/null +++ b/examples/vweb_orm_jwt/.editorconfig @@ -0,0 +1,9 @@ +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.v] +indent_style = tab +indent_size = 4 diff --git a/examples/vweb_orm_jwt/.gitattributes b/examples/vweb_orm_jwt/.gitattributes new file mode 100644 index 000000000..97038dfed --- /dev/null +++ b/examples/vweb_orm_jwt/.gitattributes @@ -0,0 +1,4 @@ +*.v linguist-language=V text=auto eol=lf +*.vv linguist-language=V text=auto eol=lf +*.vsh linguist-language=V text=auto eol=lf +**/v.mod linguist-language=V text=auto eol=lf diff --git a/examples/vweb_orm_jwt/.gitignore b/examples/vweb_orm_jwt/.gitignore new file mode 100644 index 000000000..f98f5f46b --- /dev/null +++ b/examples/vweb_orm_jwt/.gitignore @@ -0,0 +1,10 @@ +# Binaries for programs and plugins +main +v +*.exe +*.exe~ +*.so +*.dylib +*.dll +vls.log +.env \ No newline at end of file diff --git a/examples/vweb_orm_jwt/src/auth_controllers.v b/examples/vweb_orm_jwt/src/auth_controllers.v new file mode 100644 index 000000000..91635eb45 --- /dev/null +++ b/examples/vweb_orm_jwt/src/auth_controllers.v @@ -0,0 +1,19 @@ +module main + +import vweb +import json + +['/auth/login'; post] +pub fn (mut app App) controller_auth() vweb.Result { + body := json.decode(AuthRequestDto, app.req.data) or { + app.set_status(400, '') + return app.text('Failed to decode json, error: $err') + } + + response := app.service_auth(body.username, body.password) or { + app.set_status(400, '') + return app.text('error: $err') + } + + return app.json(response) +} diff --git a/examples/vweb_orm_jwt/src/auth_dto.v b/examples/vweb_orm_jwt/src/auth_dto.v new file mode 100644 index 000000000..06fb8b63c --- /dev/null +++ b/examples/vweb_orm_jwt/src/auth_dto.v @@ -0,0 +1,8 @@ +module main + +struct AuthRequestDto { + // Adding a [required] attribute will make decoding fail, if that field is not present in the input. + // If a field is not [required], but is missing, it will be assumed to have its default value, like 0 for numbers, or '' for strings, and decoding will not fail. + username string [required] + password string [required] +} diff --git a/examples/vweb_orm_jwt/src/auth_services.v b/examples/vweb_orm_jwt/src/auth_services.v new file mode 100644 index 000000000..de5e9ee36 --- /dev/null +++ b/examples/vweb_orm_jwt/src/auth_services.v @@ -0,0 +1,86 @@ +module main + +import crypto.hmac +import crypto.sha256 +import crypto.bcrypt +import encoding.base64 +import json +import databases +import time +import os + +struct JwtHeader { + alg string + typ string +} + +struct JwtPayload { + sub string // (subject) = Entidade à quem o token pertence, normalmente o ID do usuário; + iss string // (issuer) = Emissor do token; + exp string // (expiration) = Timestamp de quando o token irá expirar; + iat time.Time // (issued at) = Timestamp de quando o token foi criado; + aud string // (audience) = Destinatário do token, representa a aplicação que irá usá-lo. + name string + roles string + permissions string +} + +fn (mut app App) service_auth(username string, password string) ?string { + mut db := databases.create_db_connection() or { + eprintln(err) + panic(err) + } + + user := sql db { + select from User where username == username limit 1 + } + if user.username != username { + return error('user not found') + } + + if !user.active { + return error('user is not active') + } + + db.close() + + bcrypt.compare_hash_and_password(password.bytes(), user.password.bytes()) or { + return error('Failed to auth user, $err') + } + + token := make_token(user) + + return token +} + +fn make_token(user User) string { + secret := os.getenv('SECRET_KEY') + + jwt_header := JwtHeader{'HS256', 'JWT'} + jwt_payload := JwtPayload{ + sub: '$user.id' + name: '$user.username' + iat: time.now() + } + + header := base64.url_encode(json.encode(jwt_header).bytes()) + payload := base64.url_encode(json.encode(jwt_payload).bytes()) + signature := base64.url_encode(hmac.new(secret.bytes(), '${header}.$payload'.bytes(), + sha256.sum, sha256.block_size).bytestr().bytes()) + + jwt := '${header}.${payload}.$signature' + + return jwt +} + +fn auth_verify(token string) bool { + secret := os.getenv('SECRET_KEY') + token_split := token.split('.') + + signature_mirror := hmac.new(secret.bytes(), '${token_split[0]}.${token_split[1]}'.bytes(), + sha256.sum, sha256.block_size).bytestr().bytes() + + signature_from_token := base64.url_decode(token_split[2]) + + return hmac.equal(signature_from_token, signature_mirror) +} diff --git a/examples/vweb_orm_jwt/src/databases/config_datases_mysql.v b/examples/vweb_orm_jwt/src/databases/config_datases_mysql.v new file mode 100644 index 000000000..1280c6bf1 --- /dev/null +++ b/examples/vweb_orm_jwt/src/databases/config_datases_mysql.v @@ -0,0 +1,18 @@ +module databases + +import mysql +import os + +pub fn create_db_connection() ?mysql.Connection { + mut db := mysql.Connection{ + host: os.getenv('DB_HOST') + port: os.getenv('DB_PORT').u32() + username: os.getenv('DB_USERNAME') + password: os.getenv('DB_PASSWORD') + dbname: os.getenv('DB_NAME') + } + + db.connect() or { println(err) } + + return db +} diff --git a/examples/vweb_orm_jwt/src/main.v b/examples/vweb_orm_jwt/src/main.v new file mode 100644 index 000000000..16dbe0405 --- /dev/null +++ b/examples/vweb_orm_jwt/src/main.v @@ -0,0 +1,30 @@ +module main + +import vweb +import databases + +const ( + http_port = 8081 +) + +struct App { + vweb.Context +} + +fn main() { + mut db := databases.create_db_connection() or { panic(err) } + + sql db { + create table User + } + + db.close() + + vweb.run(new_app(), http_port) +} + +fn new_app() &App { + mut app := &App{} + + return app +} diff --git a/examples/vweb_orm_jwt/src/user_controllers.v b/examples/vweb_orm_jwt/src/user_controllers.v new file mode 100644 index 000000000..ba38cb97e --- /dev/null +++ b/examples/vweb_orm_jwt/src/user_controllers.v @@ -0,0 +1,72 @@ +module main + +import vweb +import json +import databases + +['/user/:id/get'; get] +pub fn (mut app App) controller_get_user_by_id(id int) vweb.Result { + response := app.service_get_user_by_id(id) or { + app.set_status(400, '') + return app.text('$err') + } + return app.json(response) +} + +['/user/create'; post] +pub fn (mut app App) controller_create_user() vweb.Result { + body := json.decode(User, app.req.data) or { + app.set_status(400, '') + return app.text('Failed to decode json, error: $err') + } + + response := app.service_add_user(body.username, body.password) or { + app.set_status(400, '') + return app.text('error: $err') + } + + return app.json(response) +} + +['/user/get_all'; get] +pub fn (mut app App) controller_get_all_user() vweb.Result { + token := app.get_header('token') + + if !auth_verify(token) { + app.set_status(401, '') + return app.text('Not valid token') + } + + response := app.service_get_all_user() or { + app.set_status(400, '') + return app.text('$err') + } + return app.json(response) +} + +['/user/get_by_username/:username'; get] +pub fn (mut app App) controller_get_by_username(username string) vweb.Result { + response := app.service_get_by_username(username) or { + app.set_status(400, '') + return app.text('$err') + } + return app.json(response) +} + +['/user/drop'; delete] +pub fn (mut app App) delete() vweb.Result { + mut db := databases.create_db_connection() or { + app.set_status(400, '') + return app.text('$err') + } + + defer { + db.close() + } + + sql db { + drop table User + } + + return app.text('Tabela deletada com sucesso') +} diff --git a/examples/vweb_orm_jwt/src/user_entities.v b/examples/vweb_orm_jwt/src/user_entities.v new file mode 100644 index 000000000..18bfb9dda --- /dev/null +++ b/examples/vweb_orm_jwt/src/user_entities.v @@ -0,0 +1,16 @@ +module main + +import time + +[table: 'usersxqa'] +struct User { +mut: + id int [primary; sql: serial] + username string [required; sql_type: 'varchar(191)'] + password string [required; sql_type: 'longtext'] + name string [sql_type: 'varchar(191)'] + created_at time.Time [sql_type: 'datetime(3)'] + updated_at time.Time [sql_type: 'datetime(3)'] + deleted_at time.Time [sql_type: 'datetime(3)'] + active bool +} diff --git a/examples/vweb_orm_jwt/src/user_services.v b/examples/vweb_orm_jwt/src/user_services.v new file mode 100644 index 000000000..b71ac56cc --- /dev/null +++ b/examples/vweb_orm_jwt/src/user_services.v @@ -0,0 +1,96 @@ +module main + +import crypto.bcrypt +import databases +import time + +fn (mut app App) service_add_user(username string, password string) ?User { + mut db := databases.create_db_connection() or { + eprintln(err) + return err + } + + defer { + db.close() + } + + hashed_password := bcrypt.generate_from_password(password.bytes(), bcrypt.min_cost) or { + eprintln(err) + return err + } + + user_model := User{ + username: username + name: password + password: hashed_password + created_at: time.now() + updated_at: time.now() + deleted_at: time.now() + active: true + } + + sql db { + insert user_model into User + } + + result := sql db { + select from User where username == username limit 1 + } + + return result +} + +fn (mut app App) service_get_user_by_id(user_id int) ?User { + mut db := databases.create_db_connection() or { + println(err) + return err + } + + defer { + db.close() + } + + results := sql db { + select from User where id == user_id + } + + return results +} + +fn (mut app App) service_get_all_user() ?[]User { + mut db := databases.create_db_connection() or { + println(err) + return err + } + + defer { + db.close() + } + + results := sql db { + select from User + } + + return results +} + +fn (mut app App) service_get_by_username(username string) ?User { + mut db := databases.create_db_connection() or { + println(err) + return err + } + + defer { + db.close() + } + + results := sql db { + select from User where username == username + } + + if results.len == 0 { + return error('Usuário não encontrado') + } + + return results[0] +} diff --git a/examples/vweb_orm_jwt/src/v.mod b/examples/vweb_orm_jwt/src/v.mod new file mode 100644 index 000000000..7aab9607c --- /dev/null +++ b/examples/vweb_orm_jwt/src/v.mod @@ -0,0 +1,7 @@ +Module { + name: 'vweb_orm_jwt' + description: '' + version: '' + license: '' + dependencies: [] +} -- 2.30.2