1 Commits

Author SHA1 Message Date
Maurice
1840393fa8 Experimental: websockets 2025-07-18 17:19:22 +02:00
13 changed files with 1905 additions and 1686 deletions

974
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
FROM node:24-alpine3.22 AS frontend-builder
FROM node:21-alpine3.19 AS frontend-builder
WORKDIR /build
RUN corepack enable
COPY pastabble-frontend/ .
RUN pnpm i && pnpm build
FROM rust:alpine3.22 AS builder
FROM rust:alpine3.19 AS builder
WORKDIR /build
RUN apk add --no-cache musl-dev
@@ -18,7 +18,7 @@ RUN case "$(apk --print-arch)" in \
mv ./target/aarch64-unknown-linux-musl /release ;; \
esac
FROM alpine:3.22
FROM alpine:3.19
WORKDIR /app
RUN mkdir wwwroot data && \

View File

@@ -1,4 +1,4 @@
segment_size: 524288
use_compression: false
version: 0.34
vQ<>
vQ<>

Binary file not shown.

View File

@@ -10,19 +10,20 @@
"check": "svelte-check --tsconfig ./tsconfig.json"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^5.1.0",
"@tailwindcss/vite": "^4.1.8",
"@tsconfig/svelte": "^5.0.4",
"daisyui": "^5.0.43",
"svelte": "^5.33.14",
"svelte-check": "^4.2.1",
"tailwindcss": "^4.1.8",
"tslib": "^2.8.1",
"typescript": "^5.8.3",
"vite": "^6.3.5"
"@sveltejs/vite-plugin-svelte": "^3.0.1",
"@tsconfig/svelte": "^5.0.2",
"autoprefixer": "^10.4.17",
"daisyui": "^4.6.1",
"postcss": "^8.4.33",
"svelte": "^4.2.8",
"svelte-check": "^3.6.2",
"tailwindcss": "^3.4.1",
"tslib": "^2.6.2",
"typescript": "^5.2.2",
"vite": "^5.0.8"
},
"dependencies": {
"highlight.js": "^11.11.1",
"highlight.js": "^11.9.0",
"svelte-spa-router": "^4.0.1"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -1,2 +1,3 @@
@import 'tailwindcss';
@plugin "daisyui";
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -1,8 +1,9 @@
import './app.css'
import 'highlight.js/styles/github-dark.min.css';
import { mount } from 'svelte';
import App from './App.svelte'
const app = mount(App, { target: document.getElementById("app") });
const app = new App({
target: document.getElementById('app'),
})
export default app

View File

@@ -4,5 +4,6 @@ export default {
theme: {
extend: {},
},
plugins: [require('daisyui')],
}

View File

@@ -1,8 +1,7 @@
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
import tailwindcss from '@tailwindcss/vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte(), tailwindcss()],
plugins: [svelte()]
})

View File

@@ -1,10 +1,10 @@
use std::{env, fs::{self, File}, thread};
use std::{collections::HashMap, env, fs::{self, File}, sync::{Arc, Mutex}, thread};
use chrono::Utc;
use kv::{Config, Store, Json, Bucket, Value};
use paste::Paste;
use rand::{Rng, distributions::Alphanumeric};
use rouille::{Response, router, try_or_400, post_input, Request, Server};
use rouille::{post_input, router, try_or_400, websocket, Request, Response, Server};
use signal_hook::{iterator::Signals, consts::{SIGINT, SIGTERM}};
mod paste;
@@ -30,6 +30,7 @@ fn generate_key<T : Value>(key: Option<String>, store: &Bucket<String, T>) -> St
}
}
// Get query parameters
fn get_query(req: &Request, name: &str) -> Option<String> {
let params: Vec<&str> = req.raw_query_string().split('&').collect();
let param = params.iter().find(|p|p.starts_with(&format!("{}=", name)));
@@ -58,6 +59,8 @@ fn main() {
let links = store.bucket::<String, String>(Some("links"))
.expect("Failed to open links bucket");
// Temp pastes
let temp_pastes = Arc::new(Mutex::new(HashMap::new()));
// Create server
let server = Server::new(format!("0.0.0.0:{}", port), move |req| {
@@ -68,6 +71,10 @@ fn main() {
}
}
if req.url().starts_with("/ws") {
handle_websocket(temp_pastes.clone(), &req);
}
router!(req,
(GET) (/) => {
match File::open(format!("{}/index.html", &source_dir)) {
@@ -115,7 +122,7 @@ fn main() {
},
(POST) (/{id: String}) => {
let id = if id.is_empty() { None } else { Some(id) };
register_paste(&pastes, req, id, prefix.clone())
register_paste(&pastes, req, id, prefix.clone(), None)
},
_ => Response::empty_404()
)
@@ -143,8 +150,7 @@ fn main() {
}
// Register paste handler
fn register_paste(pastes: &Bucket<String, Json<Paste>>, req: &Request, id: Option<String>, prefix: Option<String>) -> Response {
fn register_paste(pastes: &Bucket<String, Json<Paste>>, req: &Request, id: Option<String>, prefix: Option<String>, allow_edit: Option<bool>) -> Response {
// Try read body from form data, and if not present from request body
let body = match post_input!(req, {
content: String
@@ -165,6 +171,7 @@ fn register_paste(pastes: &Bucket<String, Json<Paste>>, req: &Request, id: Optio
content: body,
expires: None,
language,
allow_edit,
created: Utc::now()
});
@@ -210,3 +217,31 @@ fn register_link(links: &Bucket<String,String>, req: &Request, id: Option<String
Response::text(url)
}
fn handle_websocket(pastes: Arc<Mutex<HashMap<String, String>>>, req: &Request) -> Response {
let key = get_query(&req, "key").unwrap();
let mut current_text = String::new();
match pastes.lock().expect("Failed to access pastes DB").get(&key) {
Some(paste) => {
current_text = paste.clone()
},
None => {
// OK, ignore
}
}
let (response, websocket) = try_or_400!(websocket::start(req, Some("pastabble")));
if let Ok(mut ws) = websocket.recv() {
thread::spawn(move || {
if !current_text.is_empty() {
_ = ws.send_text(&current_text);
}
});
return response.with_additional_header("Access-Control-Allow-Origin", "*");
} else {
return Response::empty_400()
}
}

View File

@@ -9,5 +9,7 @@ pub struct Paste {
#[serde(with = "ts_seconds_option")]
pub expires: Option<DateTime<Utc>>,
pub created: DateTime<Utc>
pub created: DateTime<Utc>,
pub allow_edit: Option<bool>
}