init commit
This commit is contained in:
commit
3328e0c4b1
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/target
|
||||||
|
Secrets*.toml
|
||||||
|
.shuttle-storage
|
3076
Cargo.lock
generated
Normal file
3076
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "klectr-bin"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = "0.7.3"
|
||||||
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
|
shuttle-axum = "0.46.0"
|
||||||
|
shuttle-runtime = "0.46.0"
|
||||||
|
shuttle-shared-db = { version = "0.46.0", features = ["postgres", "sqlx"] }
|
||||||
|
sqlx = "0.7.1"
|
||||||
|
tokio = "1.28.2"
|
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Shuttle shared Postgres DB with Axum
|
||||||
|
|
||||||
|
This template shows how to connect a Postgres database and use it for a simple TODO list app.
|
||||||
|
|
||||||
|
## Example usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST -H 'content-type: application/json' localhost:8000/todos --data '{"note":"My todo"}'
|
||||||
|
# {"id":1,"note":"My todo"}
|
||||||
|
|
||||||
|
curl localhost:8000/todos/1
|
||||||
|
# {"id":1,"note":"My todo"}
|
||||||
|
```
|
3
build.rs
Normal file
3
build.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
println!("cargo:rerun-if-changed=migrations");
|
||||||
|
}
|
5
migrations/0001_init.sql
Normal file
5
migrations/0001_init.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
create table if not exists pastes (
|
||||||
|
id serial primary key,
|
||||||
|
title text NOT NULL,
|
||||||
|
content text NOT NULL
|
||||||
|
);
|
70
src/index.html
Normal file
70
src/index.html
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>KlectrBin</title>
|
||||||
|
<script src="https://unpkg.com/htmx.org@2.0.0"
|
||||||
|
integrity="sha384-wS5l5IKJBvK6sPTKa2WZ1js3d947pvWXbPJ1OmWfEuxLgeHcEbjUUA5i9V5ZkpCw"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<!-- Container-->
|
||||||
|
|
||||||
|
<body class="container mx-auto max-w-6xl">
|
||||||
|
<!-- HEADER -->
|
||||||
|
<header class="border-b-2 flex justify-center mb-4">
|
||||||
|
<div class="px-2 py-2 w-full max-w-6xl ">
|
||||||
|
<h1 class="text-2xl font-bold text-gray-900 sm:text-3xl">Klectr<sup class="text-blue-500">Bin</sup></h1>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- MAIN-->
|
||||||
|
<main class="container m-auto max-w-6xl mb-8">
|
||||||
|
<div class="rounded-lg bg-gray-200 lg:col-span-2">
|
||||||
|
<div class="rounded-lg border border-gray-200 shadow-sm">
|
||||||
|
<form hx-post="/paste">
|
||||||
|
<div>
|
||||||
|
<input name='title' id='title' placeholder="Put title here..."
|
||||||
|
class="border-none p-2 w-full focus:outline-none" />
|
||||||
|
</div>
|
||||||
|
<textarea name='content' id="content"
|
||||||
|
class="w-full resize-y border-none align-top sm:text-sm p-2 bg-gray-50 min-h-fit focus:outline-none"
|
||||||
|
rows="16" placeholder="Paste your stuff here..."></textarea>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end gap-2 bg-white p-3">
|
||||||
|
<button type="button" onclick="handleClearClick()"
|
||||||
|
class="rounded bg-gray-200 px-3 py-1.5 text-sm font-medium text-gray-700 hover:text-gray-600">
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="submit"
|
||||||
|
class="rounded bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700">
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="border-t-2">
|
||||||
|
<p class="mt-2 text-center text-sm text-gray-500 ">
|
||||||
|
Copyright © 2024. All rights reserved.
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
function handleClearClick() {
|
||||||
|
const textArea = document.querySelector("#content")
|
||||||
|
const titleInput = document.querySelector("#title")
|
||||||
|
textArea.value = ""
|
||||||
|
titleInput.value = ""
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</html>
|
83
src/main.rs
Normal file
83
src/main.rs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
use std::fs;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
extract::{Path, State},
|
||||||
|
http::StatusCode,
|
||||||
|
response::{Html, IntoResponse},
|
||||||
|
routing::{get, post},
|
||||||
|
Form, Json, Router,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::{FromRow, PgPool};
|
||||||
|
|
||||||
|
async fn retrieve(
|
||||||
|
Path(id): Path<i32>,
|
||||||
|
State(state): State<MyState>,
|
||||||
|
) -> Result<impl IntoResponse, impl IntoResponse> {
|
||||||
|
match sqlx::query_as::<_, Paste>("SELECT * FROM todos WHERE id = $1")
|
||||||
|
.bind(id)
|
||||||
|
.fetch_one(&state.pool)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(todo) => Ok((StatusCode::OK, Json(todo))),
|
||||||
|
Err(e) => Err((StatusCode::BAD_REQUEST, e.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add(
|
||||||
|
State(state): State<MyState>,
|
||||||
|
Form(data): Form<PasteNew>,
|
||||||
|
) -> Result<impl IntoResponse, impl IntoResponse> {
|
||||||
|
match sqlx::query_as::<_, Paste>(
|
||||||
|
"INSERT INTO pastes (title, content) VALUES ($1, $2) RETURNING id, title, content",
|
||||||
|
)
|
||||||
|
.bind(&data.title)
|
||||||
|
.bind(&data.content)
|
||||||
|
.fetch_one(&state.pool)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(todo) => Ok((StatusCode::CREATED, Json(todo))),
|
||||||
|
Err(e) => Err((StatusCode::BAD_REQUEST, e.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_index() -> Html<String> {
|
||||||
|
let path = format!("{}/{}/{}", env!("CARGO_MANIFEST_DIR"), "src", "index.html");
|
||||||
|
let html = fs::read_to_string(path).expect("oof");
|
||||||
|
Html(html)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct MyState {
|
||||||
|
pool: PgPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[shuttle_runtime::main]
|
||||||
|
async fn main(#[shuttle_shared_db::Postgres] pool: PgPool) -> shuttle_axum::ShuttleAxum {
|
||||||
|
sqlx::migrate!()
|
||||||
|
.run(&pool)
|
||||||
|
.await
|
||||||
|
.expect("Failed to run migrations");
|
||||||
|
|
||||||
|
let state = MyState { pool };
|
||||||
|
let router = Router::new()
|
||||||
|
.route("/", get(send_index))
|
||||||
|
.route("/paste", post(add))
|
||||||
|
.route("/pastes/:id", get(retrieve))
|
||||||
|
.with_state(state);
|
||||||
|
|
||||||
|
Ok(router.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct PasteNew {
|
||||||
|
pub title: String,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, FromRow)]
|
||||||
|
struct Paste {
|
||||||
|
pub id: i32,
|
||||||
|
pub title: String,
|
||||||
|
pub content: String,
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user