SQLX
This commit is contained in:
6
.env.example
Normal file
6
.env.example
Normal file
@@ -0,0 +1,6 @@
|
||||
# Database Configuration
|
||||
DATABASE_URL=postgresql://username:password@localhost:5432/mission_control
|
||||
|
||||
# Optional: Override default server settings
|
||||
# SERVER_HOST=0.0.0.0
|
||||
# SERVER_PORT=3001
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -66,3 +66,5 @@ coverage/
|
||||
# Storybook build outputs
|
||||
.out
|
||||
.storybook-out
|
||||
|
||||
.env
|
||||
@@ -13,3 +13,7 @@ serde_json = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "chrono", "uuid"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
uuid = { version = "1.0", features = ["v4", "serde"] }
|
||||
dotenvy = "0.15"
|
||||
|
||||
58
backend/migrations/001_initial.sql
Normal file
58
backend/migrations/001_initial.sql
Normal file
@@ -0,0 +1,58 @@
|
||||
-- Create task_status enum
|
||||
CREATE TYPE task_status AS ENUM ('todo', 'inprogress', 'done', 'cancelled');
|
||||
|
||||
-- Create users table
|
||||
CREATE TABLE users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
email VARCHAR(255) UNIQUE NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Create projects table
|
||||
CREATE TABLE projects (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(255) NOT NULL,
|
||||
owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Create tasks table
|
||||
CREATE TABLE tasks (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
status task_status NOT NULL DEFAULT 'todo',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Create indexes for better performance
|
||||
CREATE INDEX idx_projects_owner_id ON projects(owner_id);
|
||||
CREATE INDEX idx_tasks_project_id ON tasks(project_id);
|
||||
CREATE INDEX idx_tasks_status ON tasks(status);
|
||||
|
||||
-- Create updated_at trigger function
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
-- Create triggers to auto-update updated_at
|
||||
CREATE TRIGGER update_users_updated_at
|
||||
BEFORE UPDATE ON users
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_projects_updated_at
|
||||
BEFORE UPDATE ON projects
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_tasks_updated_at
|
||||
BEFORE UPDATE ON tasks
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -3,11 +3,13 @@ use axum::{
|
||||
Router,
|
||||
Json,
|
||||
response::Json as ResponseJson,
|
||||
extract::Query,
|
||||
extract::{Query, Extension},
|
||||
};
|
||||
use tower_http::cors::CorsLayer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing_subscriber;
|
||||
use sqlx::{PgPool, postgres::PgPoolOptions};
|
||||
use std::env;
|
||||
|
||||
|
||||
mod routes;
|
||||
@@ -43,13 +45,26 @@ async fn echo_handler(Json(payload): Json<serde_json::Value>) -> ResponseJson<Ap
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
// Load environment variables from .env file
|
||||
dotenvy::dotenv().ok();
|
||||
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// Database connection
|
||||
let database_url = env::var("DATABASE_URL")
|
||||
.expect("DATABASE_URL must be set in environment or .env file");
|
||||
|
||||
let pool = PgPoolOptions::new()
|
||||
.max_connections(10)
|
||||
.connect(&database_url)
|
||||
.await?;
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(|| async { "Bloop API" }))
|
||||
.route("/health", get(health::health_check))
|
||||
.route("/hello", get(hello_handler))
|
||||
.route("/echo", post(echo_handler))
|
||||
.layer(Extension(pool))
|
||||
.layer(CorsLayer::permissive());
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3001").await?;
|
||||
|
||||
8
backend/src/models/api_response.rs
Normal file
8
backend/src/models/api_response.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ApiResponse<T> {
|
||||
pub success: bool,
|
||||
pub data: Option<T>,
|
||||
pub message: Option<String>,
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
use serde::Serialize;
|
||||
pub mod user;
|
||||
pub mod project;
|
||||
pub mod task;
|
||||
pub mod api_response;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ApiResponse<T> {
|
||||
pub success: bool,
|
||||
pub data: Option<T>,
|
||||
pub message: Option<String>,
|
||||
}
|
||||
pub use user::User;
|
||||
pub use project::Project;
|
||||
pub use task::{Task, TaskStatus};
|
||||
pub use api_response::ApiResponse;
|
||||
|
||||
24
backend/src/models/project.rs
Normal file
24
backend/src/models/project.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||
pub struct Project {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub owner_id: Uuid, // Foreign key to User
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateProject {
|
||||
pub name: String,
|
||||
pub owner_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UpdateProject {
|
||||
pub name: Option<String>,
|
||||
}
|
||||
38
backend/src/models/task.rs
Normal file
38
backend/src/models/task.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{FromRow, Type};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, Type, Serialize, Deserialize, PartialEq)]
|
||||
#[sqlx(type_name = "task_status", rename_all = "lowercase")]
|
||||
pub enum TaskStatus {
|
||||
Todo,
|
||||
InProgress,
|
||||
Done,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||
pub struct Task {
|
||||
pub id: Uuid,
|
||||
pub project_id: Uuid, // Foreign key to Project
|
||||
pub title: String,
|
||||
pub description: Option<String>,
|
||||
pub status: TaskStatus,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateTask {
|
||||
pub project_id: Uuid,
|
||||
pub title: String,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UpdateTask {
|
||||
pub title: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub status: Option<TaskStatus>,
|
||||
}
|
||||
25
backend/src/models/user.rs
Normal file
25
backend/src/models/user.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, FromRow, Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
pub id: Uuid,
|
||||
pub email: String,
|
||||
pub password: String, // This should be hashed
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateUser {
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UpdateUser {
|
||||
pub email: Option<String>,
|
||||
pub password: Option<String>,
|
||||
}
|
||||
Reference in New Issue
Block a user