Initial commit
This commit is contained in:
commit
66a2e1af6c
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
config.toml
|
1732
Cargo.lock
generated
Normal file
1732
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "api"
|
||||
description = "API for sador.me website"
|
||||
version = "1.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
diesel = { version = "2.1", features = ["postgres", "r2d2"] }
|
||||
actix-web = "4"
|
||||
toml = "0.8"
|
4
config.toml.example
Normal file
4
config.toml.example
Normal file
@ -0,0 +1,4 @@
|
||||
database_url = "postgres://user:password@host/database"
|
||||
api_version = "1.0.0"
|
||||
host = "0.0.0.0"
|
||||
port = 3000
|
9
diesel.toml
Normal file
9
diesel.toml
Normal file
@ -0,0 +1,9 @@
|
||||
# For documentation on how to configure this file,
|
||||
# see https://diesel.rs/guides/configuring-diesel-cli
|
||||
|
||||
[print_schema]
|
||||
file = "src/schema.rs"
|
||||
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
|
||||
|
||||
[migrations_directory]
|
||||
dir = "migrations"
|
0
migrations/.keep
Normal file
0
migrations/.keep
Normal file
6
migrations/00000000000000_diesel_initial_setup/down.sql
Normal file
6
migrations/00000000000000_diesel_initial_setup/down.sql
Normal file
@ -0,0 +1,6 @@
|
||||
-- This file was automatically created by Diesel to setup helper functions
|
||||
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||
-- changes will be added to existing projects as new migrations.
|
||||
|
||||
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
|
||||
DROP FUNCTION IF EXISTS diesel_set_updated_at();
|
36
migrations/00000000000000_diesel_initial_setup/up.sql
Normal file
36
migrations/00000000000000_diesel_initial_setup/up.sql
Normal file
@ -0,0 +1,36 @@
|
||||
-- This file was automatically created by Diesel to setup helper functions
|
||||
-- and other internal bookkeeping. This file is safe to edit, any future
|
||||
-- changes will be added to existing projects as new migrations.
|
||||
|
||||
|
||||
|
||||
|
||||
-- Sets up a trigger for the given table to automatically set a column called
|
||||
-- `updated_at` whenever the row is modified (unless `updated_at` was included
|
||||
-- in the modified columns)
|
||||
--
|
||||
-- # Example
|
||||
--
|
||||
-- ```sql
|
||||
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
|
||||
--
|
||||
-- SELECT diesel_manage_updated_at('users');
|
||||
-- ```
|
||||
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
|
||||
BEGIN
|
||||
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
|
||||
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
IF (
|
||||
NEW IS DISTINCT FROM OLD AND
|
||||
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
|
||||
) THEN
|
||||
NEW.updated_at := current_timestamp;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
1
migrations/2025-03-16-185127_create_posts/down.sql
Normal file
1
migrations/2025-03-16-185127_create_posts/down.sql
Normal file
@ -0,0 +1 @@
|
||||
DROP TABLE posts;
|
8
migrations/2025-03-16-185127_create_posts/up.sql
Normal file
8
migrations/2025-03-16-185127_create_posts/up.sql
Normal file
@ -0,0 +1,8 @@
|
||||
CREATE TABLE posts (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
author_id INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_author_id ON posts(author_id);
|
19
src/config.rs
Normal file
19
src/config.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct Config {
|
||||
pub database_url: String,
|
||||
pub api_version: String,
|
||||
pub host: String,
|
||||
pub port: u16
|
||||
}
|
||||
|
||||
pub fn read_config() -> Config {
|
||||
let config_data = std::fs
|
||||
::read_to_string("config.toml")
|
||||
.expect("failed to read config file, maybe it's missing?");
|
||||
|
||||
toml
|
||||
::from_str(&config_data)
|
||||
.expect("invalid TOML data, check your config")
|
||||
}
|
13
src/database/mod.rs
Normal file
13
src/database/mod.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use diesel::r2d2::{ConnectionManager, Pool};
|
||||
use diesel::pg::PgConnection;
|
||||
|
||||
pub mod models;
|
||||
|
||||
pub type DbPool = Pool<ConnectionManager<PgConnection>>;
|
||||
|
||||
pub fn establish_connection(database_url: String) -> DbPool {
|
||||
let manager = ConnectionManager::<PgConnection>::new(database_url);
|
||||
Pool::builder()
|
||||
.build(manager)
|
||||
.expect("failed to create database pool")
|
||||
}
|
1
src/database/models/mod.rs
Normal file
1
src/database/models/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod post;
|
17
src/database/models/post.rs
Normal file
17
src/database/models/post.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use diesel::prelude::*;
|
||||
|
||||
#[derive(Queryable, Serialize, Deserialize)]
|
||||
pub struct Post {
|
||||
pub id: i32,
|
||||
pub title: String,
|
||||
pub content: String,
|
||||
pub author_id: i32
|
||||
}
|
||||
|
||||
#[derive(Insertable)]
|
||||
#[diesel(table_name = crate::schema::posts)]
|
||||
pub struct NewPost<'a> {
|
||||
pub title: &'a str,
|
||||
pub content: &'a str,
|
||||
}
|
39
src/main.rs
Normal file
39
src/main.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use actix_web::{main, web::Data, App, HttpServer};
|
||||
use actix_web::middleware::NormalizePath;
|
||||
|
||||
mod database;
|
||||
mod config;
|
||||
mod schema;
|
||||
mod v1;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct State {
|
||||
db: database::DbPool,
|
||||
config: config::Config
|
||||
}
|
||||
|
||||
#[main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
let config = config::read_config();
|
||||
let conn = database::establish_connection(config.clone().database_url);
|
||||
|
||||
let state = State {
|
||||
db: conn,
|
||||
config: config.clone()
|
||||
};
|
||||
|
||||
let server = HttpServer::new(move || {
|
||||
let state = state.clone();
|
||||
|
||||
App::new()
|
||||
.wrap(NormalizePath::trim())
|
||||
.app_data(Data::new(state))
|
||||
.configure(v1::init_routes)
|
||||
})
|
||||
.bind((config.host, config.port))?
|
||||
.run();
|
||||
|
||||
println!("API running at on port {}", config.port);
|
||||
|
||||
server.await
|
||||
}
|
10
src/schema.rs
Normal file
10
src/schema.rs
Normal file
@ -0,0 +1,10 @@
|
||||
// @generated automatically by Diesel CLI.
|
||||
|
||||
diesel::table! {
|
||||
posts (id) {
|
||||
id -> Int4,
|
||||
title -> Varchar,
|
||||
content -> Text,
|
||||
author_id -> Int4,
|
||||
}
|
||||
}
|
27
src/v1/mod.rs
Normal file
27
src/v1/mod.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use actix_web::{Responder, HttpResponse, web::{ServiceConfig, Data, scope}, get};
|
||||
use serde::Serialize;
|
||||
use crate::State;
|
||||
|
||||
mod posts;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PingResponse {
|
||||
version: String
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
async fn index(state: Data<State>) -> impl Responder {
|
||||
let response = PingResponse {
|
||||
version: state.config.api_version.clone(),
|
||||
};
|
||||
|
||||
HttpResponse::Ok().json(response)
|
||||
}
|
||||
|
||||
pub fn init_routes(cfg: &mut ServiceConfig) {
|
||||
cfg
|
||||
.service(index)
|
||||
.service(scope("/api/v1")
|
||||
.configure(posts::init_routes)
|
||||
);
|
||||
}
|
27
src/v1/posts.rs
Normal file
27
src/v1/posts.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use actix_web::{web::{ServiceConfig, Data, scope}, HttpResponse, Responder, get};
|
||||
use diesel::RunQueryDsl;
|
||||
|
||||
use crate::database::models::post::Post;
|
||||
use crate::schema::posts::dsl::*;
|
||||
use crate::State;
|
||||
|
||||
#[get("")]
|
||||
async fn get(state: Data<State>) -> impl Responder {
|
||||
let mut conn = match state.db.get() {
|
||||
Ok(conn) => conn,
|
||||
Err(why) => return HttpResponse
|
||||
::InternalServerError()
|
||||
.body(format!("database connection error: {}", why)),
|
||||
};
|
||||
|
||||
match posts.load::<Post>(&mut conn) {
|
||||
Ok(data) => HttpResponse::Ok().json(data),
|
||||
Err(why) => HttpResponse::InternalServerError().body(why.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_routes(cfg: &mut ServiceConfig) {
|
||||
cfg.service(scope("/posts")
|
||||
.service(get)
|
||||
);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user