diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 0e09df2..1c6405a 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -282,16 +282,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] -name = "argon2" -version = "0.5.3" +name = "anyhow" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" -dependencies = [ - "base64ct", - "blake2", - "cpufeatures", - "password-hash", -] +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "autocfg" @@ -316,9 +310,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.7" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" @@ -327,10 +321,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "base64ct" -version = "1.6.0" +name = "bcrypt" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "a7e7c93a3fb23b2fdde989b2c9ec4dd153063ec81f408507f84c090cd91c6641" +dependencies = [ + "base64 0.13.1", + "blowfish", + "getrandom", + "zeroize", +] [[package]] name = "bitflags" @@ -356,6 +356,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "brotli" version = "6.0.0" @@ -421,6 +431,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -582,10 +602,10 @@ dependencies = [ "actix-cors", "actix-files", "actix-web", - "argon2", + "anyhow", + "bcrypt", "env_logger", "futures", - "jsonwebtoken", "log", "rand_core", "rusqlite", @@ -952,6 +972,15 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "is-terminal" version = "0.4.13" @@ -1961,6 +1990,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zerovec" version = "0.10.4" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index f2cd441..4796d48 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -11,9 +11,8 @@ serde_json = "1.0" uuid = { version = "1.0", features = ["v4"] } actix-files = "0.6" actix-cors = "0.6" -env_logger = "0.10" # Check for the latest version +env_logger = "0.10" log = "0.4" -jsonwebtoken = "9" -argon2 = { version = "0.5", features = ["password-hash"] } futures = "0.3" -rand_core = "0.6.4" \ No newline at end of file +bcrypt = "0.13" +anyhow = "1.0" \ No newline at end of file diff --git a/backend/form_data.db b/backend/form_data.db index 663f658..a6446aa 100644 Binary files a/backend/form_data.db and b/backend/form_data.db differ diff --git a/backend/src/auth.rs b/backend/src/auth.rs new file mode 100644 index 0000000..ca2f9d7 --- /dev/null +++ b/backend/src/auth.rs @@ -0,0 +1,36 @@ +use actix_web::{dev::Payload, http::header::AUTHORIZATION, web, Error, FromRequest, HttpRequest}; +use futures::future::{ready, Ready}; +use rusqlite::Connection; +use std::sync::{Arc, Mutex}; + +pub struct Auth { + pub user_id: String, +} + +impl FromRequest for Auth { + type Error = Error; + type Future = Ready>; + + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + let db = req + .app_data::>>>() + .expect("Database connection missing"); + + if let Some(auth_header) = req.headers().get(AUTHORIZATION) { + if let Ok(auth_str) = auth_header.to_str() { + if auth_str.starts_with("Bearer ") { + let token = &auth_str[7..]; + let conn = db.lock().unwrap(); + + match super::db::validate_token(&conn, token) { + Ok(Some(user_id)) => return ready(Ok(Auth { user_id })), + Ok(None) | Err(_) => { + return ready(Err(actix_web::error::ErrorUnauthorized("Invalid token"))) + } + } + } + } + } + ready(Err(actix_web::error::ErrorUnauthorized("Missing token"))) + } +} diff --git a/backend/src/db.rs b/backend/src/db.rs index 6537bd4..89080b4 100644 --- a/backend/src/db.rs +++ b/backend/src/db.rs @@ -1,9 +1,10 @@ -use argon2::{password_hash::SaltString, Algorithm, Argon2, Params, PasswordHasher, Version}; -use rand_core::OsRng; -use rusqlite::{Connection, Result}; +use anyhow::{Context, Result as AnyhowResult}; +use bcrypt::{hash, verify, DEFAULT_COST}; // Add bcrypt dependency for password hashing +use rusqlite::{params, Connection, OptionalExtension}; +use uuid::Uuid; // UUID for generating unique IDs // Import anyhow -pub fn init_db() -> Result { - let conn = Connection::open("form_data.db")?; +pub fn init_db() -> AnyhowResult { + let conn = Connection::open("form_data.db").context("Failed to open the database")?; // Create tables conn.execute( @@ -27,37 +28,96 @@ pub fn init_db() -> Result { [], )?; + // Add a table for users conn.execute( - "CREATE TABLE IF NOT EXISTS admin_users ( - username TEXT PRIMARY KEY, - password_hash TEXT NOT NULL + "CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + username TEXT NOT NULL UNIQUE, + password TEXT NOT NULL, -- Store a hashed password + token TEXT UNIQUE -- Optional: For token-based auth )", [], )?; - // Check if the admin_users table is empty - let count: i64 = conn - .query_row("SELECT COUNT(*) FROM admin_users", [], |row| row.get(0)) - .unwrap_or(0); - - if count == 0 { - // Create a default admin user - let default_username = "admin"; - let default_password = "admin123"; // This should be replaced with a secure method for real applications - - // Hash the default password - let salt = SaltString::generate(&mut OsRng); - let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, Params::default()); - let password_hash = argon2 - .hash_password(default_password.as_bytes(), &salt) - .unwrap() - .to_string(); - - conn.execute( - "INSERT INTO admin_users (username, password_hash) VALUES (?1, ?2)", - &[default_username, password_hash.as_str()], - )?; - } + // Setup initial admin after creating the tables + setup_initial_admin(&conn)?; Ok(conn) } + +pub fn setup_initial_admin(conn: &Connection) -> AnyhowResult<()> { + add_admin_user(conn)?; + Ok(()) +} + +pub fn add_admin_user(conn: &Connection) -> AnyhowResult<()> { + // Check if admin user already exists + let mut stmt = conn + .prepare("SELECT id FROM users WHERE username = ?1") + .context("Failed to prepare query for checking admin user")?; + if stmt.exists(params!["admin"])? { + return Ok(()); + } + + // Generate a UUID for the admin user + let admin_id = Uuid::new_v4().to_string(); + + // Hash the password before storing it + let hashed_password = hash("admin", DEFAULT_COST).context("Failed to hash password")?; + + // Add admin user with hashed password + conn.execute( + "INSERT INTO users (id, username, password) VALUES (?1, ?2, ?3)", + params![admin_id, "admin", hashed_password], + ) + .context("Failed to insert admin user into the database")?; + + Ok(()) +} + +// Add a function to validate a token +pub fn validate_token(conn: &Connection, token: &str) -> AnyhowResult> { + let mut stmt = conn + .prepare("SELECT id FROM users WHERE token = ?1") + .context("Failed to prepare query for validating token")?; + let user_id: Option = stmt + .query_row(params![token], |row| row.get(0)) + .optional() + .context("Failed to retrieve user ID for the given token")?; + Ok(user_id) +} + +// Add a function to authenticate users (for login) +pub fn authenticate_user( + conn: &Connection, + username: &str, + password: &str, +) -> AnyhowResult> { + let mut stmt = conn + .prepare("SELECT id, password FROM users WHERE username = ?1") + .context("Failed to prepare query for authenticating user")?; + let mut rows = stmt + .query(params![username]) + .context("Failed to execute query for authenticating user")?; + + if let Some(row) = rows.next()? { + let user_id: String = row.get(0)?; + let stored_password: String = row.get(1)?; + + // Use bcrypt to verify the hashed password + if verify(password, &stored_password).context("Failed to verify password")? { + return Ok(Some(user_id)); + } + } + Ok(None) +} + +// Add a function to generate and save a token for a user +pub fn generate_token_for_user(conn: &Connection, user_id: &str, token: &str) -> AnyhowResult<()> { + conn.execute( + "UPDATE users SET token = ?1 WHERE id = ?2", + params![token, user_id], + ) + .context("Failed to update token for user")?; + Ok(()) +} diff --git a/backend/src/handlers.rs b/backend/src/handlers.rs index f6c7914..3c9bd3e 100644 --- a/backend/src/handlers.rs +++ b/backend/src/handlers.rs @@ -6,20 +6,74 @@ use argon2::{ }; use jsonwebtoken::{encode, EncodingKey, Header}; use rusqlite::{params, Connection}; -use serde_json::json; -use std::{ - sync::{Arc, Mutex}, - time::{SystemTime, UNIX_EPOCH}, -}; +use serde::{Deserialize, Serialize}; +use std::sync::{Arc, Mutex}; use uuid::Uuid; -// Create a new form +use crate::auth::Auth; + +// Structs for requests and responses +#[derive(Deserialize)] +pub struct LoginRequest { + pub username: String, + pub password: String, +} + +#[derive(Serialize)] +pub struct LoginResponse { + pub token: String, +} + +// Public: Login handler +pub async fn login( + db: web::Data>>, + login_request: web::Json, +) -> impl Responder { + let conn = db.lock().unwrap(); + let user_id = + match crate::db::authenticate_user(&conn, &login_request.username, &login_request.password) + { + Ok(Some(user_id)) => user_id, + Ok(None) => return HttpResponse::Unauthorized().body("Invalid username or password"), + Err(_) => return HttpResponse::InternalServerError().body("Database error"), + }; + + let token = Uuid::new_v4().to_string(); + if let Err(_) = crate::db::generate_token_for_user(&conn, &user_id, &token) { + return HttpResponse::InternalServerError().body("Failed to generate token"); + } + + HttpResponse::Ok().json(LoginResponse { token }) +} + +// Public: Submit a form +pub async fn submit_form( + db: web::Data>>, + path: web::Path, + submission: web::Form, +) -> impl Responder { + let conn = db.lock().unwrap(); + let submission_id = Uuid::new_v4().to_string(); + let form_id = path.into_inner(); + let submission_json = serde_json::to_string(&submission.into_inner()).unwrap(); + + match conn.execute( + "INSERT INTO submissions (id, form_id, data) VALUES (?1, ?2, ?3)", + params![submission_id, form_id, submission_json], + ) { + Ok(_) => HttpResponse::Ok().json(submission_id), + Err(e) => HttpResponse::InternalServerError().body(format!("Error: {}", e)), + } +} + +// Protected: Create a new form pub async fn create_form( db: web::Data>>, - form: web::Json
, + auth: Auth, + form: web::Json, ) -> impl Responder { - println!("Received form: {:?}", form); - let conn = db.lock().unwrap(); // Lock the Mutex to access the database + println!("Authenticated user: {}", auth.user_id); + let conn = db.lock().unwrap(); let form_id = Uuid::new_v4().to_string(); let form_json = serde_json::to_string(&form.fields).unwrap(); @@ -32,9 +86,10 @@ pub async fn create_form( } } -// Get all forms -pub async fn get_forms(db: web::Data>>) -> impl Responder { - let conn = db.lock().unwrap(); // Lock the Mutex to access the database +// Protected: Get all forms +pub async fn get_forms(db: web::Data>>, auth: Auth) -> impl Responder { + println!("Authenticated user: {}", auth.user_id); + let conn = db.lock().unwrap(); let mut stmt = match conn.prepare("SELECT id, name, fields FROM forms") { Ok(stmt) => stmt, @@ -47,41 +102,22 @@ pub async fn get_forms(db: web::Data>>) -> impl Responder let name: String = row.get(1)?; let fields: String = row.get(2)?; let fields = serde_json::from_str(&fields).unwrap(); - Ok(Form { id, name, fields }) + Ok(crate::models::Form { id, name, fields }) }) .unwrap(); - let forms: Vec = forms_iter.filter_map(|f| f.ok()).collect(); + let forms: Vec = forms_iter.filter_map(|f| f.ok()).collect(); HttpResponse::Ok().json(forms) } -// Submit a form -pub async fn submit_form( - db: web::Data>>, - path: web::Path, - submission: web::Form, -) -> impl Responder { - let conn = db.lock().unwrap(); // Lock the Mutex to access the database - let submission_id = Uuid::new_v4().to_string(); - let form_id = path.into_inner(); - - let submission_json = serde_json::to_string(&submission.into_inner()).unwrap(); - - match conn.execute( - "INSERT INTO submissions (id, form_id, data) VALUES (?1, ?2, ?3)", - params![submission_id, form_id, submission_json], - ) { - Ok(_) => HttpResponse::Ok().json(submission_id), - Err(e) => HttpResponse::InternalServerError().body(format!("Error: {}", e)), - } -} - -// Get submissions for a form +// Protected: Get submissions for a form pub async fn get_submissions( db: web::Data>>, + auth: Auth, path: web::Path, ) -> impl Responder { - let conn = db.lock().unwrap(); // Lock the Mutex to access the database + println!("Authenticated user: {}", auth.user_id); + let conn = db.lock().unwrap(); let form_id = path.into_inner(); let mut stmt = @@ -96,11 +132,12 @@ pub async fn get_submissions( let form_id: String = row.get(1)?; let data: String = row.get(2)?; let data = serde_json::from_str(&data).unwrap(); - Ok(Submission { id, form_id, data }) + Ok(crate::models::Submission { id, form_id, data }) }) .unwrap(); - let submissions: Vec = submissions_iter.filter_map(|s| s.ok()).collect(); + let submissions: Vec = + submissions_iter.filter_map(|s| s.ok()).collect(); HttpResponse::Ok().json(submissions) } diff --git a/backend/src/main.rs b/backend/src/main.rs index ac174e2..1a18a38 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -2,6 +2,7 @@ use actix_cors::Cors; use actix_web::{web, App, HttpServer}; use std::sync::{Arc, Mutex}; +mod auth; mod db; mod handlers; // Ensure handlers.rs exists mod middleware; // Ensure middleware.rs exists // Ensure db.rs exists @@ -43,7 +44,17 @@ async fn main() -> std::io::Result<()> { .allow_any_method(), ) .app_data(web::Data::new(db.clone())) - .configure(configure_routes) + .route("/login", web::post().to(handlers::login)) // Public: Login + .route( + "/forms/{id}/submissions", + web::post().to(handlers::submit_form), // Public: Submit form + ) + .route("/forms", web::post().to(handlers::create_form)) // Protected + .route("/forms", web::get().to(handlers::get_forms)) // Protected + .route( + "/forms/{id}/submissions", + web::get().to(handlers::get_submissions), // Protected + ) }) .bind("127.0.0.1:8080")? .run() diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 9e7dbf3..d2e2930 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -1,7 +1,35 @@ -import type { Form, Submission, LoginCredentials } from './types'; - const API_BASE_URL = 'http://127.0.0.1:8080'; +// A simple function to retrieve the token from local storage or wherever it is stored +function getAuthToken(): string | null { + return localStorage.getItem('auth_token'); // Assuming the token is stored in localStorage +} + +// A simple function to save the token +function setAuthToken(token: string): void { + localStorage.setItem('auth_token', token); +} + +// A simple function to save the token +function delAuthToken(): void { + localStorage.removeItem('auth_token'); +} + +// A simple function to retrieve the token from local storage or wherever it is stored +function getAuthToken(): string | null { + return localStorage.getItem('auth_token'); // Assuming the token is stored in localStorage +} + +// A simple function to save the token +function setAuthToken(token: string): void { + localStorage.setItem('auth_token', token); +} + +// A simple function to save the token +function delAuthToken(): void { + localStorage.removeItem('auth_token'); +} + /** * Helper to make authenticated requests. * @param endpoint The API endpoint (relative to base URL). @@ -38,8 +66,13 @@ async function authenticatedRequest(endpoint: string, options: RequestInit): Pro * @returns The ID of the created form. */ export async function createForm(name: string, fields: unknown): Promise { - return await authenticatedRequest('/forms', { + const token = getAuthToken(); // Get the token from storage + const response = await fetch(`${API_BASE_URL}/forms`, { method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` // Add token to the headers + }, body: JSON.stringify({ name, fields }) }); } @@ -48,17 +81,21 @@ export async function createForm(name: string, fields: unknown): Promise * Get all forms (authenticated). * @returns An array of forms. */ -export async function getForms(): Promise { - return await authenticatedRequest('/forms', { method: 'GET' }); -} +export async function getForms(): Promise { + const token = getAuthToken(); // Get the token from storage + const response = await fetch(`${API_BASE_URL}/forms`, { + method: 'GET', + headers: { + Accept: 'application/json', + Authorization: `Bearer ${token}` // Add token to the headers + } + }); -/** - * Get all submissions for a specific form (authenticated). - * @param formId The ID of the form. - * @returns An array of submissions for the form. - */ -export async function getSubmissions(formId: string): Promise { - return await authenticatedRequest(`/forms/${formId}/submissions`, { method: 'GET' }); + if (!response.ok) { + throw new Error(`Error fetching forms: ${response.statusText}`); + } + + return await response.json(); } /** @@ -68,10 +105,12 @@ export async function getSubmissions(formId: string): Promise { * @returns The ID of the created submission. */ export async function submitForm(formId: string, data: unknown): Promise { + const token = getAuthToken(); // Get the token from storage const response = await fetch(`${API_BASE_URL}/forms/${formId}/submissions`, { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` // Add token to the headers }, body: JSON.stringify(data) }); @@ -88,42 +127,50 @@ export async function submitForm(formId: string, data: unknown): Promise * @param credentials The login credentials (username and password). * @returns The generated JWT token if successful. */ -export async function adminLogin(credentials: LoginCredentials): Promise { - const response = await fetch(`${API_BASE_URL}/admin/login`, { - method: 'POST', +export async function getSubmissions(formId: string): Promise { + const token = getAuthToken(); // Get the token from storage + const response = await fetch(`${API_BASE_URL}/forms/${formId}/submissions`, { + method: 'GET', headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(credentials) + Accept: 'application/json', + Authorization: `Bearer ${token}` // Add token to the headers + } }); if (!response.ok) { - throw new Error(`Error during admin login: ${response.statusText}`); + throw new Error(`Error fetching submissions: ${response.statusText}`); } - const data = await response.json(); - localStorage.setItem('authToken', data.token); // Store token locally - return data.token; + return await response.json(); } /** - * Create a new admin user. - * @param user The login credentials for the admin user. - * @returns A success message upon creation. + * Login and get the authentication token. + * @param username The username. + * @param password The password. + * @returns The authentication token. */ -export async function createAdmin(user: LoginCredentials): Promise { - const response = await fetch(`${API_BASE_URL}/admin/create`, { +export async function login(username: string, password: string): Promise { + const response = await fetch(`${API_BASE_URL}/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(user) + body: JSON.stringify({ username, password }) }); if (!response.ok) { - throw new Error(`Error creating admin user: ${response.statusText}`); + throw new Error(`Error logging in: ${response.statusText}`); } - const data = await response.json(); - return data.message; + const { token } = await response.json(); + setAuthToken(token); // Store the token in localStorage +} + +export function logout() { + delAuthToken(); +} + +export function loggedIn() { + return localStorage.getItem('auth_token') !== null; } diff --git a/frontend/src/lib/index.ts b/frontend/src/lib/index.ts deleted file mode 100644 index 856f2b6..0000000 --- a/frontend/src/lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -// place files you want to import through the `$lib` alias in this folder. diff --git a/frontend/src/routes/(auth)/+layout.ts b/frontend/src/routes/(auth)/+layout.ts index 342fa15..fe1e106 100644 --- a/frontend/src/routes/(auth)/+layout.ts +++ b/frontend/src/routes/(auth)/+layout.ts @@ -1,8 +1,8 @@ -import session from "$lib/session.svelte"; -import { redirect } from "@sveltejs/kit"; +import { loggedIn } from '$lib/api'; +import { redirect } from '@sveltejs/kit'; export async function load() { - if (!session.loggedIn()) { - redirect(307, "/login"); + if (!loggedIn()) { + redirect(307, '/login'); } } diff --git a/frontend/src/routes/(auth)/create/+page.svelte b/frontend/src/routes/(auth)/create/+page.svelte index 3428889..0a260d0 100644 --- a/frontend/src/routes/(auth)/create/+page.svelte +++ b/frontend/src/routes/(auth)/create/+page.svelte @@ -6,10 +6,12 @@ let fields: FormField[] = []; function addField() { + // Use a new array assignment to trigger reactivity fields = [...fields, { label: '', name: '', field_type: 'text' }]; } function removeField(index: number) { + // Reassign to trigger reactivity fields = fields.filter((_, i) => i !== index); } @@ -25,125 +27,36 @@ } -
-

Create Form

+

Create Form

-
- - + + +

Fields

+{#each fields as field, i} +
+ + + +
+{/each} -

Fields

- {#each fields as field, i} -
- - - -
- {/each} - -
- - -
-
- - + + diff --git a/frontend/src/routes/(auth)/form/[id]/+page.svelte b/frontend/src/routes/(auth)/form/[id]/+page.svelte index 5cab27c..5b4105a 100644 --- a/frontend/src/routes/(auth)/form/[id]/+page.svelte +++ b/frontend/src/routes/(auth)/form/[id]/+page.svelte @@ -1,179 +1,61 @@ -
- {#if form} -

{form.name}

- +

{form?.name}

-

Submissions

- {#if submissions.length > 0} -
    - {#each submissions as submission} -
  • - {formatSubmissionData(submission.data)} -
  • - {/each} -
- {:else} -

No submissions yet.

- {/if} - {:else} -
Loading...
- {/if} -
+{#if form} + + {#each form.fields as field} +
+ + + {#if field.field_type === 'text'} + + {:else if field.field_type === 'number'} + + {:else if field.field_type === 'date'} + + {:else if field.field_type === 'textarea'} + + {/if} +
+ {/each} + + - +

Submissions

+
    + {#each submissions as submission} +
  • {JSON.stringify(submission.data)}
  • + {/each} +
+{:else} +

Loading...

+{/if} diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/(auth)/main/+page.svelte similarity index 93% rename from frontend/src/routes/+page.svelte rename to frontend/src/routes/(auth)/main/+page.svelte index f6e752d..5ff6b3d 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/(auth)/main/+page.svelte @@ -1,7 +1,7 @@ -{@render children?.()} +{@render children()} diff --git a/frontend/src/routes/+page.ts b/frontend/src/routes/+page.ts index ae27036..471709e 100644 --- a/frontend/src/routes/+page.ts +++ b/frontend/src/routes/+page.ts @@ -1,8 +1,7 @@ -import session from '$lib/session.svelte'; +import { loggedIn } from '$lib/api'; import { redirect } from '@sveltejs/kit'; export async function load() { - if (!session.loggedIn()) { - redirect(307, '/login'); - } + const page = loggedIn() ? '/main' : '/login'; + redirect(307, page); } diff --git a/frontend/src/routes/login/+page.svelte b/frontend/src/routes/login/+page.svelte index 092f2f6..b69d326 100644 --- a/frontend/src/routes/login/+page.svelte +++ b/frontend/src/routes/login/+page.svelte @@ -1,103 +1,51 @@ - diff --git a/frontend/src/routes/login/+page.ts b/frontend/src/routes/login/+page.ts index bb67d7f..22f14b6 100644 --- a/frontend/src/routes/login/+page.ts +++ b/frontend/src/routes/login/+page.ts @@ -1,8 +1,8 @@ -import session from '$lib/session.svelte'; +import { loggedIn } from '$lib/api'; import { redirect } from '@sveltejs/kit'; export async function load() { - if (session.loggedIn()) { + if (loggedIn()) { redirect(307, '/'); } }