form_validation/form_validation.rs
1use std::{collections::HashMap, time::Duration};
2
3use serde::{Deserialize, Serialize};
4
5use flyer::{
6 request::{Request, form::Form},
7 response::Response,
8 router::next::Next,
9 server,
10 validation::{Rules}
11};
12use tokio::time::sleep;
13
14/*
15
16TODO: Create file called `register.html` in folder called `views` and copy the content below in the file.
17
18```html
19<!DOCTYPE html>
20<html lang="en">
21<head>
22 <meta charset="UTF-8">
23 <meta name="viewport" content="width=device-width, initial-scale=1.0">
24 <base href="http://127.0.0.1:9999/">
25 <title>Register</title>
26 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
27 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
28 <link rel="preconnect" href="https://fonts.googleapis.com">
29 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
30 <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
31 <style>
32 * {
33 margin: 0;
34 padding: 0;
35 box-sizing: border-box;
36 font-family: 'Poppins', sans-serif;
37 }
38
39 body {
40 background-image: url(https://images.unsplash.com/photo-1542273917363-3b1817f69a2d?q=80&w=1748&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D);
41 background-size: cover;
42 background-position: center;
43 background-repeat: no-repeat;
44 }
45
46 .login-form {
47 background-color: #fff;
48 box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
49 /* border-radius: 20px; */
50 border-top-left-radius: 20px;
51 border-bottom-left-radius: 20px;
52 }
53
54 .image-section {
55 position: relative;
56 background-image: url('https://images.unsplash.com/photo-1465146344425-f00d5f5c8f07?q=80&w=1752&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D');
57 background-size: cover;
58 background-position: center;
59 border-top-right-radius: 20px;
60 border-bottom-right-radius: 20px;
61 }
62
63 .icon {
64 position: absolute;
65 width: 60px;
66 height: 60px;
67 border-radius: 50%;
68 display: flex;
69 justify-content: center;
70 align-items: center;
71 background-color: #fff;
72 box-shadow: 0 4px 15px rgba(0,0,0,0.1);
73 }
74
75 .icon i {
76 font-size: 24px;
77 }
78
79 .slack-icon {
80 top: 40px;
81 right: 60px;
82 background-color: white;
83 }
84
85 .slack-icon i {
86 color: #4A154B;
87 }
88
89 .user-icon {
90 bottom: 200px;
91 left: -30px;
92 background-color: #fecaca;
93 }
94
95 .user-icon i {
96 color: #dc2626;
97 }
98 </style>
99 <base href="http://localhost:9999/">
100</head>
101<body class="d-flex justify-content-center align-items-center vh-100">
102 <div class="container d-flex justify-content-center">
103 <div class="row" style="width: 1000px;">
104 <div class="col-md-6 d-flex flex-column p-5 login-form">
105 <h1>Register</h1>
106 <p class="text-muted">See your growth and get consulting support!</p>
107 <form action="/register" method="post">
108 <div class="mb-3">
109 <label for="email" class="form-label">Email*</label>
110 <input type="email"
111 class="form-control {{ error_has(name="email", class="is-invalid") }}"
112 name="email"
113 value="{{ old(name="email") }}">
114 <div class="invalid-feedback">{{ error(name="email") }}</div>
115 </div>
116 <div class="mb-3">
117 <label for="password" class="form-label">Password*</label>
118 <input type="password"
119 class="form-control {{ error_has(name="password", class="is-invalid") }}"
120 name="password"
121 id="password">
122 <div class="invalid-feedback">{{ error(name="password") }}</div>
123 </div>
124 <div class="mb-3">
125 <label for="password_confirmatiom" class="form-label">Password Confirmation*</label>
126 <input type="password"
127 class="form-control {{ error_has(name="password_confirmatiom", class="is-invalid") }}"
128 name="password_confirmatiom"
129 id="password_confirmatiom">
130 <div class="invalid-feedback">{{ error(name="password_confirmatiom") }}</div>
131 </div>
132 <button type="submit" class="btn btn-primary w-100 py-2">Login</button>
133 </form>
134 <button class="btn mt-4 btn-outline-secondary d-flex align-items-center justify-content-center gap-2 w-100 mb-4">
135 <i class="fab fa-google text-danger"></i>
136 Sign in with Google
137 </button>
138 </div>
139 <div class="col-md-6 image-section">
140 <div class="icon slack-icon">
141 <i class="fab fa-slack"></i>
142 </div>
143 <div class="icon user-icon">
144 <i class="fas fa-user"></i>
145 </div>
146 </div>
147 </div>
148 </div>
149 <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
150</body>
151</html>
152```
153
154*/
155
156#[derive(Serialize, Deserialize)]
157pub struct Token {
158 pub token: String,
159 pub r#type: String,
160 pub expires: u128
161}
162
163pub async fn index<'a>(_req: &'a mut Request, res: &'a mut Response) -> &'a mut Response {
164 return res.view("register.html", None);
165}
166
167pub async fn login<'a>(_req: &'a mut Request, res: &'a mut Response) -> &'a mut Response {
168 return res.json(&Token {
169 token: String::from("eye.jwt.token"),
170 r#type: String::from("jwt"),
171 expires: Duration::from_secs((606 * 60) * 24).as_millis()
172 });
173}
174
175pub async fn user_exists(table: &str, email: &str) -> bool {
176 let mut db: HashMap<&str, Vec<&str>> = HashMap::new();
177
178 db.insert("users", vec!["john@deo.com", "jane@deo.com"]);
179
180 sleep(Duration::from_millis(250)).await;
181
182 return db.get(table).unwrap_or(&Vec::new()).iter().find(|&u| u.eq(&email)).is_some();
183}
184
185pub async fn email_exists(form: &Form, field: String, args: Vec<String>) -> Option<String> {
186 return if user_exists(&args[0], form.values.get(&field).unwrap_or(&String::new())).await {
187 None
188 } else {
189 Some(String::from("The email does not exist"))
190 };
191}
192
193async fn login_form<'a>(req: &'a mut Request, res: &'a mut Response, next: &'a mut Next) -> &'a mut Response {
194 let mut rules = Rules::new();
195
196 rules.rule("email", vec!["required", "string", "email_exists:users"])
197 .rule("password", vec!["required", "string", "min:5", "max:21", "confirmed"]);
198
199 return rules.handle(req, res, next);
200}
201
202fn main() {
203 let server = server("127.0.0.1", 9999)
204 .view("views")
205 .assets("assets", 1024, Duration::from_secs((60 * 60) * 2).as_millis());
206
207 Rules::add("email_exists", email_exists);
208
209 server.router().group("/", |router| {
210 router.get("/", index);
211 router.group("register", |router| {
212 router.post("/", login).middleware(login_form);
213 });
214 });
215
216 print!("\r\n\r\nRunning server: {}\r\n\r\n", server.address());
217
218 server.listen();
219}