Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

@@ -0,0 +1,8 @@
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.v]
indent_style = tab
@@ -0,0 +1,7 @@
* text=auto eol=lf
*.bat eol=crlf
**/*.v linguist-language=V
**/*.vv linguist-language=V
**/*.vsh linguist-language=V
**/v.mod linguist-language=V
+18
View File
@@ -0,0 +1,18 @@
# Binaries for programs and plugins
main
batata
*.exe
*.exe~
*.so
*.dylib
*.dll
*.sql
# Ignore binary output folders
bin/
# Ignore common editor/system specific metadata
.DS_Store
.idea/
.vscode/
*.iml
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Hitalo Souza
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,4 @@
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill="#536b8a" d="M 323.509,471.167 480.202,32.185 C 482.857,24.74 478.671,19.312 470.853,20.071 L 347.629,32.03 c -7.816,0.759 -16.259,7.44 -18.841,14.911 L 183.753,466.465 c -2.583,7.47 1.693,13.535 9.546,13.535 h 119.938 c 3.925,0 8.192,-3.02 9.52,-6.744 l 0.746,-2.089 v 0 0 z"/>
<path fill="#5d87be" d="M 30.055,20.07 152.165,32.029 c 7.745,0.76 16.122,7.438 18.692,14.905 l 146.766,426.302 c 1.284,3.735 -0.83,6.764 -4.721,6.764 H 193.294 c -7.781,0 -16.239,-6.044 -18.871,-13.489 L 20.791,32.185 c -2.633,-7.445 1.518,-12.874 9.264,-12.114 v 0 0 z"/>
</svg>

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

@@ -0,0 +1,13 @@
module main
import veb
@['/controller/auth'; post]
pub fn (mut app App) controller_auth(mut ctx Context, username string, password string) veb.Result {
response := app.service_auth(username, password) or {
ctx.res.set_status(.bad_request)
return ctx.text('error: ${err}')
}
return ctx.json(response)
}
@@ -0,0 +1,6 @@
module main
struct AuthRequestDto {
username string @[nonull]
password string @[nonull]
}
@@ -0,0 +1,93 @@
module main
import crypto.hmac
import crypto.sha256
import crypto.bcrypt
import encoding.base64
import json
import databases
import time
struct JwtHeader {
alg string
typ string
}
struct JwtPayload {
sub string // (subject) = Entity to whom the token belongs, usually the user ID;
iss string // (issuer) = Token issuer;
exp string // (expiration) = Timestamp of when the token will expire;
iat time.Time // (issued at) = Timestamp of when the token was created;
aud string // (audience) = Token recipient, represents the application that will use it.
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)
}
defer {
db.close() or { panic(err) }
}
users := sql db {
select from User where username == username
}!
if users.len == 0 {
return error('user not found')
}
user := users.first()
if user.username != username {
return error('user not found')
}
if !user.active {
return error('user is not active')
}
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 := 'SECRET_KEY' // 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 {
if token == '' {
return false
}
secret := 'SECRET_KEY' // 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)
}
@@ -0,0 +1,8 @@
module databases
import db.sqlite // can change to 'db.mysql', 'db.pg'
pub fn create_db_connection() !sqlite.DB {
mut db := sqlite.connect('app.db')!
return db
}
+71
View File
@@ -0,0 +1,71 @@
<!DOCTYPE html>
<head>
<!--Let browser know website is optimized for mobile-->
<meta charset="UTF-8" name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Compiled and minified CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<!-- Compiled and minified JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<!-- Material UI icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<title>${title}</title>
</head>
<body>
<div>@include 'templates/header_component.html'</div>
<div class="card-panel center-align" style="max-width: 240px; padding: 10px; margin: 10px; border-radius: 5px;">
<form id="index_form" method='post' action=''>
<div style="display:flex; flex-direction: column;">
<input type='text' name='username' placeholder='Username' required autofocus>
<input type='password' name='password' placeholder='Password' required>
</div>
<div style="margin-top: 10px;">
<input class="waves-effect waves-light btn-small" type='submit' onclick="login()" formaction="javascript:void(0);" value='Login'>
<input class="waves-effect waves-light btn-small" type='submit' onclick="addUser()" formaction="javascript:void(0);" value='Register'>
</div>
</form>
<script type="text/javascript">
async function addUser() {
const form = document.querySelector('#index_form');
const formData = new FormData(form);
await fetch('/controller/user/create', {
method: 'POST',
body: formData
})
.then( async (response) => {
if (response.status != 201) {
throw await response.text()
}
return await response.text()
})
.then((data) => {
alert("User created successfully")
})
.catch((error) => {
alert(error);
});
}
async function login() {
const form = document.querySelector('#index_form');
const formData = new FormData(form);
await fetch('/controller/auth', {
method: 'POST',
body: formData
})
.then( async (response) => {
if (response.status != 200) {
throw await response.text()
}
return response.json()
})
.then((data) => {
document.cookie = 'token='+data+';';
window.location.href = '/products'
})
.catch((error) => {
alert(error);
});
}
</script>
</div>
</body>
</html>
+42
View File
@@ -0,0 +1,42 @@
module main
import veb
import databases
import os
const port = 8082
pub struct Context {
veb.Context
}
struct App {
veb.StaticHandler
}
pub fn (app &App) before_request(mut ctx Context) {
println('[veb] before_request: ${ctx.req.method} ${ctx.req.url}')
}
fn main() {
mut db := databases.create_db_connection() or { panic(err) }
sql db {
create table User
create table Product
} or { panic('error on create table: ${err}') }
db.close() or { panic(err) }
mut app := &App{}
app.serve_static('/favicon.ico', 'assets/favicon.ico') or { panic(err) }
app.mount_static_folder_at(os.resource_abs_path('.'), '/') or { panic(err) }
veb.run[App, Context](mut app, port)
}
pub fn (mut app App) index(mut ctx Context) veb.Result {
title := 'veb app'
return $veb.html()
}
@@ -0,0 +1,61 @@
module main
import veb
import encoding.base64
import json
@['/controller/products'; get]
pub fn (mut app App) controller_get_all_products(mut ctx Context) veb.Result {
token := ctx.req.header.get_custom('token') or { '' }
if !auth_verify(token) {
ctx.res.set_status(.unauthorized)
return ctx.text('Not valid token')
}
jwt_payload_stringify := base64.url_decode_str(token.split('.')[1])
jwt_payload := json.decode(JwtPayload, jwt_payload_stringify) or {
ctx.res.set_status(.internal_server_error)
return ctx.text('jwt decode error')
}
user_id := jwt_payload.sub
response := app.service_get_all_products_from(user_id.int()) or {
ctx.res.set_status(.bad_request)
return ctx.text('${err}')
}
return ctx.json(response)
}
@['/controller/product/create'; post]
pub fn (mut app App) controller_create_product(mut ctx Context, product_name string) veb.Result {
if product_name == '' {
ctx.res.set_status(.bad_request)
return ctx.text('product name cannot be empty')
}
token := ctx.req.header.get_custom('token') or { '' }
if !auth_verify(token) {
ctx.res.set_status(.unauthorized)
return ctx.text('Not valid token')
}
jwt_payload_stringify := base64.url_decode_str(token.split('.')[1])
jwt_payload := json.decode(JwtPayload, jwt_payload_stringify) or {
ctx.res.set_status(.internal_server_error)
return ctx.text('jwt decode error')
}
user_id := jwt_payload.sub
app.service_add_product(product_name, user_id.int()) or {
ctx.res.set_status(.bad_request)
return ctx.text('error: ${err}')
}
ctx.res.set_status(.created)
return ctx.text('product created successfully')
}
@@ -0,0 +1,9 @@
module main
@[table: 'products']
struct Product {
id int @[primary; sql: serial]
user_id int
name string @[sql_type: 'TEXT']
created_at string @[default: 'CURRENT_TIMESTAMP']
}
@@ -0,0 +1,43 @@
module main
import databases
fn (mut app App) service_add_product(product_name string, user_id int) ! {
mut db := databases.create_db_connection()!
defer {
db.close() or { panic(err) }
}
product_model := Product{
name: product_name
user_id: user_id
}
mut insert_error := ''
sql db {
insert product_model into Product
} or { insert_error = err.msg() }
if insert_error != '' {
return error(insert_error)
}
}
fn (mut app App) service_get_all_products_from(user_id int) ![]Product {
mut db := databases.create_db_connection() or {
println(err)
return err
}
defer {
db.close() or { panic(err) }
}
results := sql db {
select from Product where user_id == user_id
}!
return results
}
@@ -0,0 +1,18 @@
module main
import veb
@['/products'; get]
pub fn (mut app App) products(mut ctx Context) !veb.Result {
token := ctx.get_cookie('token') or {
ctx.res.set_status(.bad_request)
return ctx.text('${err}')
}
user := get_user(token) or {
ctx.res.set_status(.bad_request)
return ctx.text('Failed to fetch data from the server. Error: ${err}')
}
return $veb.html()
}
@@ -0,0 +1,35 @@
module main
import json
import net.http
pub fn get_products(token string) ![]Product {
mut header := http.new_header()
header.add_custom('token', token)!
url := 'http://localhost:8082/controller/products'
mut config := http.FetchConfig{
header: header
}
resp := http.fetch(http.FetchConfig{ ...config, url: url })!
products := json.decode([]Product, resp.body)!
return products
}
pub fn get_user(token string) !User {
mut header := http.new_header()
header.add_custom('token', token)!
url := 'http://localhost:8082/controller/user'
mut config := http.FetchConfig{
header: header
}
resp := http.fetch(http.FetchConfig{ ...config, url: url })!
user := json.decode(User, resp.body)!
return user
}
+3
View File
@@ -0,0 +1,3 @@
![image](https://user-images.githubusercontent.com/63821277/230925310-6b8aecfd-ad8d-4967-931b-9656e315f3dd.png)
![image](https://user-images.githubusercontent.com/63821277/230925505-567e64af-343e-4385-9516-9cbf14a18f98.png)
@@ -0,0 +1,15 @@
<nav>
<div class="nav-wrapper">
<a href="javascript:window.history.back();" class="left">
<i class="material-icons">arrow_back_ios_new</i>
</a>
<a href="/">
<img src="src/assets/veasel.png" alt="logo" style="max-height: 100%" />
</a>
<ul id="nav-mobile" class="right">
<li><a href="https://github.com/vlang/v">github</a></li>
<li><a href="https://vlang.io/">website</a></li>
<li><a href="https://github.com/sponsors/medvednikov">support</a></li>
</ul>
</div>
</nav>
@@ -0,0 +1,11 @@
h1.title {
font-family: Arial, Helvetica, sans-serif;
color: #3b7bbf;
}
div.products-table {
border: 1px solid;
max-width: 720px;
padding: 10px;
margin: 10px;
}
@@ -0,0 +1,88 @@
<!DOCTYPE html>
<head>
<!--Let browser know website is optimized for mobile-->
<meta charset="UTF-8" name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Compiled and minified CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<!-- Compiled and minified JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<!-- Material UI icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<title>Login</title>
@css 'src/templates/products.css'
</head>
<body>
<div>@include 'header_component.html'</div>
<h1 class="title">Hi, ${user.username}! you are online</h1>
<form id="product_form" method='post' action=''>
<div class="row">
<div class="input-field col s2">
<input id="product_name" name='product_name' type="text" class="validate">
<label class="active" for="product_name">product name</label>
</div>
<div style="margin-top: 10px;">
<input class="waves-effect waves-light btn-small" type='submit' onclick="addProduct()" formaction="javascript:void(0);" value='Register' required autofocus>
</div>
</div>
</form>
<script type="text/javascript">
function getCookie(cookieName) {
let cookie = {};
document.cookie.split(';').forEach(function(el) {
let [key,value] = el.split('=');
cookie[key.trim()] = value;
})
return cookie[cookieName];
}
async function addProduct() {
const form = document.querySelector('#product_form');
const formData = new FormData(form);
console.log(getCookie("token"));
await fetch('/controller/product/create', {
method: 'POST',
body: formData,
headers :{
token: getCookie("token")
}
})
.then( async (response) => {
if (response.status != 201) {
throw await response.text()
}
return await response.text()
})
.then((data) => {
document.location.reload(true)
})
.catch((error) => {
alert(error);
});
}
</script>
<div class="products-table card-panel">
<table class="highlight striped responsive-table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Created date</th>
</tr>
</thead>
<tbody>
@for product in user.products
<tr>
<td>${product.id}</td>
<td>${product.name}</td>
<td>${product.created_at}</td>
</tr>
@end
</tbody>
</table>
</div>
</body>
</html>
@@ -0,0 +1,64 @@
module main
import veb
import encoding.base64
import json
@['/controller/users'; get]
pub fn (mut app App) controller_get_all_user(mut ctx Context) veb.Result {
token := ctx.req.header.get_custom('token') or { '' }
if !auth_verify(token) {
ctx.res.set_status(.unauthorized)
return ctx.text('Not valid token')
}
response := app.service_get_all_user() or {
ctx.res.set_status(.bad_request)
return ctx.text('${err}')
}
return ctx.json(response)
}
@['/controller/user'; get]
pub fn (mut app App) controller_get_user(mut ctx Context) veb.Result {
token := ctx.req.header.get_custom('token') or { '' }
if !auth_verify(token) {
ctx.res.set_status(.unauthorized)
return ctx.text('Not valid token')
}
jwt_payload_stringify := base64.url_decode_str(token.split('.')[1])
jwt_payload := json.decode(JwtPayload, jwt_payload_stringify) or {
ctx.res.set_status(.internal_server_error)
return ctx.text('jwt decode error')
}
user_id := jwt_payload.sub
response := app.service_get_user(user_id.int()) or {
ctx.res.set_status(.bad_request)
return ctx.text('${err}')
}
return ctx.json(response)
}
@['/controller/user/create'; post]
pub fn (mut app App) controller_create_user(mut ctx Context, username string, password string) veb.Result {
if username == '' {
ctx.res.set_status(.bad_request)
return ctx.text('username cannot be empty')
}
if password == '' {
ctx.res.set_status(.bad_request)
return ctx.text('password cannot be empty')
}
app.service_add_user(username, password) or {
ctx.res.set_status(.bad_request)
return ctx.text('error: ${err}')
}
ctx.res.set_status(.created)
return ctx.text('User created successfully')
}
@@ -0,0 +1,11 @@
module main
@[table: 'users']
pub struct User {
mut:
id int @[primary; sql: serial]
username string @[sql_type: 'TEXT'; unique]
password string @[sql_type: 'TEXT']
active bool
products []Product @[fkey: 'user_id']
}
@@ -0,0 +1,65 @@
module main
import crypto.bcrypt
import databases
fn (mut app App) service_add_user(username string, password string) ! {
mut db := databases.create_db_connection()!
defer {
db.close() or { panic(err) }
}
hashed_password := bcrypt.generate_from_password(password.bytes(), bcrypt.min_cost) or {
eprintln(err)
return err
}
user_model := User{
username: username
password: hashed_password
active: true
}
mut insert_error := ''
sql db {
insert user_model into User
} or { insert_error = err.msg() }
if insert_error != '' {
return error(insert_error)
}
}
fn (mut app App) service_get_all_user() ![]User {
mut db := databases.create_db_connection() or {
println(err)
return err
}
defer {
db.close() or { panic(err) }
}
results := sql db {
select from User
}!
return results
}
fn (mut app App) service_get_user(id int) !User {
mut db := databases.create_db_connection() or {
println(err)
return err
}
defer {
db.close() or { panic(err) }
}
results := sql db {
select from User where id == id
}!
return results.first()
}
+7
View File
@@ -0,0 +1,7 @@
Module {
name: 'veb_fullstack'
description: ''
version: ''
license: ''
dependencies: []
}