initial commit of slink
This commit is contained in:
commit
d64f205162
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
3150
Cargo.lock
generated
Normal file
3150
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
Cargo.toml
Normal file
26
Cargo.toml
Normal file
@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "slink"
|
||||
version = "0.1.0"
|
||||
authors = ["Dietrich <dietrich@teilgedanken.de>"]
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
actix-web = "3"
|
||||
actix-web-static-files = "3"
|
||||
tera = "1.6"
|
||||
serde = "1.0"
|
||||
diesel = { version = "1.4", features = ["sqlite", "chrono"] }
|
||||
diesel_codegen = { version = "0.16.1", features = ["sqlite"] }
|
||||
dotenv = "0.10.1"
|
||||
actix-identity = "0.3"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
argonautica = "0.2"
|
||||
env_logger = "0.8"
|
||||
log = "0.4"
|
||||
qrcodegen = "1.6"
|
||||
|
||||
[build-dependencies]
|
||||
actix-web-static-files = "3"
|
5
build.rs
Normal file
5
build.rs
Normal file
@ -0,0 +1,5 @@
|
||||
use actix_web_static_files::resource_dir;
|
||||
|
||||
fn main() {
|
||||
resource_dir("./static").build().unwrap();
|
||||
}
|
5
diesel.toml
Normal file
5
diesel.toml
Normal file
@ -0,0 +1,5 @@
|
||||
# For documentation on how to configure this file,
|
||||
# see diesel.rs/guides/configuring-diesel-cli
|
||||
|
||||
[print_schema]
|
||||
file = "src/schema.rs"
|
0
migrations/.gitkeep
Normal file
0
migrations/.gitkeep
Normal file
3
migrations/2021-02-03-113236_initial/down.sql
Normal file
3
migrations/2021-02-03-113236_initial/down.sql
Normal file
@ -0,0 +1,3 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
DROP TABLE users;
|
||||
DROP TABLE links;
|
29
migrations/2021-02-03-113236_initial/up.sql
Normal file
29
migrations/2021-02-03-113236_initial/up.sql
Normal file
@ -0,0 +1,29 @@
|
||||
-- Your SQL goes here
|
||||
CREATE TABLE users
|
||||
(
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
username VARCHAR NOT NULL,
|
||||
email VARCHAR NOT NULL,
|
||||
password VARCHAR NOT NULL,
|
||||
|
||||
UNIQUE(username, email)
|
||||
);
|
||||
|
||||
CREATE TABLE links
|
||||
(
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
title VARCHAR NOT NULL,
|
||||
target VARCHAR NOT NULL,
|
||||
code VARCHAR NOT NULL,
|
||||
author INT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
|
||||
|
||||
FOREIGN KEY
|
||||
(author)
|
||||
REFERENCES users
|
||||
(id),
|
||||
|
||||
UNIQUE
|
||||
(code)
|
||||
);
|
7
src/forms.rs
Normal file
7
src/forms.rs
Normal file
@ -0,0 +1,7 @@
|
||||
use serde::Deserialize;
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct LinkForm {
|
||||
pub title: String,
|
||||
pub target: String,
|
||||
pub code: String,
|
||||
}
|
139
src/main.rs
Normal file
139
src/main.rs
Normal file
@ -0,0 +1,139 @@
|
||||
#[macro_use]
|
||||
extern crate diesel;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
mod forms;
|
||||
pub mod models;
|
||||
pub mod schema;
|
||||
mod views;
|
||||
|
||||
use actix_identity::{CookieIdentityPolicy, IdentityService};
|
||||
use actix_web::middleware::Logger;
|
||||
use actix_web::{web, App, HttpResponse, HttpServer};
|
||||
use actix_web_static_files;
|
||||
|
||||
use dotenv::dotenv;
|
||||
use tera::Tera;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ServerError {
|
||||
Argonautic,
|
||||
Diesel,
|
||||
Environment,
|
||||
Template(tera::Error),
|
||||
User(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ServerError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Test")
|
||||
}
|
||||
}
|
||||
|
||||
impl actix_web::error::ResponseError for ServerError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
match self {
|
||||
ServerError::Argonautic => {
|
||||
HttpResponse::InternalServerError().json("Argonautica Error.")
|
||||
}
|
||||
ServerError::Diesel => HttpResponse::InternalServerError().json("Diesel Error."),
|
||||
ServerError::Environment => {
|
||||
HttpResponse::InternalServerError().json("Environment Error.")
|
||||
}
|
||||
ServerError::Template(e) => {
|
||||
HttpResponse::InternalServerError().json(format!("Template Error. {:?}", e))
|
||||
}
|
||||
ServerError::User(data) => HttpResponse::InternalServerError().json(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::env::VarError> for ServerError {
|
||||
fn from(e: std::env::VarError) -> ServerError {
|
||||
error!("Environment error {:?}", e);
|
||||
ServerError::Environment
|
||||
}
|
||||
}
|
||||
|
||||
/* impl From<r2d2::Error> for ServerError {
|
||||
fn from(_: r2d2::Error) -> ServerError {
|
||||
ServerError::R2D2Error
|
||||
}
|
||||
} */
|
||||
|
||||
impl From<diesel::result::Error> for ServerError {
|
||||
fn from(err: diesel::result::Error) -> ServerError {
|
||||
error!("Database error {:?}", err);
|
||||
match err {
|
||||
diesel::result::Error::NotFound => ServerError::User("Username not found.".to_string()),
|
||||
_ => ServerError::Diesel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<argonautica::Error> for ServerError {
|
||||
fn from(e: argonautica::Error) -> ServerError {
|
||||
error!("Authentication error {:?}", e);
|
||||
ServerError::Argonautic
|
||||
}
|
||||
}
|
||||
impl From<tera::Error> for ServerError {
|
||||
fn from(e: tera::Error) -> ServerError {
|
||||
error!("Template error {:?}", e);
|
||||
ServerError::Template(e)
|
||||
}
|
||||
}
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
dotenv().ok();
|
||||
env_logger::init();
|
||||
|
||||
println!("Running on: http://127.0.0.1:8156");
|
||||
HttpServer::new(|| {
|
||||
let tera = Tera::new("templates/**/*").expect("failed to initialize the templates");
|
||||
let generated = generate();
|
||||
App::new()
|
||||
.wrap(Logger::default())
|
||||
.wrap(IdentityService::new(
|
||||
CookieIdentityPolicy::new(&[0; 32])
|
||||
.name("auth-cookie")
|
||||
.secure(false),
|
||||
))
|
||||
.data(tera)
|
||||
.service(actix_web_static_files::ResourceFiles::new(
|
||||
"/static", generated,
|
||||
))
|
||||
// directly go to the main page of Freie-Hochschule-Stuttgart
|
||||
.route("/", web::get().to(views::redirect_fhs))
|
||||
// admin block
|
||||
.service(
|
||||
web::scope("/admin")
|
||||
// list all links
|
||||
.route("/index/", web::get().to(views::index))
|
||||
// invite users
|
||||
.route("/signup/", web::get().to(views::signup))
|
||||
.route("/signup/", web::post().to(views::process_signup))
|
||||
// logout
|
||||
.route("/logout/", web::to(views::logout))
|
||||
// submit a new url for shortening
|
||||
.route("/submit/", web::get().to(views::submission))
|
||||
.route("/submit/", web::post().to(views::process_submission))
|
||||
// view an existing url
|
||||
.service(
|
||||
web::scope("/view")
|
||||
.route("/{redirect_id}", web::get().to(views::view_link)),
|
||||
)
|
||||
// login to the admin area
|
||||
.route("/login/", web::get().to(views::login))
|
||||
.route("/login/", web::post().to(views::process_login)),
|
||||
)
|
||||
// redirect to the url hidden behind the code
|
||||
.route("/{redirect_id}", web::get().to(views::redirect))
|
||||
})
|
||||
.bind("127.0.0.1:8156")?
|
||||
.run()
|
||||
.await
|
||||
}
|
85
src/models.rs
Normal file
85
src/models.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use crate::{forms::LinkForm, ServerError};
|
||||
|
||||
use super::schema::{links, users};
|
||||
use argonautica::Hasher;
|
||||
use diesel::{Insertable, Queryable};
|
||||
use dotenv::dotenv;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Queryable, Serialize)]
|
||||
pub struct User {
|
||||
pub id: i32,
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Insertable)]
|
||||
#[table_name = "users"]
|
||||
pub struct NewUser {
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
impl NewUser {
|
||||
pub(crate) fn new(
|
||||
username: String,
|
||||
email: String,
|
||||
password: String,
|
||||
) -> Result<Self, ServerError> {
|
||||
dotenv().ok();
|
||||
|
||||
let secret = std::env::var("SECRET_KEY")?;
|
||||
|
||||
let hash = Hasher::default()
|
||||
.with_password(password)
|
||||
.with_secret_key(secret)
|
||||
.hash()
|
||||
.unwrap();
|
||||
|
||||
Ok(NewUser {
|
||||
username,
|
||||
email,
|
||||
password: hash,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct LoginUser {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug, Queryable)]
|
||||
pub struct Link {
|
||||
pub id: i32,
|
||||
pub title: String,
|
||||
pub target: String,
|
||||
pub code: String,
|
||||
pub author: i32,
|
||||
pub created_at: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Insertable)]
|
||||
#[table_name = "links"]
|
||||
pub struct NewLink {
|
||||
pub title: String,
|
||||
pub target: String,
|
||||
pub code: String,
|
||||
pub author: i32,
|
||||
pub created_at: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
impl NewLink {
|
||||
pub(crate) fn from_link_form(form: LinkForm, uid: i32) -> Self {
|
||||
Self {
|
||||
title: form.title,
|
||||
target: form.target,
|
||||
code: form.code,
|
||||
author: uid,
|
||||
created_at: chrono::Local::now().naive_utc(),
|
||||
}
|
||||
}
|
||||
}
|
23
src/schema.rs
Normal file
23
src/schema.rs
Normal file
@ -0,0 +1,23 @@
|
||||
table! {
|
||||
links (id) {
|
||||
id -> Integer,
|
||||
title -> Text,
|
||||
target -> Text,
|
||||
code -> Text,
|
||||
author -> Integer,
|
||||
created_at -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
users (id) {
|
||||
id -> Integer,
|
||||
username -> Text,
|
||||
email -> Text,
|
||||
password -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
joinable!(links -> users (author));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(links, users,);
|
281
src/views.rs
Normal file
281
src/views.rs
Normal file
@ -0,0 +1,281 @@
|
||||
use actix_identity::Identity;
|
||||
|
||||
use actix_web::{web, HttpResponse};
|
||||
use qrcodegen::{QrCode, QrCodeEcc};
|
||||
|
||||
use crate::ServerError;
|
||||
|
||||
use super::forms::LinkForm;
|
||||
use super::models::{Link, LoginUser, NewLink, NewUser, User};
|
||||
use argonautica::Verifier;
|
||||
use diesel::sqlite::SqliteConnection;
|
||||
use diesel::{prelude::*, result::Error::NotFound};
|
||||
use dotenv::dotenv;
|
||||
use tera::{Context, Tera};
|
||||
|
||||
fn establish_connection() -> Result<SqliteConnection, ServerError> {
|
||||
dotenv().ok();
|
||||
|
||||
let database_url = std::env::var("DATABASE_URL")?;
|
||||
|
||||
match SqliteConnection::establish(&database_url) {
|
||||
Ok(c) => Ok(c),
|
||||
Err(e) => {
|
||||
info!("Error connecting to database: {}, {}", database_url, e);
|
||||
Err(ServerError::User(
|
||||
"Error connecting to Database".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Show the list of all available links if a user is authenticated
|
||||
pub(crate) async fn index(
|
||||
tera: web::Data<Tera>,
|
||||
id: Identity,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
use super::schema::links::dsl::links;
|
||||
use super::schema::users::dsl::users;
|
||||
if let Some(id) = id.identity() {
|
||||
let connection = establish_connection()?;
|
||||
let all_links: Vec<(Link, User)> = links.inner_join(users).load(&connection)?;
|
||||
|
||||
let mut data = Context::new();
|
||||
data.insert("name", &id);
|
||||
data.insert("title", "Links der Freien Hochschule Stuttgart");
|
||||
data.insert("links_per_users", &all_links);
|
||||
|
||||
let rendered = tera.render("index.html", &data)?;
|
||||
Ok(HttpResponse::Ok().body(rendered))
|
||||
} else {
|
||||
Ok(HttpResponse::TemporaryRedirect()
|
||||
.set_header(actix_web::http::header::LOCATION, "/login/")
|
||||
.body("Redirect to /login/"))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn view_link(
|
||||
tera: web::Data<Tera>,
|
||||
id: Identity,
|
||||
link_id: web::Path<String>,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
println!("Viewing link!");
|
||||
use super::schema::links::dsl::{code, links};
|
||||
if let Some(id) = id.identity() {
|
||||
let connection = establish_connection()?;
|
||||
let link: Link = links
|
||||
.filter(code.eq(&link_id.0))
|
||||
.first::<Link>(&connection)?;
|
||||
|
||||
let qr =
|
||||
QrCode::encode_text(&format!("http://fhs.li/{}", &link_id.0), QrCodeEcc::Low).unwrap();
|
||||
let svg = qr.to_svg_string(4);
|
||||
|
||||
let mut data = Context::new();
|
||||
data.insert("name", &id);
|
||||
data.insert(
|
||||
"title",
|
||||
&format!("Links {} der Freien Hochschule Stuttgart", link_id.0),
|
||||
);
|
||||
data.insert("link", &link);
|
||||
data.insert("qr", &svg);
|
||||
|
||||
let rendered = tera.render("view_link.html", &data)?;
|
||||
Ok(HttpResponse::Ok().body(rendered))
|
||||
} else {
|
||||
Ok(HttpResponse::TemporaryRedirect()
|
||||
.set_header(actix_web::http::header::LOCATION, "/login/")
|
||||
.body("Redirect to /login/"))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn signup(
|
||||
tera: web::Data<Tera>,
|
||||
id: Identity,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
if let Some(id) = id.identity() {
|
||||
let mut data = Context::new();
|
||||
data.insert("title", "Sign Up");
|
||||
data.insert("name", &id);
|
||||
|
||||
let rendered = tera.render("signup.html", &data)?;
|
||||
Ok(HttpResponse::Ok().body(rendered))
|
||||
} else {
|
||||
Ok(HttpResponse::TemporaryRedirect()
|
||||
.set_header(actix_web::http::header::LOCATION, "/login/")
|
||||
.body("Redirect to /login/"))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn process_signup(
|
||||
data: web::Form<NewUser>,
|
||||
id: Identity,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
if let Some(_id) = id.identity() {
|
||||
use super::schema::users;
|
||||
|
||||
let connection = establish_connection()?;
|
||||
let new_user = NewUser::new(
|
||||
data.username.clone(),
|
||||
data.email.clone(),
|
||||
data.password.clone(),
|
||||
)?;
|
||||
|
||||
diesel::insert_into(users::table)
|
||||
.values(&new_user)
|
||||
.execute(&connection)?;
|
||||
|
||||
println!("{:?}", data);
|
||||
Ok(HttpResponse::Ok().body(format!("Successfully saved user: {}", data.username)))
|
||||
} else {
|
||||
Ok(HttpResponse::TemporaryRedirect()
|
||||
.set_header(actix_web::http::header::LOCATION, "/login/")
|
||||
.body("Redirect to /login/"))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn login(
|
||||
tera: web::Data<Tera>,
|
||||
id: Identity,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
let mut data = Context::new();
|
||||
data.insert("title", "Login");
|
||||
|
||||
if let Some(_id) = id.identity() {
|
||||
return Ok(HttpResponse::TemporaryRedirect()
|
||||
.set_header(actix_web::http::header::LOCATION, "/index/")
|
||||
.body("Redirect to /index/"));
|
||||
}
|
||||
|
||||
let rendered = tera.render("login.html", &data)?;
|
||||
Ok(HttpResponse::Ok().body(rendered))
|
||||
}
|
||||
|
||||
pub(crate) async fn process_login(
|
||||
data: web::Form<LoginUser>,
|
||||
id: Identity,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
use super::schema::users::dsl::{username, users};
|
||||
|
||||
let connection = establish_connection()?;
|
||||
let user = users
|
||||
.filter(username.eq(&data.username))
|
||||
.first::<User>(&connection);
|
||||
|
||||
match user {
|
||||
Ok(u) => {
|
||||
dotenv().ok();
|
||||
let secret = std::env::var("SECRET_KEY")?;
|
||||
|
||||
let valid = Verifier::default()
|
||||
.with_hash(u.password)
|
||||
.with_password(data.password.clone())
|
||||
.with_secret_key(secret)
|
||||
.verify()?;
|
||||
|
||||
if valid {
|
||||
let session_token = u.username;
|
||||
id.remember(session_token);
|
||||
|
||||
Ok(HttpResponse::TemporaryRedirect()
|
||||
.set_header(actix_web::http::header::LOCATION, "/index/")
|
||||
.body("Redirect to /index/"))
|
||||
} else {
|
||||
Ok(HttpResponse::TemporaryRedirect()
|
||||
.set_header(actix_web::http::header::LOCATION, "/login/")
|
||||
.body("Redirect to /login/"))
|
||||
}
|
||||
}
|
||||
Err(_e) => Ok(HttpResponse::TemporaryRedirect()
|
||||
.set_header(actix_web::http::header::LOCATION, "/login/")
|
||||
.body("Redirect to /login/")),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn logout(id: Identity) -> Result<HttpResponse, ServerError> {
|
||||
id.forget();
|
||||
Ok(HttpResponse::TemporaryRedirect()
|
||||
.set_header(actix_web::http::header::LOCATION, "/login/")
|
||||
.body("Redirect to /login/"))
|
||||
}
|
||||
|
||||
pub(crate) async fn redirect(
|
||||
tera: web::Data<Tera>,
|
||||
data: web::Path<String>,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
use super::schema::links::dsl::{code, links};
|
||||
let connection = establish_connection()?;
|
||||
|
||||
let link = links.filter(code.eq(&data.0)).first::<Link>(&connection);
|
||||
match link {
|
||||
Ok(link) => Ok(HttpResponse::TemporaryRedirect()
|
||||
.set_header(actix_web::http::header::LOCATION, link.target.clone())
|
||||
.body(format!("Redirect to {}", link.target))),
|
||||
Err(NotFound) => {
|
||||
let mut data = Context::new();
|
||||
data.insert("title", "Wurde gelöscht");
|
||||
let rendered = tera.render("not_found.html", &data)?;
|
||||
Ok(HttpResponse::NotFound().body(rendered))
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn redirect_fhs() -> Result<HttpResponse, ServerError> {
|
||||
Ok(HttpResponse::TemporaryRedirect().set_header(
|
||||
actix_web::http::header::LOCATION,
|
||||
"https://www.freie-hochschule-stuttgart.de",
|
||||
).body("If you are not redirected automatically go to https://www.freie-hochschule-stuttgart.de"))
|
||||
}
|
||||
|
||||
pub(crate) async fn submission(
|
||||
tera: web::Data<Tera>,
|
||||
id: Identity,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
if let Some(id) = id.identity() {
|
||||
let mut data = Context::new();
|
||||
data.insert("title", "Submit a Post");
|
||||
|
||||
data.insert("name", &id);
|
||||
let rendered = tera.render("submission.html", &data)?;
|
||||
return Ok(HttpResponse::Ok().body(rendered));
|
||||
}
|
||||
Ok(HttpResponse::TemporaryRedirect()
|
||||
.set_header(actix_web::http::header::LOCATION, "/login/")
|
||||
.body("Redirect to /login/"))
|
||||
}
|
||||
|
||||
pub(crate) async fn process_submission(
|
||||
data: web::Form<LinkForm>,
|
||||
id: Identity,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
if let Some(id) = id.identity() {
|
||||
use super::schema::users::dsl::{username, users};
|
||||
|
||||
let connection = establish_connection()?;
|
||||
let user: Result<User, diesel::result::Error> =
|
||||
users.filter(username.eq(id)).first(&connection);
|
||||
|
||||
match user {
|
||||
Ok(u) => {
|
||||
use super::schema::links;
|
||||
let new_post = NewLink::from_link_form(data.into_inner(), u.id);
|
||||
|
||||
diesel::insert_into(links::table)
|
||||
.values(&new_post)
|
||||
.execute(&connection)?;
|
||||
|
||||
return Ok(HttpResponse::TemporaryRedirect()
|
||||
.set_header(actix_web::http::header::LOCATION, "/index/")
|
||||
.body("Redirect to /index/"));
|
||||
}
|
||||
Err(_e) => Ok(HttpResponse::TemporaryRedirect()
|
||||
.set_header(actix_web::http::header::LOCATION, "/login/")
|
||||
.body("Redirect to /login/")),
|
||||
}
|
||||
} else {
|
||||
Ok(HttpResponse::TemporaryRedirect()
|
||||
.set_header(actix_web::http::header::LOCATION, "/login/")
|
||||
.body("Redirect to /login/"))
|
||||
}
|
||||
}
|
48
static/admin.css
Normal file
48
static/admin.css
Normal file
@ -0,0 +1,48 @@
|
||||
|
||||
|
||||
form {
|
||||
width: 100%;
|
||||
}
|
||||
.center {
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
margin-left: -400px;
|
||||
margin-top: -300px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
nav ol {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
background-color: #333;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
nav li a {
|
||||
display: block;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 14px 16px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
nav li {
|
||||
float: left;
|
||||
}
|
||||
nav li a:hover {
|
||||
background-color: #111;
|
||||
}
|
||||
|
||||
nav li {
|
||||
border-right: 1px solid #bbb;
|
||||
}
|
||||
|
||||
nav li:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
svg {
|
||||
width: 100px;
|
||||
}
|
34
static/style.css
Normal file
34
static/style.css
Normal file
@ -0,0 +1,34 @@
|
||||
|
||||
*, *:before, *:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.center {
|
||||
position: absolute;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -200px;
|
||||
margin-top: -200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30px;
|
||||
color: #333;
|
||||
}
|
||||
.center input {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
margin: 5px;
|
||||
border-radius: 1px;
|
||||
border: 1px solid rgb(90, 90, 90);
|
||||
font-family: inherit;
|
||||
background-color: #eae9ea;
|
||||
}
|
24
templates/admin.html
Normal file
24
templates/admin.html
Normal file
@ -0,0 +1,24 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block head %}
|
||||
<link rel="stylesheet" href="/static/admin.css">
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="admin">
|
||||
<nav>
|
||||
<ol>
|
||||
<li><a href="/admin/index/">Liste</a></li>
|
||||
<li><a href="/admin/submit/">Hinzufügen</a></li>
|
||||
<li><a href="/admin/signup/">Einladen</a></li>
|
||||
<li style="float:right"><a href="/admin/logout/">Abmelden</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="center">
|
||||
<h1>Herzlich willkommen {{ name }}!</h1>
|
||||
{% block admin %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
21
templates/base.html
Normal file
21
templates/base.html
Normal file
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{title}}</title>
|
||||
<meta name="author" content="Franz Dietrich">
|
||||
<meta http-equiv="robots" content="[noindex|nofollow]">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="content">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
12
templates/index.html
Normal file
12
templates/index.html
Normal file
@ -0,0 +1,12 @@
|
||||
{% extends "admin.html" %}
|
||||
|
||||
{% block admin %}
|
||||
{% for links_user in links_per_users %}
|
||||
{% set l = links_user[0] %}
|
||||
{% set u = links_user[1] %}
|
||||
<div>
|
||||
<a href="/admin/view/{{l.code}}"><span>{{l.code}}:</span>{{ l.target }}</a>
|
||||
<small>{{ u.username }}</small>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
18
templates/login.html
Normal file
18
templates/login.html
Normal file
@ -0,0 +1,18 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="center">
|
||||
<form action="" method="POST">
|
||||
<div>
|
||||
<label for="username">Benutzername:</label>
|
||||
<input type="text" name="username">
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Passwort:</label>
|
||||
<input type="password" name="password">
|
||||
</div>
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
<h2> </h2>
|
||||
</div>
|
||||
{% endblock %}
|
8
templates/not_found.html
Normal file
8
templates/not_found.html
Normal file
@ -0,0 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="center">
|
||||
<h3>This Link has not been found or has been deleted</h3>
|
||||
<h2> </h2>
|
||||
</div>
|
||||
{% endblock %}
|
20
templates/signup.html
Normal file
20
templates/signup.html
Normal file
@ -0,0 +1,20 @@
|
||||
{% extends "admin.html" %}
|
||||
|
||||
{% block admin %}
|
||||
<form action="" method="POST">
|
||||
<div>
|
||||
<label for="username">Benutzername:</label>
|
||||
<input type="text" name="username">
|
||||
</div>
|
||||
<div>
|
||||
<label for="email">E-mail:</label>
|
||||
<input type="email" name="email">
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Passwort:</label>
|
||||
<input type="password" name="password">
|
||||
</div>
|
||||
<input type="submit" value="Einladen">
|
||||
</form>
|
||||
<h2> </h2>
|
||||
{% endblock %}
|
19
templates/submission.html
Normal file
19
templates/submission.html
Normal file
@ -0,0 +1,19 @@
|
||||
{% extends "admin.html" %}
|
||||
|
||||
{% block admin %}
|
||||
<form action="" method="POST">
|
||||
<div>
|
||||
<label for="title">Beschreibung:</label>
|
||||
<input type="text" name="title">
|
||||
</div>
|
||||
<div>
|
||||
<label for="target">Ziel:</label>
|
||||
<input type="text" name="target">
|
||||
</div>
|
||||
<div>
|
||||
<label for="code">Code:</label>
|
||||
<input type="text" name="code">
|
||||
</div>
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
{% endblock %}
|
31
templates/view_link.html
Normal file
31
templates/view_link.html
Normal file
@ -0,0 +1,31 @@
|
||||
{% extends "admin.html" %}
|
||||
|
||||
{% block admin %}
|
||||
<h1>The Link {{ link.code }}</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Beschreibung:</td>
|
||||
<td>{{ link.title }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Code:</td>
|
||||
<td>{{ link.code }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kurzlink:</td>
|
||||
<td><a href="https://fhs.li/{{ link.code }}">https://fhs.li/{{ link.code }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ziel:</td>
|
||||
<td>{{ link.target }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QR-Code</td>
|
||||
<td>{{ qr | safe }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user