Compare commits
56 commits
actions-te
...
master
Author | SHA1 | Date | |
---|---|---|---|
68996acd54 | |||
768c8c60b8 | |||
5002f11bf3 | |||
808ffd4964 | |||
1f69a77a03 | |||
f3ca7e3328 | |||
42aceb2a01 | |||
1652a850b8 | |||
181ebb3a63 | |||
c83a775ac2 | |||
e3a5527a8c | |||
5d0f73a5f3 | |||
c5b0a8ef79 | |||
75755052c3 | |||
01eb19e732 | |||
91568a1590 | |||
8039fe75a2 | |||
86e41865bb | |||
b313b5ce73 | |||
f1de42e5c0 | |||
e4575d7d6e | |||
d1c583d4b0 | |||
8623bcb9ae | |||
353cb5dfde | |||
1f2589976d | |||
3f2bdfdaed | |||
27fe305640 | |||
1ebbe5d922 | |||
51751e3ee2 | |||
f352129c78 | |||
8db35b80d9 | |||
56332c61f3 | |||
fa31a0a51a | |||
1069d4c676 | |||
7bfebb27d9 | |||
7abb3c5d11 | |||
7d5c70ddd6 | |||
528a7b6899 | |||
fa67edc8c5 | |||
39881a036a | |||
57fd472eda | |||
|
84136f1106 | ||
|
ba784da74e | ||
|
0a80ac1359 | ||
|
6564499c98 | ||
|
4fa2cc2a53 | ||
|
4279653ca9 | ||
|
53326b0435 | ||
|
68f4081745 | ||
|
089bb95c4f | ||
|
5f05206891 | ||
|
4fcd4e9e19 | ||
|
89f902f99f | ||
|
958466818b | ||
|
f41c2eb66b | ||
|
76cfc906ef |
44 changed files with 1658 additions and 435 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
use flake
|
13
.github/workflows/build_nix.yml
vendored
Normal file
13
.github/workflows/build_nix.yml
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
name: "Build legacy Nix package on Ubuntu"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: cachix/install-nix-action@v12
|
||||||
|
- name: Building package
|
||||||
|
run: nix-build . -A defaultPackage.x86_64-linux
|
154
.github/workflows/gh-release.yml
vendored
Normal file
154
.github/workflows/gh-release.yml
vendored
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
name: GitHub Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v[0-9]+.[0-9]+.[0-9]+*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Publish to Github Releases
|
||||||
|
outputs:
|
||||||
|
rc: ${{ steps.check-tag.outputs.rc }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- target: aarch64-unknown-linux-musl
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: aarch64-apple-darwin
|
||||||
|
os: macos-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: aarch64-pc-windows-msvc
|
||||||
|
os: windows-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: "--no-default-features"
|
||||||
|
- target: x86_64-apple-darwin
|
||||||
|
os: macos-latest
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: x86_64-pc-windows-msvc
|
||||||
|
os: windows-latest
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: x86_64-unknown-linux-musl
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: i686-unknown-linux-musl
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: i686-pc-windows-msvc
|
||||||
|
os: windows-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: armv7-unknown-linux-musleabihf
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: arm-unknown-linux-musleabihf
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: ""
|
||||||
|
- target: mips-unknown-linux-musl
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: "--no-default-features"
|
||||||
|
- target: mipsel-unknown-linux-musl
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: "--no-default-features"
|
||||||
|
- target: mips64-unknown-linux-gnuabi64
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: "--no-default-features"
|
||||||
|
- target: mips64el-unknown-linux-gnuabi64
|
||||||
|
os: ubuntu-latest
|
||||||
|
use-cross: true
|
||||||
|
cargo-flags: "--no-default-features"
|
||||||
|
runs-on: ${{matrix.os}}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Check Tag
|
||||||
|
id: check-tag
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
tag=${GITHUB_REF##*/}
|
||||||
|
echo "::set-output name=version::$tag"
|
||||||
|
if [[ "$tag" =~ [0-9]+.[0-9]+.[0-9]+$ ]]; then
|
||||||
|
echo "::set-output name=rc::false"
|
||||||
|
else
|
||||||
|
echo "::set-output name=rc::true"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
- name: Install Rust Toolchain Components
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
override: true
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
toolchain: stable
|
||||||
|
profile: minimal # minimal component installation (ie, no documentation)
|
||||||
|
|
||||||
|
- name: Show Version Information (Rust, cargo, GCC)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
gcc --version || true
|
||||||
|
rustup -V
|
||||||
|
rustup toolchain list
|
||||||
|
rustup default
|
||||||
|
cargo -V
|
||||||
|
rustc -V
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
use-cross: ${{ matrix.use-cross }}
|
||||||
|
command: build
|
||||||
|
args: --locked --release --target=${{ matrix.target }} ${{ matrix.cargo-flags }}
|
||||||
|
|
||||||
|
- name: Build Archive
|
||||||
|
shell: bash
|
||||||
|
id: package
|
||||||
|
env:
|
||||||
|
target: ${{ matrix.target }}
|
||||||
|
version: ${{ steps.check-tag.outputs.version }}
|
||||||
|
run: |
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
bin=${GITHUB_REPOSITORY##*/}
|
||||||
|
src=`pwd`
|
||||||
|
dist=$src/dist
|
||||||
|
name=$bin-$version-$target
|
||||||
|
executable=target/$target/release/$bin
|
||||||
|
|
||||||
|
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
||||||
|
executable=$executable.exe
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir $dist
|
||||||
|
cp $executable $dist
|
||||||
|
cd $dist
|
||||||
|
|
||||||
|
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
||||||
|
archive=$dist/$name.zip
|
||||||
|
7z a $archive *
|
||||||
|
echo "::set-output name=archive::`pwd -W`/$name.zip"
|
||||||
|
else
|
||||||
|
archive=$dist/$name.tar.gz
|
||||||
|
tar czf $archive *
|
||||||
|
echo "::set-output name=archive::$archive"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Publish Archive
|
||||||
|
uses: softprops/action-gh-release@v0.1.5
|
||||||
|
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
|
with:
|
||||||
|
draft: false
|
||||||
|
files: ${{ steps.package.outputs.archive }}
|
||||||
|
prerelease: ${{ steps.check-tag.outputs.rc == 'true' }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -2,6 +2,8 @@
|
||||||
# will have compiled files and executables
|
# will have compiled files and executables
|
||||||
debug/
|
debug/
|
||||||
target/
|
target/
|
||||||
|
result
|
||||||
|
.direnv/
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|
58
Cargo.lock
generated
58
Cargo.lock
generated
|
@ -1159,6 +1159,35 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "karton"
|
||||||
|
version = "2.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"actix-files",
|
||||||
|
"actix-multipart",
|
||||||
|
"actix-web",
|
||||||
|
"actix-web-httpauth",
|
||||||
|
"askama",
|
||||||
|
"askama-filters",
|
||||||
|
"bytesize",
|
||||||
|
"chrono",
|
||||||
|
"clap",
|
||||||
|
"env_logger",
|
||||||
|
"futures",
|
||||||
|
"harsh",
|
||||||
|
"lazy_static",
|
||||||
|
"linkify",
|
||||||
|
"log",
|
||||||
|
"mime_guess",
|
||||||
|
"qrcode-generator",
|
||||||
|
"rand",
|
||||||
|
"rust-embed",
|
||||||
|
"sanitize-filename",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"syntect",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "language-tags"
|
name = "language-tags"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
@ -1281,35 +1310,6 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "microbin"
|
|
||||||
version = "1.2.0"
|
|
||||||
dependencies = [
|
|
||||||
"actix-files",
|
|
||||||
"actix-multipart",
|
|
||||||
"actix-web",
|
|
||||||
"actix-web-httpauth",
|
|
||||||
"askama",
|
|
||||||
"askama-filters",
|
|
||||||
"bytesize",
|
|
||||||
"chrono",
|
|
||||||
"clap",
|
|
||||||
"env_logger",
|
|
||||||
"futures",
|
|
||||||
"harsh",
|
|
||||||
"lazy_static",
|
|
||||||
"linkify",
|
|
||||||
"log",
|
|
||||||
"mime_guess",
|
|
||||||
"qrcode-generator",
|
|
||||||
"rand",
|
|
||||||
"rust-embed",
|
|
||||||
"sanitize-filename",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"syntect",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mime"
|
name = "mime"
|
||||||
version = "0.3.16"
|
version = "0.3.16"
|
||||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -1,14 +1,14 @@
|
||||||
[package]
|
[package]
|
||||||
name = "microbin"
|
name = "karton"
|
||||||
version = "1.2.0"
|
version = "2.0.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["Daniel Szabo <daniel.szabo99@outlook.com>"]
|
authors = ["Jade <jade@schrottkatze.de>", "Daniel Szabo <daniel.szabo99@outlook.com>"]
|
||||||
license = "BSD-3-Clause"
|
license = "BSD-3-Clause"
|
||||||
description = "Simple, performant, configurable, entirely self-contained Pastebin and URL shortener."
|
description = "Simple, performant, configurable, entirely self-contained Pastebin and URL shortener."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
homepage = "https://github.com/szabodanika/microbin"
|
homepage = "https://gitlab.com/obsidianical/microbin"
|
||||||
repository = "https://github.com/szabodanika/microbin"
|
repository = "https://gitlab.com/obsidianical/microbin"
|
||||||
keywords = ["pastebin", "pastabin", "microbin", "actix", "selfhosted"]
|
keywords = ["pastebin", "karton", "microbin", "actix", "selfhosted"]
|
||||||
categories = ["pastebins"]
|
categories = ["pastebins"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
13
Dockerfile
13
Dockerfile
|
@ -1,4 +1,4 @@
|
||||||
FROM rust:latest as build
|
FROM docker.io/rust:latest as build
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
@ -8,10 +8,11 @@ RUN \
|
||||||
DEBIAN_FRONTEND=noninteractive \
|
DEBIAN_FRONTEND=noninteractive \
|
||||||
apt-get update &&\
|
apt-get update &&\
|
||||||
apt-get -y install ca-certificates tzdata &&\
|
apt-get -y install ca-certificates tzdata &&\
|
||||||
|
CARGO_NET_GIT_FETCH_WITH_CLI=true \
|
||||||
cargo build --release
|
cargo build --release
|
||||||
|
|
||||||
# https://hub.docker.com/r/bitnami/minideb
|
# https://hub.docker.com/r/bitnami/minideb
|
||||||
FROM bitnami/minideb:latest
|
FROM docker.io/bitnami/minideb:latest
|
||||||
|
|
||||||
# microbin will be in /app
|
# microbin will be in /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
@ -25,12 +26,12 @@ COPY --from=build \
|
||||||
/etc/ssl/certs/ca-certificates.crt \
|
/etc/ssl/certs/ca-certificates.crt \
|
||||||
/etc/ssl/certs/ca-certificates.crt
|
/etc/ssl/certs/ca-certificates.crt
|
||||||
|
|
||||||
# copy built exacutable
|
# copy built executable
|
||||||
COPY --from=build \
|
COPY --from=build \
|
||||||
/app/target/release/microbin \
|
/app/target/release/karton \
|
||||||
/usr/bin/microbin
|
/usr/bin/karton
|
||||||
|
|
||||||
# Expose webport used for the webserver to the docker runtime
|
# Expose webport used for the webserver to the docker runtime
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
ENTRYPOINT ["microbin"]
|
ENTRYPOINT ["karton"]
|
||||||
|
|
70
README.MD
70
README.MD
|
@ -1,70 +0,0 @@
|
||||||
|
|
||||||
![Screenshot](.github/index.png)
|
|
||||||
|
|
||||||
# MicroBin
|
|
||||||
|
|
||||||
![Build](https://github.com/szabodanika/microbin/actions/workflows/rust.yml/badge.svg)
|
|
||||||
![crates.io](https://img.shields.io/crates/v/microbin.svg)
|
|
||||||
[![Docker Image](https://github.com/szabodanika/microbin/actions/workflows/docker.yml/badge.svg)](https://hub.docker.com/r/danielszabo99/microbin)
|
|
||||||
|
|
||||||
MicroBin is a super tiny, feature rich, configurable, self-contained and self-hosted paste bin web application. It is very easy to set up and use, and will only require a few megabytes of memory and disk storage. It takes only a couple minutes to set it up, why not give it a try now?
|
|
||||||
|
|
||||||
Install from Cargo:
|
|
||||||
|
|
||||||
`cargo install microbin`
|
|
||||||
|
|
||||||
And run with your custom configuration:
|
|
||||||
|
|
||||||
`microbin --port 8080 --public-path https://myserver.net --highlightsyntax --editable`
|
|
||||||
|
|
||||||
Or get the Docker image from [Dockerhub: danielszabo99/microbin](https://hub.docker.com/r/danielszabo99/microbin)
|
|
||||||
|
|
||||||
On our website [microbin.eu](microbin.eu) you will find the following:
|
|
||||||
|
|
||||||
- [Screenshots](https://microbin.eu/screenshots/)
|
|
||||||
- [Quickstart Guide](https://microbin.eu/quickstart/)
|
|
||||||
- [Documentation](https://microbin.eu/documentation/)
|
|
||||||
- [Donations and Sponsorhip](https://microbin.eu/donate/)
|
|
||||||
- [Community](https://microbin.eu/community/)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- Is very small
|
|
||||||
- Entirely self-contained executable, MicroBin is a single file!
|
|
||||||
- Animal names instead of random numbers for pasta identifiers (64 animals)
|
|
||||||
- File uploads (eg. server.com/file/pig-dog-cat)
|
|
||||||
- Raw text serving (eg. server.com/raw/pig-dog-cat)
|
|
||||||
- URL shortening and redirection
|
|
||||||
- QR code support
|
|
||||||
- Very simple database (JSON + files) for portability, easy backups and integration
|
|
||||||
- Listing and manually removing pastas (/pastalist)
|
|
||||||
- Private and public, editable and final, automatically and never expiring pastas
|
|
||||||
- Syntax highlighting
|
|
||||||
- Automatic dark mode and custom styling support with very little CSS and only vanilla JS (see [water.css](https://github.com/kognise/water.css))
|
|
||||||
- Most of the above can be toggled on and off!
|
|
||||||
|
|
||||||
### What is a "pasta" anyway?
|
|
||||||
|
|
||||||
In microbin, a pasta can be:
|
|
||||||
- A text that you want to paste from one machine to another, eg. some code,
|
|
||||||
- A file that you want to share, eg. a video that is too large for Discord, a zip with a code project in it or an image,
|
|
||||||
- A URL redirect.
|
|
||||||
|
|
||||||
### When is MicroBin useful?
|
|
||||||
|
|
||||||
You can use MicroBin
|
|
||||||
- As a URL shortener/redirect service,
|
|
||||||
- To send long texts to other people,
|
|
||||||
- To send large files to other people,
|
|
||||||
- To serve content on the web, eg. configuration files for testing, images, or any other file content using the Raw functionality,
|
|
||||||
- To move files between your desktop and a server you access from the console,
|
|
||||||
- As a "postbox" service where people can upload their files or texts, but they cannot see or remove what others sent you - just disable the pastalist page
|
|
||||||
- To take notes! Simply create an editable pasta.
|
|
||||||
|
|
||||||
...and many other things, why not get creative?
|
|
||||||
|
|
||||||
|
|
||||||
### License
|
|
||||||
|
|
||||||
MicroBin and MicroBin.eu are available under the BSD 3-Clause License.
|
|
||||||
|
|
||||||
© Dániel Szabó 2022
|
|
76
README.md
Normal file
76
README.md
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
# Karton
|
||||||
|
|
||||||
|
A small, rusty pastebin with URL shortener functionality.
|
||||||
|
|
||||||
|
The github repository is a mirror of [this gitlab repository](https://gitlab.com/obsidianical/microbin).
|
||||||
|
|
||||||
|
This is a fork of [MicroBin](https://github.com/szabodanika/microbin).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Animal names (by default) or custom namefiles instead of just hashes (though hashes are an option too!)
|
||||||
|
- File and image uploads
|
||||||
|
- raw text serving
|
||||||
|
- URL shortening
|
||||||
|
- QR codes
|
||||||
|
- Listing and removing pastas (though currently everyone can do that)
|
||||||
|
- Expiration times
|
||||||
|
- Editable pastas
|
||||||
|
- Syntax highlighting
|
||||||
|
- Styling via [water.css](https://github.com/kognise/water.css)
|
||||||
|
- Customizable endpoints
|
||||||
|
|
||||||
|
## Installation guide
|
||||||
|
|
||||||
|
Karton is available on [Docker hub](https://hub.docker.com/r/schrottkatze/karton), [crates.io](https://crates.io/crates/karton) and using the nix flake.
|
||||||
|
|
||||||
|
The only "officially supported" (I will actively debug and search for the problem) method is the last one using nix flakes.
|
||||||
|
|
||||||
|
### Installation via the nix flake
|
||||||
|
|
||||||
|
Add the repository to your inputs.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
karton.url = "git+https://gitlab.com/obsidianical/microbin.git";
|
||||||
|
```
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# microbin.nix
|
||||||
|
{ inputs, config, pkgs, ... }:
|
||||||
|
{
|
||||||
|
environment.systemPackages = [ inputs.karton.defaultPackage."x86_64-linux" ];
|
||||||
|
systemd.services.karton = {
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
environment = {
|
||||||
|
# set environment variables to configure karton
|
||||||
|
KARTON_HASH_IDS = "";
|
||||||
|
KARTON_EDITABLE = "";
|
||||||
|
KARTON_PRIVATE = "";
|
||||||
|
KARTON_HIGHLIGHTSYNTAX = "";
|
||||||
|
# adjust this to your domain
|
||||||
|
KARTON_PUBLIC_PATH = "https://example.org";
|
||||||
|
KARTON_QR = "";
|
||||||
|
# configure endpoints to be shorter
|
||||||
|
KARTON_URL_EP = "u";
|
||||||
|
KARTON_RAW_EP = "r";
|
||||||
|
KARTON_PASTA_EP = "p";
|
||||||
|
};
|
||||||
|
script = "${inputs.karton.defaultPackage."x86_64-linux"}/bin/karton";
|
||||||
|
# register a simple systemd service
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "simple";
|
||||||
|
RootDirectory="/";
|
||||||
|
WorkingDirectory = "/karton";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
This fork of MicroBin was created by [Schrottkatze](https://schrottkatze.de).
|
||||||
|
|
||||||
|
Join [the matrix room](https://matrix.to/#/#s10e-microbin:matrix.org) to chat!
|
||||||
|
|
||||||
|
Contact me via e-mail at [contact@schrottkatze.de](mailto:contact@schrottkatze.de).
|
12
SECURITY.md
12
SECURITY.md
|
@ -1,12 +0,0 @@
|
||||||
# Security Policy
|
|
||||||
|
|
||||||
## Version Support
|
|
||||||
|
|
||||||
Currently we only have capacity to support the latest version of MicroBin. We recommend that you always update to the newest one and check our pages regularly for announcements.
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
|
||||||
|
|
||||||
Security vulnerabilities can be reported directly to the developer/maintainer at d@szab.eu.
|
|
||||||
|
|
||||||
Sensitive information may be GPG encrypted with my public key available at
|
|
||||||
https://szab.eu/assets/files/daniel-szabo-pub.asc.
|
|
54
TODO.md
Normal file
54
TODO.md
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
# TODO lists
|
||||||
|
|
||||||
|
these are just rough guides tho
|
||||||
|
|
||||||
|
## v2.1
|
||||||
|
|
||||||
|
- [ ] customizable endpoints
|
||||||
|
- [ ] create
|
||||||
|
- [ ] edit
|
||||||
|
- [ ] info
|
||||||
|
- [ ] get pastas
|
||||||
|
- [ ] remove
|
||||||
|
- [ ] improve remove endpoint
|
||||||
|
- [ ] disable it
|
||||||
|
- [ ] client library
|
||||||
|
- [ ] request .well-known data
|
||||||
|
- [ ] support most endpoints
|
||||||
|
- [ ] karton cli
|
||||||
|
|
||||||
|
## v3.0
|
||||||
|
|
||||||
|
- [ ] internal rewrite & docs
|
||||||
|
- [ ] design new frontend
|
||||||
|
- [ ] switch to yew
|
||||||
|
- [ ] using client lib
|
||||||
|
- [ ] theme and general config files
|
||||||
|
- [ ] unified theme format
|
||||||
|
- [ ] no env configs anymore if possible
|
||||||
|
- [ ] proper dbs
|
||||||
|
- [ ] sqlite
|
||||||
|
- [ ] postgres
|
||||||
|
- [ ] apis/endpoints
|
||||||
|
- [ ] IDs, name IDs AND user/pastaname
|
||||||
|
- [ ] root (and admin) user for root level pastas
|
||||||
|
- [ ] status/instance health admin dashboard and ap
|
||||||
|
- [ ] storage
|
||||||
|
- [ ] db status
|
||||||
|
- [ ] how up to date
|
||||||
|
- [ ] stats (users etc)
|
||||||
|
- [ ] errors
|
||||||
|
- [ ] loading speeds, performance monitor?
|
||||||
|
- [ ] memory use
|
||||||
|
- [ ] auth
|
||||||
|
- [ ] general auth
|
||||||
|
- [ ] oidc
|
||||||
|
- [ ] permssion system & api keys
|
||||||
|
- [ ] only allow some other users to open pasta
|
||||||
|
- [ ] access control and editing
|
||||||
|
- [ ] password protected pastas too
|
||||||
|
- [ ] features for pastas
|
||||||
|
- [ ] pw protection
|
||||||
|
- [ ] better editor
|
||||||
|
- [ ] markdown pastas
|
||||||
|
- [ ] optional, opt-in commenting
|
7
default.nix
Normal file
7
default.nix
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
(import (
|
||||||
|
fetchTarball {
|
||||||
|
url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz";
|
||||||
|
sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; }
|
||||||
|
) {
|
||||||
|
src = ./.;
|
||||||
|
}).defaultNix
|
77
flake.lock
Normal file
77
flake.lock
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"naersk": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1671096816,
|
||||||
|
"narHash": "sha256-ezQCsNgmpUHdZANDCILm3RvtO1xH8uujk/+EqNvzIOg=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "naersk",
|
||||||
|
"rev": "d998160d6a076cfe8f9741e56aeec7e267e3e114",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"ref": "master",
|
||||||
|
"repo": "naersk",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1677852945,
|
||||||
|
"narHash": "sha256-liiVJjkBTuBTAkRW3hrI8MbPD2ImYzwUpa7kvteiKhM=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "f5ffd5787786dde3a8bf648c7a1b5f78c4e01abb",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1677852945,
|
||||||
|
"narHash": "sha256-liiVJjkBTuBTAkRW3hrI8MbPD2ImYzwUpa7kvteiKhM=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "f5ffd5787786dde3a8bf648c7a1b5f78c4e01abb",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"naersk": "naersk",
|
||||||
|
"nixpkgs": "nixpkgs_2",
|
||||||
|
"utils": "utils"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1676283394,
|
||||||
|
"narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
21
flake.nix
Normal file
21
flake.nix
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
naersk.url = "github:nix-community/naersk/master";
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, utils, naersk }:
|
||||||
|
utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
naersk-lib = pkgs.callPackage naersk { };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
defaultPackage = naersk-lib.buildPackage ./.;
|
||||||
|
devShell = with pkgs; mkShell {
|
||||||
|
buildInputs = [ cargo rustc rustfmt pre-commit rustPackages.clippy cargo-watch podman ];
|
||||||
|
RUST_SRC_PATH = rustPlatform.rustLibSrc;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,9 +0,0 @@
|
||||||
services:
|
|
||||||
- type: web
|
|
||||||
name: microbin
|
|
||||||
plan: free
|
|
||||||
numInstances: 1
|
|
||||||
env: rust
|
|
||||||
repo: https://github.com/szabodanika/microbin.git
|
|
||||||
buildCommand: cargo build --release
|
|
||||||
startCommand: ./target/release/microbin --editable --highlightsyntax
|
|
7
shell.nix
Normal file
7
shell.nix
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
(import (
|
||||||
|
fetchTarball {
|
||||||
|
url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz";
|
||||||
|
sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; }
|
||||||
|
) {
|
||||||
|
src = ./.;
|
||||||
|
}).shellNix
|
135
src/args.rs
135
src/args.rs
|
@ -3,6 +3,7 @@ use lazy_static::lazy_static;
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
@ -12,86 +13,138 @@ lazy_static! {
|
||||||
#[derive(Parser, Debug, Clone)]
|
#[derive(Parser, Debug, Clone)]
|
||||||
#[clap(author, version, about, long_about = None)]
|
#[clap(author, version, about, long_about = None)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
#[clap(long, env = "MICROBIN_AUTH_USERNAME")]
|
/// The username for basic HTTP auth.
|
||||||
|
/// If unset, HTTP authentication stays disabled.
|
||||||
|
///
|
||||||
|
/// WARNING: people opening pastas will have to authenticate too.
|
||||||
|
#[clap(long, env = "KARTON_AUTH_USERNAME")]
|
||||||
pub auth_username: Option<String>,
|
pub auth_username: Option<String>,
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_AUTH_PASSWORD")]
|
/// Set a password for HTTP authentication.
|
||||||
|
/// If unset, HTTP authentication will not require a password.
|
||||||
|
/// If `auth_username` is unset, this option will not have any effect.
|
||||||
|
#[clap(long, env = "KARTON_AUTH_PASSWORD")]
|
||||||
pub auth_password: Option<String>,
|
pub auth_password: Option<String>,
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_EDITABLE")]
|
/// Enable the option to make pastas editable.
|
||||||
|
#[clap(long, env = "KARTON_EDITABLE")]
|
||||||
pub editable: bool,
|
pub editable: bool,
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_FOOTER_TEXT")]
|
/// The text displayed in the browser navigation bar.
|
||||||
|
#[clap(long, env = "KARTON_TITLE", default_value = " Karton")]
|
||||||
|
pub title: String,
|
||||||
|
|
||||||
|
/// The web interfaces' footer text.
|
||||||
|
#[clap(long, env = "KARTON_FOOTER_TEXT")]
|
||||||
pub footer_text: Option<String>,
|
pub footer_text: Option<String>,
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_HIDE_FOOTER")]
|
/// Hide the footer of the web interface.
|
||||||
|
#[clap(long, env = "KARTON_HIDE_FOOTER")]
|
||||||
pub hide_footer: bool,
|
pub hide_footer: bool,
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_HIDE_HEADER")]
|
/// Hide the header of the web interface.
|
||||||
|
#[clap(long, env = "KARTON_HIDE_HEADER")]
|
||||||
pub hide_header: bool,
|
pub hide_header: bool,
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_HIDE_LOGO")]
|
/// Hide the logo in the header.
|
||||||
|
#[clap(long, env = "KARTON_HIDE_LOGO")]
|
||||||
pub hide_logo: bool,
|
pub hide_logo: bool,
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_NO_LISTING")]
|
/// Disable the listing page.
|
||||||
|
#[clap(long, env = "KARTON_NO_LISTING")]
|
||||||
pub no_listing: bool,
|
pub no_listing: bool,
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_HIGHLIGHTSYNTAX")]
|
/// Enable syntax highlighting in pastas.
|
||||||
|
#[clap(long, env = "KARTON_HIGHLIGHTSYNTAX")]
|
||||||
pub highlightsyntax: bool,
|
pub highlightsyntax: bool,
|
||||||
|
|
||||||
#[clap(short, long, env = "MICROBIN_PORT", default_value_t = 8080)]
|
/// The port to which to bind the server.
|
||||||
|
#[clap(short, long, env = "KARTON_PORT", default_value_t = 8080)]
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
|
|
||||||
#[clap(short, long, env="MICROBIN_BIND", default_value_t = IpAddr::from([0, 0, 0, 0]))]
|
/// The IP adress to bind the server to.
|
||||||
|
#[clap(short, long, env="KARTON_BIND", default_value_t = IpAddr::from([0, 0, 0, 0]))]
|
||||||
pub bind: IpAddr,
|
pub bind: IpAddr,
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_PRIVATE")]
|
/// Enable the option to create private pastas.
|
||||||
|
#[clap(long, env = "KARTON_PRIVATE")]
|
||||||
pub private: bool,
|
pub private: bool,
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_PURE_HTML")]
|
/// Disables most css, apart form some inline styles.
|
||||||
|
#[clap(long, env = "KARTON_PURE_HTML")]
|
||||||
pub pure_html: bool,
|
pub pure_html: bool,
|
||||||
|
|
||||||
#[clap(long, env="MICROBIN_PUBLIC_PATH", default_value_t = PublicUrl(String::from("")))]
|
/// The servers public path, making it possible to run Karton behind a reverse proxy subpath.
|
||||||
|
#[clap(long, env="KARTON_PUBLIC_PATH", default_value_t = PublicUrl(String::from("")))]
|
||||||
pub public_path: PublicUrl,
|
pub public_path: PublicUrl,
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_READONLY")]
|
/// Enable creation of QR codes of pastas. Requires `public_path` to be set.
|
||||||
pub readonly: bool,
|
#[clap(long, env = "KARTON_QR")]
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_TITLE")]
|
|
||||||
pub title: Option<String>,
|
|
||||||
|
|
||||||
#[clap(short, long, env = "MICROBIN_THREADS", default_value_t = 1)]
|
|
||||||
pub threads: u8,
|
|
||||||
|
|
||||||
#[clap(short, long, env = "MICROBIN_GC_DAYS", default_value_t = 90)]
|
|
||||||
pub gc_days: u16,
|
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_ENABLE_BURN_AFTER")]
|
|
||||||
pub enable_burn_after: bool,
|
|
||||||
|
|
||||||
#[clap(short, long, env = "MICROBIN_DEFAULT_BURN_AFTER", default_value_t = 0)]
|
|
||||||
pub default_burn_after: u16,
|
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_WIDE")]
|
|
||||||
pub wide: bool,
|
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_QR")]
|
|
||||||
pub qr: bool,
|
pub qr: bool,
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_NO_ETERNAL_PASTA")]
|
|
||||||
|
/// Disable adding/removing/editing pastas.
|
||||||
|
#[clap(long, env = "KARTON_READONLY")]
|
||||||
|
pub readonly: bool,
|
||||||
|
|
||||||
|
/// The amount of worker threads that the server is allowed to have.
|
||||||
|
#[clap(short, long, env = "KARTON_THREADS", default_value_t = 1)]
|
||||||
|
pub threads: u8,
|
||||||
|
|
||||||
|
/// Sets a time value for the garbage collector. Pastas that aren't accessed for the given
|
||||||
|
/// amount of days will be deleted. Set to 0 to disable garbage collection.
|
||||||
|
#[clap(short, long, env = "KARTON_GC_DAYS", default_value_t = 90)]
|
||||||
|
pub gc_days: u16,
|
||||||
|
|
||||||
|
/// Enable the option to delete after a given amount of reads.
|
||||||
|
#[clap(long, env = "KARTON_ENABLE_BURN_AFTER")]
|
||||||
|
pub enable_burn_after: bool,
|
||||||
|
|
||||||
|
/// The default amount of reads for the self-delete mechanism.
|
||||||
|
#[clap(short, long, env = "KARTON_DEFAULT_BURN_AFTER", default_value_t = 0)]
|
||||||
|
pub default_burn_after: u16,
|
||||||
|
|
||||||
|
/// Changes the UIs maximum width from 720 pixels to 1080.
|
||||||
|
#[clap(long, env = "KARTON_WIDE")]
|
||||||
|
pub wide: bool,
|
||||||
|
|
||||||
|
/// Disable "Never" expiry setting.
|
||||||
|
#[clap(long, env = "KARTON_NO_ETERNAL_PASTA")]
|
||||||
pub no_eternal_pasta: bool,
|
pub no_eternal_pasta: bool,
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_DEFAULT_EXPIRY", default_value = "24hour")]
|
/// Set the default expiry time value.
|
||||||
|
#[clap(long, env = "KARTON_DEFAULT_EXPIRY", default_value = "24hour")]
|
||||||
pub default_expiry: String,
|
pub default_expiry: String,
|
||||||
|
|
||||||
#[clap(short, long, env = "MICROBIN_NO_FILE_UPLOAD")]
|
/// Disable file uploading.
|
||||||
|
#[clap(short, long, env = "KARTON_NO_FILE_UPLOAD")]
|
||||||
pub no_file_upload: bool,
|
pub no_file_upload: bool,
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_CUSTOM_CSS")]
|
// TODO: replace with simple path.
|
||||||
|
/// Replace built-in CSS file with a CSS file provided by the linked URL.
|
||||||
|
#[clap(long, env = "KARTON_CUSTOM_CSS")]
|
||||||
pub custom_css: Option<String>,
|
pub custom_css: Option<String>,
|
||||||
|
|
||||||
#[clap(long, env = "MICROBIN_HASH_IDS")]
|
/// Replace built-in animal names file with custom names file for pasta links.
|
||||||
|
/// The file must be newline seperated.
|
||||||
|
#[clap(long, env = "KARTON_CUSTOM_NAMES")]
|
||||||
|
pub custom_names: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Enable the use of Hash IDs for shorter URLs instead of animal names.
|
||||||
|
#[clap(long, env = "KARTON_HASH_IDS")]
|
||||||
pub hash_ids: bool,
|
pub hash_ids: bool,
|
||||||
|
|
||||||
|
/// Endpoint for /url/
|
||||||
|
#[clap(long, env = "KARTON_URL_EP", default_value = "url" )]
|
||||||
|
pub url_endpoint: String,
|
||||||
|
|
||||||
|
/// Endpoint for /pasta/
|
||||||
|
#[clap(long, env = "KARTON_PASTA_EP", default_value = "pasta" )]
|
||||||
|
pub pasta_endpoint: String,
|
||||||
|
|
||||||
|
/// Endpoint for /raw/
|
||||||
|
#[clap(long, env = "KARTON_RAW_EP", default_value = "raw" )]
|
||||||
|
pub raw_endpoint: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use crate::dbio::save_to_file;
|
use crate::dbio::save_to_file;
|
||||||
use crate::pasta::PastaFile;
|
use crate::pasta::PastaFile;
|
||||||
use crate::util::animalnumbers::to_animal_names;
|
|
||||||
use crate::util::hashids::to_hashids;
|
use crate::util::hashids::to_hashids;
|
||||||
use crate::util::misc::is_valid_url;
|
use crate::util::misc::is_valid_url;
|
||||||
|
use crate::util::pasta_id_converter::CONVERTER;
|
||||||
use crate::{AppState, Pasta, ARGS};
|
use crate::{AppState, Pasta, ARGS};
|
||||||
use actix_multipart::Multipart;
|
use actix_multipart::Multipart;
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
|
use actix_web::web::{BytesMut, BufMut};
|
||||||
use actix_web::{get, web, Error, HttpResponse, Responder};
|
use actix_web::{get, web, Error, HttpResponse, Responder};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use bytesize::ByteSize;
|
use bytesize::ByteSize;
|
||||||
|
@ -14,6 +16,8 @@ use rand::Rng;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use super::errors::ErrorTemplate;
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "index.html")]
|
#[template(path = "index.html")]
|
||||||
struct IndexTemplate<'a> {
|
struct IndexTemplate<'a> {
|
||||||
|
@ -27,6 +31,7 @@ pub async fn index() -> impl Responder {
|
||||||
.body(IndexTemplate { args: &ARGS }.render().unwrap())
|
.body(IndexTemplate { args: &ARGS }.render().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pasta creation endpoint.
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
data: web::Data<AppState>,
|
data: web::Data<AppState>,
|
||||||
mut payload: Multipart,
|
mut payload: Multipart,
|
||||||
|
@ -37,7 +42,7 @@ pub async fn create(
|
||||||
.finish());
|
.finish());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut pastas = data.pastas.lock().unwrap();
|
let mut pastas = data.pastas.lock().await;
|
||||||
|
|
||||||
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||||
Ok(n) => n.as_secs(),
|
Ok(n) => n.as_secs(),
|
||||||
|
@ -65,22 +70,20 @@ pub async fn create(
|
||||||
while let Some(mut field) = payload.try_next().await? {
|
while let Some(mut field) = payload.try_next().await? {
|
||||||
match field.name() {
|
match field.name() {
|
||||||
"editable" => {
|
"editable" => {
|
||||||
// while let Some(_chunk) = field.try_next().await? {}
|
|
||||||
new_pasta.editable = true;
|
new_pasta.editable = true;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
"private" => {
|
"private" => {
|
||||||
// while let Some(_chunk) = field.try_next().await? {}
|
|
||||||
new_pasta.private = true;
|
new_pasta.private = true;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
"expiration" => {
|
"expiration" => {
|
||||||
while let Some(chunk) = field.try_next().await? {
|
while let Some(chunk) = field.try_next().await? {
|
||||||
new_pasta.expiration = match std::str::from_utf8(&chunk).unwrap() {
|
new_pasta.expiration = match std::str::from_utf8(&chunk).unwrap() {
|
||||||
|
// TODO: customizable times
|
||||||
"1min" => timenow + 60,
|
"1min" => timenow + 60,
|
||||||
"10min" => timenow + 60 * 10,
|
"10min" => timenow + 60 * 10,
|
||||||
"1hour" => timenow + 60 * 60,
|
"1hour" => timenow + 60 * 60,
|
||||||
"24hour" => timenow + 60 * 60 * 24,
|
"24hour" => timenow + 60 * 60 * 24,
|
||||||
|
"3days" => timenow + 60 * 60 * 24 * 3,
|
||||||
"1week" => timenow + 60 * 60 * 24 * 7,
|
"1week" => timenow + 60 * 60 * 24 * 7,
|
||||||
"never" => {
|
"never" => {
|
||||||
if ARGS.no_eternal_pasta {
|
if ARGS.no_eternal_pasta {
|
||||||
|
@ -95,12 +98,12 @@ pub async fn create(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
"burn_after" => {
|
"burn_after" => {
|
||||||
while let Some(chunk) = field.try_next().await? {
|
while let Some(chunk) = field.try_next().await? {
|
||||||
new_pasta.burn_after_reads = match std::str::from_utf8(&chunk).unwrap() {
|
new_pasta.burn_after_reads = match std::str::from_utf8(&chunk).unwrap() {
|
||||||
|
// TODO: also make customizable
|
||||||
|
// maybe options in config files, with defaults
|
||||||
// give an extra read because the user will be redirected to the pasta page automatically
|
// give an extra read because the user will be redirected to the pasta page automatically
|
||||||
"1" => 2,
|
"1" => 2,
|
||||||
"10" => 10,
|
"10" => 10,
|
||||||
|
@ -114,16 +117,22 @@ pub async fn create(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
"content" => {
|
"content" => {
|
||||||
let mut content = String::from("");
|
let mut content = BytesMut::new();
|
||||||
while let Some(chunk) = field.try_next().await? {
|
while let Some(chunk) = field.try_next().await? {
|
||||||
content.push_str(std::str::from_utf8(&chunk).unwrap().to_string().as_str());
|
content.put(chunk);
|
||||||
}
|
}
|
||||||
if content.len() > 0 {
|
if !content.is_empty() {
|
||||||
new_pasta.content = content;
|
new_pasta.content = match String::from_utf8(content.to_vec()) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => return Ok(HttpResponse::BadRequest()
|
||||||
|
.content_type("text/html")
|
||||||
|
.body(ErrorTemplate {
|
||||||
|
status_code: StatusCode::BAD_REQUEST,
|
||||||
|
args: &ARGS
|
||||||
|
}.render().unwrap())),
|
||||||
|
};
|
||||||
|
|
||||||
new_pasta.pasta_type = if is_valid_url(new_pasta.content.as_str()) {
|
new_pasta.pasta_type = if is_valid_url(new_pasta.content.as_str()) {
|
||||||
String::from("url")
|
String::from("url")
|
||||||
|
@ -131,13 +140,11 @@ pub async fn create(
|
||||||
String::from("text")
|
String::from("text")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
"syntax-highlight" => {
|
"syntax-highlight" => {
|
||||||
while let Some(chunk) = field.try_next().await? {
|
while let Some(chunk) = field.try_next().await? {
|
||||||
new_pasta.extension = std::str::from_utf8(&chunk).unwrap().to_string();
|
new_pasta.extension = std::str::from_utf8(&chunk).unwrap().to_string();
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
"file" => {
|
"file" => {
|
||||||
if ARGS.no_file_upload {
|
if ARGS.no_file_upload {
|
||||||
|
@ -152,7 +159,7 @@ pub async fn create(
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut file = match PastaFile::from_unsanitized(&path) {
|
let mut file = match PastaFile::from_unsanitized(path) {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Unsafe file name: {e:?}");
|
warn!("Unsafe file name: {e:?}");
|
||||||
|
@ -199,9 +206,9 @@ pub async fn create(
|
||||||
let slug = if ARGS.hash_ids {
|
let slug = if ARGS.hash_ids {
|
||||||
to_hashids(id)
|
to_hashids(id)
|
||||||
} else {
|
} else {
|
||||||
to_animal_names(id)
|
CONVERTER.to_names(id)
|
||||||
};
|
};
|
||||||
Ok(HttpResponse::Found()
|
Ok(HttpResponse::Found()
|
||||||
.append_header(("Location", format!("{}/pasta/{}", ARGS.public_path, slug)))
|
.append_header(("Location", format!("{}/{}/{}", ARGS.public_path, ARGS.pasta_endpoint, slug)))
|
||||||
.finish())
|
.finish())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use crate::args::Args;
|
use crate::args::Args;
|
||||||
use crate::dbio::save_to_file;
|
use crate::dbio::save_to_file;
|
||||||
use crate::endpoints::errors::ErrorTemplate;
|
use crate::endpoints::errors::ErrorTemplate;
|
||||||
use crate::util::animalnumbers::to_u64;
|
|
||||||
use crate::util::hashids::to_u64 as hashid_to_u64;
|
use crate::util::hashids::to_u64 as hashid_to_u64;
|
||||||
use crate::util::misc::remove_expired;
|
use crate::util::misc::remove_expired;
|
||||||
|
use crate::util::pasta_id_converter::CONVERTER;
|
||||||
use crate::{AppState, Pasta, ARGS};
|
use crate::{AppState, Pasta, ARGS};
|
||||||
use actix_multipart::Multipart;
|
use actix_multipart::Multipart;
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
use actix_web::{get, post, web, Error, HttpResponse};
|
use actix_web::{get, post, web, Error, HttpResponse};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
|
@ -19,12 +20,12 @@ struct EditTemplate<'a> {
|
||||||
|
|
||||||
#[get("/edit/{id}")]
|
#[get("/edit/{id}")]
|
||||||
pub async fn get_edit(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
pub async fn get_edit(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||||
let mut pastas = data.pastas.lock().unwrap();
|
let mut pastas = data.pastas.lock().await;
|
||||||
|
|
||||||
let id = if ARGS.hash_ids {
|
let id = if ARGS.hash_ids {
|
||||||
hashid_to_u64(&*id).unwrap_or(0)
|
hashid_to_u64(&id).unwrap_or(0)
|
||||||
} else {
|
} else {
|
||||||
to_u64(&*id.into_inner()).unwrap_or(0)
|
CONVERTER.to_u64(&id.into_inner()).unwrap_or(0)
|
||||||
};
|
};
|
||||||
|
|
||||||
remove_expired(&mut pastas);
|
remove_expired(&mut pastas);
|
||||||
|
@ -36,20 +37,18 @@ pub async fn get_edit(data: web::Data<AppState>, id: web::Path<String>) -> HttpR
|
||||||
.append_header(("Location", format!("{}/", ARGS.public_path)))
|
.append_header(("Location", format!("{}/", ARGS.public_path)))
|
||||||
.finish();
|
.finish();
|
||||||
}
|
}
|
||||||
return HttpResponse::Ok().content_type("text/html").body(
|
return HttpResponse::Ok()
|
||||||
EditTemplate {
|
.content_type("text/html")
|
||||||
pasta: &pasta,
|
.body(EditTemplate { pasta, args: &ARGS }.render().unwrap());
|
||||||
args: &ARGS,
|
|
||||||
}
|
|
||||||
.render()
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpResponse::Ok()
|
HttpResponse::NotFound()
|
||||||
.content_type("text/html")
|
.content_type("text/html")
|
||||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
.body(ErrorTemplate {
|
||||||
|
status_code: StatusCode::NOT_FOUND,
|
||||||
|
args: &ARGS
|
||||||
|
}.render().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/edit/{id}")]
|
#[post("/edit/{id}")]
|
||||||
|
@ -65,32 +64,29 @@ pub async fn post_edit(
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = if ARGS.hash_ids {
|
let id = if ARGS.hash_ids {
|
||||||
hashid_to_u64(&*id).unwrap_or(0)
|
hashid_to_u64(&id).unwrap_or(0)
|
||||||
} else {
|
} else {
|
||||||
to_u64(&*id.into_inner()).unwrap_or(0)
|
CONVERTER.to_u64(&id.into_inner()).unwrap_or(0)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut pastas = data.pastas.lock().unwrap();
|
let mut pastas = data.pastas.lock().await;
|
||||||
|
|
||||||
remove_expired(&mut pastas);
|
remove_expired(&mut pastas);
|
||||||
|
|
||||||
let mut new_content = String::from("");
|
let mut new_content = String::from("");
|
||||||
|
|
||||||
while let Some(mut field) = payload.try_next().await? {
|
while let Some(mut field) = payload.try_next().await? {
|
||||||
match field.name() {
|
if field.name() == "content" {
|
||||||
"content" => {
|
while let Some(chunk) = field.try_next().await? {
|
||||||
while let Some(chunk) = field.try_next().await? {
|
new_content = std::str::from_utf8(&chunk).unwrap().to_string();
|
||||||
new_content = std::str::from_utf8(&chunk).unwrap().to_string();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i, pasta) in pastas.iter().enumerate() {
|
for (i, pasta) in pastas.iter().enumerate() {
|
||||||
if pasta.id == id {
|
if pasta.id == id {
|
||||||
if pasta.editable {
|
if pasta.editable {
|
||||||
pastas[i].content.replace_range(.., &*new_content);
|
pastas[i].content.replace_range(.., &new_content);
|
||||||
save_to_file(&pastas);
|
save_to_file(&pastas);
|
||||||
|
|
||||||
return Ok(HttpResponse::Found()
|
return Ok(HttpResponse::Found()
|
||||||
|
@ -105,7 +101,10 @@ pub async fn post_edit(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::NotFound()
|
||||||
.content_type("text/html")
|
.content_type("text/html")
|
||||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap()))
|
.body(ErrorTemplate {
|
||||||
|
status_code: StatusCode::NOT_FOUND,
|
||||||
|
args: &ARGS
|
||||||
|
}.render().unwrap()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use actix_web::{Error, HttpResponse};
|
use actix_web::{Error, HttpResponse, http::StatusCode};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
|
||||||
use crate::args::{Args, ARGS};
|
use crate::args::{Args, ARGS};
|
||||||
|
@ -6,11 +6,15 @@ use crate::args::{Args, ARGS};
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "error.html")]
|
#[template(path = "error.html")]
|
||||||
pub struct ErrorTemplate<'a> {
|
pub struct ErrorTemplate<'a> {
|
||||||
|
pub status_code: StatusCode,
|
||||||
pub args: &'a Args,
|
pub args: &'a Args,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn not_found() -> Result<HttpResponse, Error> {
|
pub async fn not_found() -> Result<HttpResponse, Error> {
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::NotFound()
|
||||||
.content_type("text/html")
|
.content_type("text/html")
|
||||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap()))
|
.body(ErrorTemplate {
|
||||||
|
status_code: StatusCode::NOT_FOUND,
|
||||||
|
args: &ARGS
|
||||||
|
}.render().unwrap()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,17 +9,22 @@ use askama::Template;
|
||||||
struct Info<'a> {
|
struct Info<'a> {
|
||||||
args: &'a Args,
|
args: &'a Args,
|
||||||
pastas: &'a Vec<Pasta>,
|
pastas: &'a Vec<Pasta>,
|
||||||
status: &'a String,
|
status: &'a str,
|
||||||
version_string: &'a String,
|
version_string: &'a str,
|
||||||
message: &'a String,
|
message: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Endpoint to get information about the instance.
|
||||||
#[get("/info")]
|
#[get("/info")]
|
||||||
pub async fn info(data: web::Data<AppState>) -> HttpResponse {
|
pub async fn info(data: web::Data<AppState>) -> HttpResponse {
|
||||||
// get access to the pasta collection
|
// get access to the pasta collection
|
||||||
let mut pastas = data.pastas.lock().unwrap();
|
let pastas = data.pastas.lock().await;
|
||||||
|
|
||||||
// todo status report more sophisticated
|
// TODO: status report more sophisticated
|
||||||
|
// maybe:
|
||||||
|
// - detect weird/invalid configurations?
|
||||||
|
// - detect server storage issues
|
||||||
|
// - detect performance problems?
|
||||||
let mut status = "OK";
|
let mut status = "OK";
|
||||||
let mut message = "";
|
let mut message = "";
|
||||||
|
|
||||||
|
@ -32,9 +37,9 @@ pub async fn info(data: web::Data<AppState>) -> HttpResponse {
|
||||||
Info {
|
Info {
|
||||||
args: &ARGS,
|
args: &ARGS,
|
||||||
pastas: &pastas,
|
pastas: &pastas,
|
||||||
status: &String::from(status),
|
status,
|
||||||
version_string: &String::from("1.2.0-20221107"),
|
version_string: env!("CARGO_PKG_VERSION"),
|
||||||
message: &String::from(message),
|
message
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
|
|
@ -2,12 +2,13 @@ use crate::args::{Args, ARGS};
|
||||||
use crate::dbio::save_to_file;
|
use crate::dbio::save_to_file;
|
||||||
use crate::endpoints::errors::ErrorTemplate;
|
use crate::endpoints::errors::ErrorTemplate;
|
||||||
use crate::pasta::Pasta;
|
use crate::pasta::Pasta;
|
||||||
use crate::util::animalnumbers::to_u64;
|
|
||||||
use crate::util::hashids::to_u64 as hashid_to_u64;
|
use crate::util::hashids::to_u64 as hashid_to_u64;
|
||||||
use crate::util::misc::remove_expired;
|
use crate::util::misc::remove_expired;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use actix_web::rt::time;
|
use crate::util::pasta_id_converter::CONVERTER;
|
||||||
use actix_web::{get, web, HttpResponse};
|
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
|
use actix_web::{web, HttpResponse};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
@ -18,15 +19,15 @@ struct PastaTemplate<'a> {
|
||||||
args: &'a Args,
|
args: &'a Args,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/pasta/{id}")]
|
/// Endpoint to view a pasta.
|
||||||
pub async fn getpasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
pub async fn getpasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||||
// get access to the pasta collection
|
// get access to the pasta collection
|
||||||
let mut pastas = data.pastas.lock().unwrap();
|
let mut pastas = data.pastas.lock().await;
|
||||||
|
|
||||||
let id = if ARGS.hash_ids {
|
let id = if ARGS.hash_ids {
|
||||||
hashid_to_u64(&*id).unwrap_or(0)
|
hashid_to_u64(&id).unwrap_or(0)
|
||||||
} else {
|
} else {
|
||||||
to_u64(&*id.into_inner()).unwrap_or(0)
|
CONVERTER.to_u64(&id.into_inner()).unwrap_or(0)
|
||||||
};
|
};
|
||||||
|
|
||||||
// remove expired pastas (including this one if needed)
|
// remove expired pastas (including this one if needed)
|
||||||
|
@ -45,7 +46,7 @@ pub async fn getpasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpR
|
||||||
|
|
||||||
if found {
|
if found {
|
||||||
// increment read count
|
// increment read count
|
||||||
pastas[index].read_count = pastas[index].read_count + 1;
|
pastas[index].read_count += 1;
|
||||||
|
|
||||||
// save the updated read count
|
// save the updated read count
|
||||||
save_to_file(&pastas);
|
save_to_file(&pastas);
|
||||||
|
@ -77,20 +78,23 @@ pub async fn getpasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpR
|
||||||
|
|
||||||
// otherwise
|
// otherwise
|
||||||
// send pasta not found error
|
// send pasta not found error
|
||||||
HttpResponse::Ok()
|
HttpResponse::NotFound()
|
||||||
.content_type("text/html")
|
.content_type("text/html")
|
||||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
.body(ErrorTemplate {
|
||||||
|
status_code: StatusCode::NOT_FOUND,
|
||||||
|
args: &ARGS
|
||||||
|
}.render().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/url/{id}")]
|
/// Endpoint for redirection.
|
||||||
pub async fn redirecturl(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
pub async fn redirecturl(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||||
// get access to the pasta collection
|
// get access to the pasta collection
|
||||||
let mut pastas = data.pastas.lock().unwrap();
|
let mut pastas = data.pastas.lock().await;
|
||||||
|
|
||||||
let id = if ARGS.hash_ids {
|
let id = if ARGS.hash_ids {
|
||||||
hashid_to_u64(&*id).unwrap_or(0)
|
hashid_to_u64(&id).unwrap_or(0)
|
||||||
} else {
|
} else {
|
||||||
to_u64(&*id.into_inner()).unwrap_or(0)
|
CONVERTER.to_u64(&id.into_inner()).unwrap_or(0)
|
||||||
};
|
};
|
||||||
|
|
||||||
// remove expired pastas (including this one if needed)
|
// remove expired pastas (including this one if needed)
|
||||||
|
@ -110,7 +114,7 @@ pub async fn redirecturl(data: web::Data<AppState>, id: web::Path<String>) -> Ht
|
||||||
|
|
||||||
if found {
|
if found {
|
||||||
// increment read count
|
// increment read count
|
||||||
pastas[index].read_count = pastas[index].read_count + 1;
|
pastas[index].read_count += 1;
|
||||||
|
|
||||||
// save the updated read count
|
// save the updated read count
|
||||||
save_to_file(&pastas);
|
save_to_file(&pastas);
|
||||||
|
@ -136,28 +140,34 @@ pub async fn redirecturl(data: web::Data<AppState>, id: web::Path<String>) -> Ht
|
||||||
return response;
|
return response;
|
||||||
// send error if we're trying to open a non-url pasta as a redirect
|
// send error if we're trying to open a non-url pasta as a redirect
|
||||||
} else {
|
} else {
|
||||||
HttpResponse::Ok()
|
HttpResponse::NotFound()
|
||||||
.content_type("text/html")
|
.content_type("text/html")
|
||||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap());
|
.body(ErrorTemplate {
|
||||||
|
status_code: StatusCode::NOT_FOUND,
|
||||||
|
args: &ARGS
|
||||||
|
}.render().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise
|
// otherwise
|
||||||
// send pasta not found error
|
// send pasta not found error
|
||||||
HttpResponse::Ok()
|
HttpResponse::NotFound()
|
||||||
.content_type("text/html")
|
.content_type("text/html")
|
||||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
.body(ErrorTemplate {
|
||||||
|
status_code: StatusCode::NOT_FOUND,
|
||||||
|
args: &ARGS
|
||||||
|
}.render().unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/raw/{id}")]
|
/// Endpoint to request pasta as raw file.
|
||||||
pub async fn getrawpasta(data: web::Data<AppState>, id: web::Path<String>) -> String {
|
pub async fn getrawpasta(data: web::Data<AppState>, id: web::Path<String>) -> String {
|
||||||
// get access to the pasta collection
|
// get access to the pasta collection
|
||||||
let mut pastas = data.pastas.lock().unwrap();
|
let mut pastas = data.pastas.lock().await;
|
||||||
|
|
||||||
let id = if ARGS.hash_ids {
|
let id = if ARGS.hash_ids {
|
||||||
hashid_to_u64(&*id).unwrap_or(0)
|
hashid_to_u64(&id).unwrap_or(0)
|
||||||
} else {
|
} else {
|
||||||
to_u64(&*id.into_inner()).unwrap_or(0)
|
CONVERTER.to_u64(&id.into_inner()).unwrap_or(0)
|
||||||
};
|
};
|
||||||
|
|
||||||
// remove expired pastas (including this one if needed)
|
// remove expired pastas (including this one if needed)
|
||||||
|
@ -176,7 +186,7 @@ pub async fn getrawpasta(data: web::Data<AppState>, id: web::Path<String>) -> St
|
||||||
|
|
||||||
if found {
|
if found {
|
||||||
// increment read count
|
// increment read count
|
||||||
pastas[index].read_count = pastas[index].read_count + 1;
|
pastas[index].read_count += 1;
|
||||||
|
|
||||||
// get current unix time in seconds
|
// get current unix time in seconds
|
||||||
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||||
|
|
|
@ -13,6 +13,7 @@ struct PastaListTemplate<'a> {
|
||||||
args: &'a Args,
|
args: &'a Args,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The endpoint to view all currently registered pastas.
|
||||||
#[get("/pastalist")]
|
#[get("/pastalist")]
|
||||||
pub async fn list(data: web::Data<AppState>) -> HttpResponse {
|
pub async fn list(data: web::Data<AppState>) -> HttpResponse {
|
||||||
if ARGS.no_listing {
|
if ARGS.no_listing {
|
||||||
|
@ -21,7 +22,7 @@ pub async fn list(data: web::Data<AppState>) -> HttpResponse {
|
||||||
.finish();
|
.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut pastas = data.pastas.lock().unwrap();
|
let mut pastas = data.pastas.lock().await;
|
||||||
|
|
||||||
remove_expired(&mut pastas);
|
remove_expired(&mut pastas);
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
use crate::args::{Args, ARGS};
|
use crate::args::{Args, ARGS};
|
||||||
use crate::endpoints::errors::ErrorTemplate;
|
use crate::endpoints::errors::ErrorTemplate;
|
||||||
use crate::pasta::Pasta;
|
use crate::pasta::Pasta;
|
||||||
use crate::util::animalnumbers::to_u64;
|
|
||||||
use crate::util::hashids::to_u64 as hashid_to_u64;
|
use crate::util::hashids::to_u64 as hashid_to_u64;
|
||||||
use crate::util::misc::{self, remove_expired};
|
use crate::util::misc::{self, remove_expired};
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
|
use crate::util::pasta_id_converter::CONVERTER;
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
use actix_web::{get, web, HttpResponse};
|
use actix_web::{get, web, HttpResponse};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use qrcode_generator::QrCodeEcc;
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "qr.html", escape = "none")]
|
#[template(path = "qr.html", escape = "none")]
|
||||||
|
@ -18,15 +17,16 @@ struct QRTemplate<'a> {
|
||||||
args: &'a Args,
|
args: &'a Args,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Endpoint to open a QR code to a pasta.
|
||||||
#[get("/qr/{id}")]
|
#[get("/qr/{id}")]
|
||||||
pub async fn getqr(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
pub async fn getqr(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||||
// get access to the pasta collection
|
// get access to the pasta collection
|
||||||
let mut pastas = data.pastas.lock().unwrap();
|
let mut pastas = data.pastas.lock().await;
|
||||||
|
|
||||||
let u64_id = if ARGS.hash_ids {
|
let u64_id = if ARGS.hash_ids {
|
||||||
hashid_to_u64(&id).unwrap_or(0)
|
hashid_to_u64(&id).unwrap_or(0)
|
||||||
} else {
|
} else {
|
||||||
to_u64(&id).unwrap_or(0)
|
CONVERTER.to_u64(&id).unwrap_or(0)
|
||||||
};
|
};
|
||||||
|
|
||||||
// remove expired pastas (including this one if needed)
|
// remove expired pastas (including this one if needed)
|
||||||
|
@ -64,7 +64,10 @@ pub async fn getqr(data: web::Data<AppState>, id: web::Path<String>) -> HttpResp
|
||||||
|
|
||||||
// otherwise
|
// otherwise
|
||||||
// send pasta not found error
|
// send pasta not found error
|
||||||
HttpResponse::Ok()
|
HttpResponse::NotFound()
|
||||||
.content_type("text/html")
|
.content_type("text/html")
|
||||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
.body(ErrorTemplate {
|
||||||
|
status_code: StatusCode::NOT_FOUND,
|
||||||
|
args: &ARGS
|
||||||
|
}.render().unwrap())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
use actix_web::{get, web, HttpResponse};
|
use actix_web::{get, web, HttpResponse};
|
||||||
|
|
||||||
use crate::args::ARGS;
|
use crate::args::ARGS;
|
||||||
use crate::endpoints::errors::ErrorTemplate;
|
use crate::endpoints::errors::ErrorTemplate;
|
||||||
use crate::pasta::PastaFile;
|
use crate::pasta::PastaFile;
|
||||||
use crate::util::animalnumbers::to_u64;
|
|
||||||
use crate::util::hashids::to_u64 as hashid_to_u64;
|
use crate::util::hashids::to_u64 as hashid_to_u64;
|
||||||
use crate::util::misc::remove_expired;
|
use crate::util::misc::remove_expired;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
|
use crate::util::pasta_id_converter::CONVERTER;
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
|
/// Endpoint to remove a pasta.
|
||||||
#[get("/remove/{id}")]
|
#[get("/remove/{id}")]
|
||||||
pub async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
pub async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
|
||||||
if ARGS.readonly {
|
if ARGS.readonly {
|
||||||
|
@ -18,12 +20,12 @@ pub async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpRes
|
||||||
.finish();
|
.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut pastas = data.pastas.lock().unwrap();
|
let mut pastas = data.pastas.lock().await;
|
||||||
|
|
||||||
let id = if ARGS.hash_ids {
|
let id = if ARGS.hash_ids {
|
||||||
hashid_to_u64(&*id).unwrap_or(0)
|
hashid_to_u64(&id).unwrap_or(0)
|
||||||
} else {
|
} else {
|
||||||
to_u64(&*id.into_inner()).unwrap_or(0)
|
CONVERTER.to_u64(&id.into_inner()).unwrap_or(0)
|
||||||
};
|
};
|
||||||
|
|
||||||
for (i, pasta) in pastas.iter().enumerate() {
|
for (i, pasta) in pastas.iter().enumerate() {
|
||||||
|
@ -57,7 +59,10 @@ pub async fn remove(data: web::Data<AppState>, id: web::Path<String>) -> HttpRes
|
||||||
|
|
||||||
remove_expired(&mut pastas);
|
remove_expired(&mut pastas);
|
||||||
|
|
||||||
HttpResponse::Ok()
|
HttpResponse::NotFound()
|
||||||
.content_type("text/html")
|
.content_type("text/html")
|
||||||
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
|
.body(ErrorTemplate {
|
||||||
|
status_code: StatusCode::NOT_FOUND,
|
||||||
|
args: &ARGS
|
||||||
|
}.render().unwrap())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use actix_web::{web, App, HttpResponse, HttpServer, Responder};
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
use mime_guess::from_path;
|
use mime_guess::from_path;
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
|
|
||||||
|
@ -19,45 +19,3 @@ fn handle_embedded_file(path: &str) -> HttpResponse {
|
||||||
async fn static_resources(path: web::Path<String>) -> impl Responder {
|
async fn static_resources(path: web::Path<String>) -> impl Responder {
|
||||||
handle_embedded_file(path.as_str())
|
handle_embedded_file(path.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[derive(Template)]
|
|
||||||
// #[template(path = "water.css", escape = "none")]
|
|
||||||
// struct WaterCSS<'a> {
|
|
||||||
// _marker: PhantomData<&'a ()>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // #[derive(Template)]
|
|
||||||
// // #[template(path = "logo.png", escape = "none")]
|
|
||||||
// struct LogoPNG<'a> {
|
|
||||||
// _marker: PhantomData<&'a ()>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[derive(Template)]
|
|
||||||
// #[template(path = "favicon.svg", escape = "none")]
|
|
||||||
// struct Favicon<'a> {
|
|
||||||
// _marker: PhantomData<&'a ()>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[get("/static/{resource}")]
|
|
||||||
// pub async fn static_resources(resource_id: web::Path<String>) -> HttpResponse {
|
|
||||||
// match resource_id.into_inner().as_str() {
|
|
||||||
// "water.css" => HttpResponse::Ok().content_type("text/css").body(
|
|
||||||
// WaterCSS {
|
|
||||||
// _marker: Default::default(),
|
|
||||||
// }
|
|
||||||
// .render()
|
|
||||||
// .unwrap(),
|
|
||||||
// ),
|
|
||||||
// "logo.png" => HttpResponse::Ok()
|
|
||||||
// .content_type("image/png")
|
|
||||||
// .body(Ok(EmbedFile::open("templates/logo.png")?)),
|
|
||||||
// "favicon.svg" => HttpResponse::Ok().content_type("image/svg+xml").body(
|
|
||||||
// Favicon {
|
|
||||||
// _marker: Default::default(),
|
|
||||||
// }
|
|
||||||
// .render()
|
|
||||||
// .unwrap(),
|
|
||||||
// ),
|
|
||||||
// _ => HttpResponse::NotFound().content_type("text/html").finish(),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
32
src/main.rs
32
src/main.rs
|
@ -11,16 +11,16 @@ use actix_web::{middleware, web, App, HttpServer};
|
||||||
use actix_web_httpauth::middleware::HttpAuthentication;
|
use actix_web_httpauth::middleware::HttpAuthentication;
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use env_logger::Builder;
|
use env_logger::Builder;
|
||||||
|
use futures::lock::Mutex;
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::sync::Mutex;
|
|
||||||
|
|
||||||
pub mod args;
|
pub mod args;
|
||||||
pub mod pasta;
|
pub mod pasta;
|
||||||
|
|
||||||
pub mod util {
|
pub mod util {
|
||||||
pub mod animalnumbers;
|
pub mod pasta_id_converter;
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod dbio;
|
pub mod dbio;
|
||||||
pub mod hashids;
|
pub mod hashids;
|
||||||
|
@ -68,14 +68,8 @@ async fn main() -> std::io::Result<()> {
|
||||||
match fs::create_dir_all("./pasta_data/public") {
|
match fs::create_dir_all("./pasta_data/public") {
|
||||||
Ok(dir) => dir,
|
Ok(dir) => dir,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::error!(
|
log::error!("Couldn't create data directory ./pasta_data/public/: {error:?}");
|
||||||
"Couldn't create data directory ./pasta_data/public/: {:?}",
|
panic!("Couldn't create data directory ./pasta_data/public/: {error:?}");
|
||||||
error
|
|
||||||
);
|
|
||||||
panic!(
|
|
||||||
"Couldn't create data directory ./pasta_data/public/: {:?}",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -89,9 +83,21 @@ async fn main() -> std::io::Result<()> {
|
||||||
.wrap(middleware::NormalizePath::trim())
|
.wrap(middleware::NormalizePath::trim())
|
||||||
.service(create::index)
|
.service(create::index)
|
||||||
.service(info::info)
|
.service(info::info)
|
||||||
.service(pasta_endpoint::getpasta)
|
.route(
|
||||||
.service(pasta_endpoint::getrawpasta)
|
&format!("/{}/{{id}}", ARGS.pasta_endpoint),
|
||||||
.service(pasta_endpoint::redirecturl)
|
web::get().to(pasta_endpoint::getpasta)
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
&format!("/{}/{{id}}", ARGS.raw_endpoint),
|
||||||
|
web::get().to(pasta_endpoint::getrawpasta)
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
&format!("/{}/{{id}}", ARGS.url_endpoint),
|
||||||
|
web::get().to(pasta_endpoint::redirecturl)
|
||||||
|
)
|
||||||
|
//.service(pasta_endpoint::getpasta)
|
||||||
|
//.service(pasta_endpoint::getrawpasta)
|
||||||
|
//.service(pasta_endpoint::redirecturl)
|
||||||
.service(edit::get_edit)
|
.service(edit::get_edit)
|
||||||
.service(edit::post_edit)
|
.service(edit::post_edit)
|
||||||
.service(static_resources::static_resources)
|
.service(static_resources::static_resources)
|
||||||
|
|
29
src/pasta.rs
29
src/pasta.rs
|
@ -6,8 +6,8 @@ use std::path::Path;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use crate::args::ARGS;
|
use crate::args::ARGS;
|
||||||
use crate::util::animalnumbers::to_animal_names;
|
|
||||||
use crate::util::hashids::to_hashids;
|
use crate::util::hashids::to_hashids;
|
||||||
|
use crate::util::pasta_id_converter::CONVERTER;
|
||||||
use crate::util::syntaxhighlighter::html_highlight;
|
use crate::util::syntaxhighlighter::html_highlight;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
@ -30,6 +30,12 @@ impl PastaFile {
|
||||||
pub fn name(&self) -> &str {
|
pub fn name(&self) -> &str {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if file is an image for embedding
|
||||||
|
pub fn is_image(&self) -> bool {
|
||||||
|
let guess = mime_guess::from_path(&self.name).first_or_text_plain();
|
||||||
|
guess.type_() == "image"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -45,6 +51,8 @@ pub struct Pasta {
|
||||||
pub last_read: i64,
|
pub last_read: i64,
|
||||||
pub read_count: u64,
|
pub read_count: u64,
|
||||||
pub burn_after_reads: u64,
|
pub burn_after_reads: u64,
|
||||||
|
// what types can there be?
|
||||||
|
// `url`, `text`,
|
||||||
pub pasta_type: String,
|
pub pasta_type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +61,7 @@ impl Pasta {
|
||||||
if ARGS.hash_ids {
|
if ARGS.hash_ids {
|
||||||
to_hashids(self.id)
|
to_hashids(self.id)
|
||||||
} else {
|
} else {
|
||||||
to_animal_names(self.id)
|
CONVERTER.to_names(self.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,29 +104,29 @@ impl Pasta {
|
||||||
// get seconds since last read and convert it to days
|
// get seconds since last read and convert it to days
|
||||||
let days = ((timenow - self.last_read) / 86400) as u16;
|
let days = ((timenow - self.last_read) / 86400) as u16;
|
||||||
if days > 1 {
|
if days > 1 {
|
||||||
return format!("{} days ago", days);
|
return format!("{days} days ago");
|
||||||
};
|
};
|
||||||
|
|
||||||
// it's less than 1 day, let's do hours then
|
// it's less than 1 day, let's do hours then
|
||||||
let hours = ((timenow - self.last_read) / 3600) as u16;
|
let hours = ((timenow - self.last_read) / 3600) as u16;
|
||||||
if hours > 1 {
|
if hours > 1 {
|
||||||
return format!("{} hours ago", hours);
|
return format!("{hours} hours ago");
|
||||||
};
|
};
|
||||||
|
|
||||||
// it's less than 1 hour, let's do minutes then
|
// it's less than 1 hour, let's do minutes then
|
||||||
let minutes = ((timenow - self.last_read) / 60) as u16;
|
let minutes = ((timenow - self.last_read) / 60) as u16;
|
||||||
if minutes > 1 {
|
if minutes > 1 {
|
||||||
return format!("{} minutes ago", minutes);
|
return format!("{minutes} minutes ago");
|
||||||
};
|
};
|
||||||
|
|
||||||
// it's less than 1 minute, let's do seconds then
|
// it's less than 1 minute, let's do seconds then
|
||||||
let seconds = (timenow - self.last_read) as u16;
|
let seconds = (timenow - self.last_read) as u16;
|
||||||
if seconds > 1 {
|
if seconds > 1 {
|
||||||
return format!("{} seconds ago", seconds);
|
return format!("{seconds} seconds ago");
|
||||||
};
|
};
|
||||||
|
|
||||||
// it's less than 1 second?????
|
// it's less than 1 second?????
|
||||||
return String::from("just now");
|
String::from("just now")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn last_read_days_ago(&self) -> u16 {
|
pub fn last_read_days_ago(&self) -> u16 {
|
||||||
|
@ -132,7 +140,7 @@ impl Pasta {
|
||||||
} as i64;
|
} as i64;
|
||||||
|
|
||||||
// get seconds since last read and convert it to days
|
// get seconds since last read and convert it to days
|
||||||
return ((timenow - self.last_read) / 86400) as u16;
|
((timenow - self.last_read) / 86400) as u16
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn content_syntax_highlighted(&self) -> String {
|
pub fn content_syntax_highlighted(&self) -> String {
|
||||||
|
@ -144,7 +152,10 @@ impl Pasta {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn content_escaped(&self) -> String {
|
pub fn content_escaped(&self) -> String {
|
||||||
self.content.replace("`", "\\`").replace("$", "\\$")
|
self.content
|
||||||
|
.replace('`', "\\`")
|
||||||
|
.replace('$', "\\$")
|
||||||
|
.replace('/', "\\/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
const ANIMAL_NAMES: &[&str] = &[
|
|
||||||
"ant", "eel", "mole", "sloth", "ape", "emu", "monkey", "snail", "bat", "falcon", "mouse",
|
|
||||||
"snake", "bear", "fish", "otter", "spider", "bee", "fly", "parrot", "squid", "bird", "fox",
|
|
||||||
"panda", "swan", "bison", "frog", "pig", "tiger", "camel", "gecko", "pigeon", "toad", "cat",
|
|
||||||
"goat", "pony", "turkey", "cobra", "goose", "pug", "turtle", "crow", "hawk", "rabbit", "viper",
|
|
||||||
"deer", "horse", "rat", "wasp", "dog", "jaguar", "raven", "whale", "dove", "koala", "seal",
|
|
||||||
"wolf", "duck", "lion", "shark", "worm", "eagle", "lizard", "sheep", "zebra",
|
|
||||||
];
|
|
||||||
|
|
||||||
pub fn to_animal_names(mut number: u64) -> String {
|
|
||||||
let mut result: Vec<&str> = Vec::new();
|
|
||||||
|
|
||||||
if number == 0 {
|
|
||||||
return ANIMAL_NAMES[0].parse().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut power = 6;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let digit = number / ANIMAL_NAMES.len().pow(power) as u64;
|
|
||||||
if !(result.is_empty() && digit == 0) {
|
|
||||||
result.push(ANIMAL_NAMES[digit as usize]);
|
|
||||||
}
|
|
||||||
number -= digit * ANIMAL_NAMES.len().pow(power) as u64;
|
|
||||||
if power > 0 {
|
|
||||||
power -= 1;
|
|
||||||
} else if power <= 0 || number == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.join("-")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_u64(animal_names: &str) -> Result<u64, &str> {
|
|
||||||
let mut result: u64 = 0;
|
|
||||||
|
|
||||||
let animals: Vec<&str> = animal_names.split("-").collect();
|
|
||||||
|
|
||||||
let mut pow = animals.len();
|
|
||||||
for i in 0..animals.len() {
|
|
||||||
pow -= 1;
|
|
||||||
let animal_index = ANIMAL_NAMES.iter().position(|&r| r == animals[i]);
|
|
||||||
match animal_index {
|
|
||||||
None => return Err("Failed to convert animal name to u64!"),
|
|
||||||
Some(_) => {
|
|
||||||
result += (animal_index.unwrap() * ANIMAL_NAMES.len().pow(pow as u32)) as u64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
|
@ -4,7 +4,7 @@ use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
use crate::Pasta;
|
use crate::Pasta;
|
||||||
|
|
||||||
static DATABASE_PATH: &'static str = "pasta_data/database.json";
|
static DATABASE_PATH: &str = "pasta_data/database.json";
|
||||||
|
|
||||||
pub fn save_to_file(pasta_data: &Vec<Pasta>) {
|
pub fn save_to_file(pasta_data: &Vec<Pasta>) {
|
||||||
let mut file = File::create(DATABASE_PATH);
|
let mut file = File::create(DATABASE_PATH);
|
||||||
|
@ -14,11 +14,11 @@ pub fn save_to_file(pasta_data: &Vec<Pasta>) {
|
||||||
serde_json::to_writer(writer, &pasta_data).expect("Failed to create JSON writer");
|
serde_json::to_writer(writer, &pasta_data).expect("Failed to create JSON writer");
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log::info!("Database file {} not found!", DATABASE_PATH);
|
log::info!("Database file {DATABASE_PATH} not found!");
|
||||||
file = File::create(DATABASE_PATH);
|
file = File::create(DATABASE_PATH);
|
||||||
match file {
|
match file {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
log::info!("Database file {} created.", DATABASE_PATH);
|
log::info!("Database file {DATABASE_PATH} created.");
|
||||||
save_to_file(pasta_data);
|
save_to_file(pasta_data);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -27,7 +27,7 @@ pub fn save_to_file(pasta_data: &Vec<Pasta>) {
|
||||||
&DATABASE_PATH,
|
&DATABASE_PATH,
|
||||||
&err
|
&err
|
||||||
);
|
);
|
||||||
panic!("Failed to create database file {}: {}!", DATABASE_PATH, err)
|
panic!("Failed to create database file {DATABASE_PATH}: {err}!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,10 +46,10 @@ pub fn load_from_file() -> io::Result<Vec<Pasta>> {
|
||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log::info!("Database file {} not found!", DATABASE_PATH);
|
log::info!("Database file {DATABASE_PATH} not found!");
|
||||||
save_to_file(&Vec::<Pasta>::new());
|
save_to_file(&Vec::<Pasta>::new());
|
||||||
|
|
||||||
log::info!("Database file {} created.", DATABASE_PATH);
|
log::info!("Database file {DATABASE_PATH} created.");
|
||||||
load_from_file()
|
load_from_file()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,6 @@ pub fn to_u64(hash_id: &str) -> Result<u64, &str> {
|
||||||
let ids = HARSH
|
let ids = HARSH
|
||||||
.decode(hash_id)
|
.decode(hash_id)
|
||||||
.map_err(|_e| "Failed to decode hash ID")?;
|
.map_err(|_e| "Failed to decode hash ID")?;
|
||||||
let id = ids.get(0).ok_or("No ID found in hash ID")?;
|
let id = ids.first().ok_or("No ID found in hash ID")?;
|
||||||
Ok(*id)
|
Ok(*id)
|
||||||
}
|
}
|
||||||
|
|
98
src/util/pasta_id_converter.rs
Normal file
98
src/util/pasta_id_converter.rs
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
use crate::args::ARGS;
|
||||||
|
|
||||||
|
const ANIMAL_NAMES: &[&str] = &[
|
||||||
|
"ant", "eel", "mole", "sloth", "ape", "emu", "monkey", "snail", "bat", "falcon", "mouse",
|
||||||
|
"snake", "bear", "fish", "otter", "spider", "bee", "fly", "parrot", "squid", "bird", "fox",
|
||||||
|
"panda", "swan", "bison", "frog", "pig", "tiger", "camel", "gecko", "pigeon", "toad", "cat",
|
||||||
|
"goat", "pony", "turkey", "cobra", "goose", "pug", "turtle", "crow", "hawk", "rabbit", "viper",
|
||||||
|
"deer", "horse", "rat", "wasp", "dog", "jaguar", "raven", "whale", "dove", "koala", "seal",
|
||||||
|
"wolf", "duck", "lion", "shark", "worm", "eagle", "lizard", "sheep", "zebra",
|
||||||
|
];
|
||||||
|
|
||||||
|
lazy_static!{
|
||||||
|
pub static ref CONVERTER: PastaIdConverter = PastaIdConverter::new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert pasta IDs to names and vice versa
|
||||||
|
pub struct PastaIdConverter {
|
||||||
|
names: Vec<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PastaIdConverter {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let names;
|
||||||
|
if let Some(names_path) = &ARGS.custom_names {
|
||||||
|
let names_data = fs::read_to_string(names_path)
|
||||||
|
.expect("path for the names file should contain a names file");
|
||||||
|
names = names_data
|
||||||
|
.split('\n')
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
} else {
|
||||||
|
names = ANIMAL_NAMES
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
Self { names }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_names(&self, mut number: u64) -> String {
|
||||||
|
let mut result: Vec<&str> = Vec::new();
|
||||||
|
|
||||||
|
if number == 0 {
|
||||||
|
return self.names[0].parse().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut power = 6;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let digit = number / self.names.len().pow(power) as u64;
|
||||||
|
if !(result.is_empty() && digit == 0) {
|
||||||
|
result.push(&self.names[digit as usize]);
|
||||||
|
}
|
||||||
|
number -= digit * self.names.len().pow(power) as u64;
|
||||||
|
if power > 0 {
|
||||||
|
power -= 1;
|
||||||
|
} else if power == 0 || number == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.join("-")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_u64(&self, pasta_id: &str) -> Result<u64, &str> {
|
||||||
|
let mut result: u64 = 0;
|
||||||
|
|
||||||
|
let names: Vec<&str> = pasta_id.split('-').collect();
|
||||||
|
|
||||||
|
let mut pow = names.len();
|
||||||
|
for name in names {
|
||||||
|
pow -= 1;
|
||||||
|
let name_index = self.names.iter().position(|r| r == name);
|
||||||
|
match name_index {
|
||||||
|
None => return Err("Failed to convert animal name to u64!"),
|
||||||
|
Some(_) => {
|
||||||
|
result += (name_index.unwrap() * self.names.len().pow(pow as u32)) as u64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PastaIdConverter {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub fn html_highlight(text: &str, extension: &str) -> String {
|
||||||
|
|
||||||
let syntax = ps
|
let syntax = ps
|
||||||
.find_syntax_by_extension(extension)
|
.find_syntax_by_extension(extension)
|
||||||
.or(Option::from(ps.find_syntax_plain_text()))
|
.or_else(|| Option::from(ps.find_syntax_plain_text()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut h = HighlightLines::new(syntax, &ts.themes["InspiredGitHub"]);
|
let mut h = HighlightLines::new(syntax, &ts.themes["InspiredGitHub"]);
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ pub fn html_highlight(text: &str, extension: &str) -> String {
|
||||||
|
|
||||||
let mut highlighted_content2: String = String::from("");
|
let mut highlighted_content2: String = String::from("");
|
||||||
for line in highlighted_content.lines() {
|
for line in highlighted_content.lines() {
|
||||||
highlighted_content2 += &*format!("<code-line>{}</code-line>\n", line);
|
highlighted_content2 += &*format!("<code-line>{line}</code-line>\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rewrite colours to ones that are compatible with water.css and both light/dark modes
|
// Rewrite colours to ones that are compatible with water.css and both light/dark modes
|
||||||
|
@ -33,5 +33,5 @@ pub fn html_highlight(text: &str, extension: &str) -> String {
|
||||||
highlighted_content2 =
|
highlighted_content2 =
|
||||||
highlighted_content2.replace("style=\"color:#183691;\"", "style=\"color:blue;\"");
|
highlighted_content2.replace("style=\"color:#183691;\"", "style=\"color:blue;\"");
|
||||||
|
|
||||||
return highlighted_content2;
|
highlighted_content2
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,81 @@
|
||||||
<svg width="63" height="73" viewBox="0 0 63 73" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<path d="M0.806818 72.1932L12.7045 0.636362H25.0455L19.9659 30.9773C19.6023 33.3409 19.7955 35.4205 20.5455 37.2159C21.3182 38.9886 22.5682 40.3864 24.2955 41.4091C26.0227 42.4091 28.1364 42.9091 30.6364 42.9091C33.1591 42.9091 35.4432 42.4091 37.4886 41.4091C39.5568 40.3864 41.2614 38.9886 42.6023 37.2159C43.9432 35.4205 44.7955 33.3409 45.1591 30.9773L50.2386 0.636362H62.6136L53.8864 53H41.8864L43.2159 45.4318H42.8068C41.1932 47.8409 39.1591 49.7159 36.7045 51.0568C34.25 52.375 31.6477 53.0341 28.8977 53.0341C26.2386 53.0341 23.8864 52.375 21.8409 51.0568C19.8182 49.7159 18.4205 47.8409 17.6477 45.4318H17.2386L12.7727 72.1932H0.806818Z" fill="#868686"/>
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
<path d="M0.806818 72.1932L12.7045 0.636362H25.0455L19.9659 30.9773C19.6023 33.3409 19.7955 35.4205 20.5455 37.2159C21.3182 38.9886 22.5682 40.3864 24.2955 41.4091C26.0227 42.4091 28.1364 42.9091 30.6364 42.9091C33.1591 42.9091 35.4432 42.4091 37.4886 41.4091C39.5568 40.3864 41.2614 38.9886 42.6023 37.2159C43.9432 35.4205 44.7955 33.3409 45.1591 30.9773L50.2386 0.636362H62.6136L53.8864 53H41.8864L43.2159 45.4318H42.8068C41.1932 47.8409 39.1591 49.7159 36.7045 51.0568C34.25 52.375 31.6477 53.0341 28.8977 53.0341C26.2386 53.0341 23.8864 52.375 21.8409 51.0568C19.8182 49.7159 18.4205 47.8409 17.6477 45.4318H17.2386L12.7727 72.1932H0.806818Z" fill="#868686"/>
|
|
||||||
<path d="M0.806818 72.1932L12.7045 0.636362H25.0455L19.9659 30.9773C19.6023 33.3409 19.7955 35.4205 20.5455 37.2159C21.3182 38.9886 22.5682 40.3864 24.2955 41.4091C26.0227 42.4091 28.1364 42.9091 30.6364 42.9091C33.1591 42.9091 35.4432 42.4091 37.4886 41.4091C39.5568 40.3864 41.2614 38.9886 42.6023 37.2159C43.9432 35.4205 44.7955 33.3409 45.1591 30.9773L50.2386 0.636362H62.6136L53.8864 53H41.8864L43.2159 45.4318H42.8068C41.1932 47.8409 39.1591 49.7159 36.7045 51.0568C34.25 52.375 31.6477 53.0341 28.8977 53.0341C26.2386 53.0341 23.8864 52.375 21.8409 51.0568C19.8182 49.7159 18.4205 47.8409 17.6477 45.4318H17.2386L12.7727 72.1932H0.806818Z" fill="#868686"/>
|
<svg
|
||||||
|
width="31.999998"
|
||||||
|
height="31.999998"
|
||||||
|
viewBox="0 0 8.4666661 8.4666661"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
sodipodi:docname="logo.svg"
|
||||||
|
inkscape:export-filename="logo.png"
|
||||||
|
inkscape:export-xdpi="384"
|
||||||
|
inkscape:export-ydpi="384"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#4a4a55"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#505050"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="19.556004"
|
||||||
|
inkscape:cx="6.0339524"
|
||||||
|
inkscape:cy="16.721207"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1036"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="44"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid182"
|
||||||
|
visible="true" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<g
|
||||||
|
id="g14438"
|
||||||
|
inkscape:label="Box">
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#f7f7ff;-inkscape-stroke:none"
|
||||||
|
d="M 1.984375,3.8066406 V 6.2207031 L 4.2324219,7.34375 4.2929687,7.3144531 6.4824219,6.2207031 V 3.8066406 l -2.25,1.125 z m 0.2636719,0.4296875 1.984375,0.9921875 1.984375,-0.9921875 V 6.0566406 L 4.2324219,7.0488281 2.2480469,6.0566406 Z"
|
||||||
|
id="path2056" />
|
||||||
|
<path
|
||||||
|
id="path3512"
|
||||||
|
style="color:#000000;fill:#f7f7ff;fill-opacity:1;-inkscape-stroke:none"
|
||||||
|
d="M 4.3651082,5.3087199 4.2322998,5.3748657 4.1015584,5.3097534 v 1.5260051 l 0.1328085,0.066663 0.1307413,-0.065629 z" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g18197"
|
||||||
|
inkscape:label="Cat"
|
||||||
|
style="display:inline">
|
||||||
|
<path
|
||||||
|
id="path14440"
|
||||||
|
style="color:#000000;display:inline;fill:#f7f7ff;fill-opacity:1;-inkscape-stroke:none"
|
||||||
|
d="M 4.6663818,2.8716593 4.3862956,3.0959351 h 0.045475 L 5.4063883,3.291272 5.709729,4.0462646 5.9464071,3.9279256 5.5996582,3.058728 Z M 2.6199951,2.9434896 2.4618652,3.8989868 2.7088786,4.0230103 2.8571899,3.1331421 Z" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#f7f7ff;-inkscape-stroke:none"
|
||||||
|
d="m 2.1308594,1.0234375 0.2773437,1.109375 v 0.4707031 l 1.1894532,0.953125 0.083984,-0.066406 1.1074219,-0.8867188 V 2.1328125 L 5.0664062,1.0234375 3.9902344,1.5605469 H 3.2070313 Z M 2.5273438,1.5175781 3.1445313,1.8261719 H 4.0527344 L 4.6699219,1.5175781 4.5234375,2.0996094 V 2.4765625 L 3.5976563,3.2167969 2.671875,2.4765625 V 2.0996094 Z"
|
||||||
|
id="path14903" />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
id="path22977"
|
||||||
|
style="color:#000000;fill:#f7f7ff;fill-opacity:1;-inkscape-stroke:none"
|
||||||
|
d="M 5.94434,3.5646403 6.0657796,3.8684977 6.3086589,3.7470581 Z m -3.5723918,0.074414 -0.2149739,0.1074869 0.181901,0.090951 z"
|
||||||
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 3.1 KiB |
81
templates/assets/logo.svg
Normal file
81
templates/assets/logo.svg
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 8.4666666 8.4666666"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
sodipodi:docname="microbin-logo.svg"
|
||||||
|
inkscape:export-filename="microbin-logo-exp.svg"
|
||||||
|
inkscape:export-xdpi="144"
|
||||||
|
inkscape:export-ydpi="144"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#4a4a55"
|
||||||
|
bordercolor="#eeeeee"
|
||||||
|
borderopacity="1"
|
||||||
|
inkscape:showpageshadow="0"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#505050"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="19.556004"
|
||||||
|
inkscape:cx="6.0339524"
|
||||||
|
inkscape:cy="16.721207"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1036"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="44"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid182"
|
||||||
|
visible="true" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<g
|
||||||
|
id="g14438"
|
||||||
|
inkscape:label="Box">
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#f7f7ff;-inkscape-stroke:none"
|
||||||
|
d="M 1.984375,3.8066406 V 6.2207031 L 4.2324219,7.34375 4.2929687,7.3144531 6.4824219,6.2207031 V 3.8066406 l -2.25,1.125 z m 0.2636719,0.4296875 1.984375,0.9921875 1.984375,-0.9921875 V 6.0566406 L 4.2324219,7.0488281 2.2480469,6.0566406 Z"
|
||||||
|
id="path2056" />
|
||||||
|
<path
|
||||||
|
id="path3512"
|
||||||
|
style="color:#000000;fill:#f7f7ff;-inkscape-stroke:none;fill-opacity:1"
|
||||||
|
d="M 4.3651082 5.3087199 L 4.2322998 5.3748657 L 4.1015584 5.3097534 L 4.1015584 6.8357585 L 4.2343669 6.9024211 L 4.3651082 6.836792 L 4.3651082 5.3087199 z " />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
id="g18197"
|
||||||
|
inkscape:label="Cat"
|
||||||
|
style="display:inline">
|
||||||
|
<path
|
||||||
|
id="path14440"
|
||||||
|
style="color:#000000;display:inline;fill:#f7f7ff;-inkscape-stroke:none;fill-opacity:1"
|
||||||
|
d="M 4.6663818,2.8716593 4.3862956,3.0959351 h 0.045475 L 5.4063883,3.291272 5.709729,4.0462646 5.9464071,3.9279256 5.5996582,3.058728 Z M 2.6199951,2.9434896 2.4618652,3.8989868 2.7088786,4.0230103 2.8571899,3.1331421 Z" />
|
||||||
|
<path
|
||||||
|
style="color:#000000;fill:#f7f7ff;-inkscape-stroke:none"
|
||||||
|
d="m 2.1308594,1.0234375 0.2773437,1.109375 v 0.4707031 l 1.1894532,0.953125 0.083984,-0.066406 1.1074219,-0.8867188 V 2.1328125 L 5.0664062,1.0234375 3.9902344,1.5605469 H 3.2070313 Z M 2.5273438,1.5175781 3.1445313,1.8261719 H 4.0527344 L 4.6699219,1.5175781 4.5234375,2.0996094 V 2.4765625 L 3.5976563,3.2167969 2.671875,2.4765625 V 2.0996094 Z"
|
||||||
|
id="path14903" />
|
||||||
|
</g>
|
||||||
|
<path
|
||||||
|
id="path22977"
|
||||||
|
style="color:#000000;fill:#f7f7ff;-inkscape-stroke:none;fill-opacity:1"
|
||||||
|
d="M 5.94434,3.5646403 6.0657796,3.8684977 6.3086589,3.7470581 Z m -3.5723918,0.074414 -0.2149739,0.1074869 0.181901,0.090951 z"
|
||||||
|
sodipodi:nodetypes="cccccccc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
File diff suppressed because one or more lines are too long
|
@ -1,7 +1,7 @@
|
||||||
{% include "header.html" %}
|
{% include "header.html" %}
|
||||||
<br>
|
<br>
|
||||||
<h2>404</h2>
|
<h2>{{ status_code.as_u16() }}</h2>
|
||||||
<b>Not Found</b>
|
<b>{{ status_code.canonical_reason().unwrap_or("Unknown error") }}</b>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<a href="{{ args.public_path }}/"> Go Home</a>
|
<a href="{{ args.public_path }}/"> Go Home</a>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<hr>
|
<hr>
|
||||||
<p style="font-size: smaller">
|
<p style="font-size: smaller">
|
||||||
{% if args.footer_text.as_ref().is_none() %}
|
{% if args.footer_text.as_ref().is_none() %}
|
||||||
<a href="https://microbin.eu">MicroBin</a> by Dániel Szabó and the FOSS Community.
|
<b>Karton</b> by Schrottkatze, based on <a href="https://microbin.eu">MicroBin</a> by Dániel Szabó and the FOSS Community.
|
||||||
Let's keep the Web <b>compact</b>, <b>accessible</b> and <b>humane</b>!
|
Let's keep the Web <b>compact</b>, <b>accessible</b> and <b>humane</b>!
|
||||||
{%- else %}
|
{%- else %}
|
||||||
{{ args.footer_text.as_ref().unwrap() }}
|
{{ args.footer_text.as_ref().unwrap() }}
|
||||||
|
@ -15,3 +15,4 @@
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,7 @@
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
{% if args.title.as_ref().is_none() %}
|
<title>{{ args.title }}</title>
|
||||||
<title>MicroBin</title>
|
|
||||||
{%- else %}
|
|
||||||
<title>{{ args.title.as_ref().unwrap() }}</title>
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
@ -48,11 +44,13 @@
|
||||||
<b style="margin-right: 0.5rem">
|
<b style="margin-right: 0.5rem">
|
||||||
|
|
||||||
{% if !args.hide_logo %}
|
{% if !args.hide_logo %}
|
||||||
<!-- <i><span style="font-size:2.2rem; margin-right:1rem">μ</span></i> -->
|
<a href="{{ args.public_path }}/"><img
|
||||||
<img width=26 style="margin-bottom: -6px; margin-right: 0.5rem;"
|
width=48
|
||||||
src="{{ args.public_path }}/static/logo.png">
|
style="margin-bottom: -12px;"
|
||||||
{%- endif %} {% if args.title.as_ref().is_none() %}
|
src="{{ args.public_path }}/static/logo.png"
|
||||||
MicroBin {%- else %} {{ args.title.as_ref().unwrap() }} {%- endif %}
|
></a>
|
||||||
|
{%- endif %}
|
||||||
|
{{ args.title }}
|
||||||
</b>
|
</b>
|
||||||
|
|
||||||
<a href="{{ args.public_path }}/" style="margin-right: 0.5rem; margin-left: 0.5rem">New
|
<a href="{{ args.public_path }}/" style="margin-right: 0.5rem; margin-left: 0.5rem">New
|
||||||
|
|
|
@ -35,6 +35,13 @@
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
24 hours
|
24 hours
|
||||||
</option>
|
</option>
|
||||||
|
{% if args.default_expiry == "3days" %}
|
||||||
|
<option selected value="3days">
|
||||||
|
{%- else %}
|
||||||
|
<option value="3days">
|
||||||
|
{%- endif %}
|
||||||
|
3 days
|
||||||
|
</option>
|
||||||
{% if args.default_expiry == "1week" %}
|
{% if args.default_expiry == "1week" %}
|
||||||
<option selected value="1week">
|
<option selected value="1week">
|
||||||
{%- else %}
|
{%- else %}
|
||||||
|
@ -172,11 +179,14 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if args.readonly %}
|
{% if args.readonly %}
|
||||||
<b>
|
<b>
|
||||||
<input style="width: 140px; float: right; background-color: #0076d18f;" disabled type="submit"
|
<!--<input style="width: 140px; float: right; background-color: #0076d18f;" disabled type="submit"-->
|
||||||
|
<!--value="Read Only" /></b>-->
|
||||||
|
<input style="width: 140px; float: right" disabled type="submit"
|
||||||
value="Read Only" /></b>
|
value="Read Only" /></b>
|
||||||
{%- else %}
|
{%- else %}
|
||||||
<b>
|
<b>
|
||||||
<input style="width: 140px; float: right; background-color: #0076d18f;" type="submit" value="Save" />
|
<!--<input style="width: 140px; float: right; background-color: #0076d18f;" type="submit" value="Save" />-->
|
||||||
|
<input style="width: 140px; float: right" type="submit" value="Save" />
|
||||||
</b>
|
</b>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
<h4>Links</h4>
|
<h4>Links</h4>
|
||||||
<a href="https://microbin.eu/documentation" style="margin-right: 1rem">Documentation and Help</a>
|
<a href="https://microbin.eu/documentation" style="margin-right: 1rem">Documentation and Help</a>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://github.com/szabodanika/microbin" style="margin-right: 1rem">Source Code</a>
|
<a href="https://gitlab.com/obsidianical/microbin" style="margin-right: 1rem">Source Code</a>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://github.com/szabodanika/microbin/issues" style="margin-right: 1rem">Feedback</a>
|
<a href="https://gitlab.com/obsidianical/microbin/issues" style="margin-right: 1rem">Feedback</a>
|
||||||
<br>
|
<br>
|
||||||
<a href="https://microbin.eu/donate">Donate and Sponsor</a>
|
<a href="https://microbin.eu/donate">Donate and Sponsor</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
Copy Redirect
|
Copy Redirect
|
||||||
</button>
|
</button>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
<a style="margin-right: 1rem" href="{{ args.public_path }}/raw/{{pasta.id_as_animals()}}">Raw Text
|
<a style="margin-right: 1rem" href="{{ args.public_path }}/{{ args.raw_endpoint }}/{{pasta.id_as_animals()}}">Raw Text
|
||||||
Content</a>
|
Content</a>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
{% if args.qr && args.public_path.to_string() != "" %}
|
{% if args.qr && args.public_path.to_string() != "" %}
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div style="float: right">
|
<div style="float: right">
|
||||||
<a style="margin-right: 0.5rem"
|
<a style="margin-right: 0.5rem"
|
||||||
href="{{ args.public_path }}/pasta/{{pasta.id_as_animals()}}"><i>{{pasta.id_as_animals()}}</i></a>
|
href="{{ args.public_path }}/{{ args.pasta_endpoint }}/{{pasta.id_as_animals()}}"><i>{{pasta.id_as_animals()}}</i></a>
|
||||||
{% if args.public_path.to_string() != "" %}
|
{% if args.public_path.to_string() != "" %}
|
||||||
<button id="copy-url-button" class="copy-button" style="margin-right: 0">
|
<button id="copy-url-button" class="copy-button" style="margin-right: 0">
|
||||||
Copy URL
|
Copy URL
|
||||||
|
@ -32,6 +32,10 @@
|
||||||
{% if pasta.file.is_some() %}
|
{% if pasta.file.is_some() %}
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
|
{% if pasta.file.as_ref().unwrap().is_image() %}
|
||||||
|
<img src="{{ args.public_path }}/file/{{pasta.id_as_animals()}}/{{pasta.file.as_ref().unwrap().name()}}" alt="">
|
||||||
|
<br>
|
||||||
|
{%- endif %}
|
||||||
<a href="{{ args.public_path }}/file/{{pasta.id_as_animals()}}/{{pasta.file.as_ref().unwrap().name()}}" download>
|
<a href="{{ args.public_path }}/file/{{pasta.id_as_animals()}}/{{pasta.file.as_ref().unwrap().name()}}" download>
|
||||||
Download attached file: '{{pasta.file.as_ref().unwrap().name()}}' [{{pasta.file.as_ref().unwrap().size}}]
|
Download attached file: '{{pasta.file.as_ref().unwrap().name()}}' [{{pasta.file.as_ref().unwrap().size}}]
|
||||||
</a>
|
</a>
|
||||||
|
@ -66,8 +70,8 @@
|
||||||
const copyTextBtn = document.getElementById("copy-text-button")
|
const copyTextBtn = document.getElementById("copy-text-button")
|
||||||
const copyRedirectBtn = document.getElementById("copy-redirect-button")
|
const copyRedirectBtn = document.getElementById("copy-redirect-button")
|
||||||
const content = `{{ pasta.content_escaped() }}`
|
const content = `{{ pasta.content_escaped() }}`
|
||||||
const url = `{{ args.public_path }}/pasta/{{pasta.id_as_animals()}}`
|
const url = `{{ args.public_path }}/{{ args.pasta_endpoint }}/{{pasta.id_as_animals()}}`
|
||||||
const redirect_url = `{{ args.public_path }}/url/{{pasta.id_as_animals()}}`
|
const redirect_url = `{{ args.public_path }}/{{ args.url_endpoint }}/{{pasta.id_as_animals()}}`
|
||||||
|
|
||||||
copyURLBtn.addEventListener("click", () => {
|
copyURLBtn.addEventListener("click", () => {
|
||||||
navigator.clipboard.writeText(url)
|
navigator.clipboard.writeText(url)
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
{% if pasta.pasta_type == "text" && !pasta.private %}
|
{% if pasta.pasta_type == "text" && !pasta.private %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ args.public_path }}/pasta/{{pasta.id_as_animals()}}">{{pasta.id_as_animals()}}</a>
|
<a href="{{ args.public_path }}/{{ args.pasta_endpoint }}/{{pasta.id_as_animals()}}">{{pasta.id_as_animals()}}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{pasta.created_as_string()}}
|
{{pasta.created_as_string()}}
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
{{pasta.expiration_as_string()}}
|
{{pasta.expiration_as_string()}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a style="margin-right:1rem" href="{{ args.public_path }}/raw/{{pasta.id_as_animals()}}">Raw</a>
|
<a style="margin-right:1rem" href="{{ args.public_path }}/{{ args.raw_endpoint }}/{{pasta.id_as_animals()}}">Raw</a>
|
||||||
{% if pasta.file.is_some() %}
|
{% if pasta.file.is_some() %}
|
||||||
<a style="margin-right:1rem"
|
<a style="margin-right:1rem"
|
||||||
href="{{ args.public_path }}/file/{{pasta.id_as_animals()}}/{{pasta.file.as_ref().unwrap().name()}}">File</a>
|
href="{{ args.public_path }}/file/{{pasta.id_as_animals()}}/{{pasta.file.as_ref().unwrap().name()}}">File</a>
|
||||||
|
@ -80,7 +80,7 @@
|
||||||
{% if pasta.pasta_type == "url" && !pasta.private %}
|
{% if pasta.pasta_type == "url" && !pasta.private %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{{ args.public_path }}/pasta/{{pasta.id_as_animals()}}">{{pasta.id_as_animals()}}</a>
|
<a href="{{ args.public_path }}/{{ args.pasta_endpoint }}/{{pasta.id_as_animals()}}">{{pasta.id_as_animals()}}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{pasta.created_as_string()}}
|
{{pasta.created_as_string()}}
|
||||||
|
@ -89,9 +89,9 @@
|
||||||
{{pasta.expiration_as_string()}}
|
{{pasta.expiration_as_string()}}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a style="margin-right:1rem" href="{{ args.public_path }}/url/{{pasta.id_as_animals()}}">Open</a>
|
<a style="margin-right:1rem" href="{{ args.public_path }}/{{ args.url_endpoint }}/{{pasta.id_as_animals()}}">Open</a>
|
||||||
<a style="margin-right:1rem; cursor: pointer;" id="copy-button"
|
<a style="margin-right:1rem; cursor: pointer;" id="copy-button"
|
||||||
data-url="{{ args.public_path }}/url/{{pasta.id_as_animals()}}">Copy</a>
|
data-url="{{ args.public_path }}/{{ args.url_endpoint }}/{{pasta.id_as_animals()}}">Copy</a>
|
||||||
{% if pasta.editable %}
|
{% if pasta.editable %}
|
||||||
<a style="margin-right:1rem" href="{{ args.public_path }}/edit/{{pasta.id_as_animals()}}">Edit</a>
|
<a style="margin-right:1rem" href="{{ args.public_path }}/edit/{{pasta.id_as_animals()}}">Edit</a>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
@ -106,7 +106,6 @@
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// const btn = document.getElementById("copy-button")
|
|
||||||
const btn = document.querySelector("#copy-button");
|
const btn = document.querySelector("#copy-button");
|
||||||
btn.addEventListener("click", () => {
|
btn.addEventListener("click", () => {
|
||||||
navigator.clipboard.writeText(btn.dataset.url)
|
navigator.clipboard.writeText(btn.dataset.url)
|
||||||
|
|
Loading…
Reference in a new issue