Move language meta-data to JSON API

This commit is contained in:
Konrad Borowski 2019-04-11 17:02:33 +01:00
parent 1d998ed2f4
commit 0927449791
3 changed files with 51 additions and 33 deletions

View File

@ -8,7 +8,8 @@ use actix_diesel::{AsyncError, Database};
use actix_web::error::InternalError;
use actix_web::fs::{NamedFile, StaticFiles};
use actix_web::http::header::{
CONTENT_SECURITY_POLICY, LOCATION, REFERRER_POLICY, X_FRAME_OPTIONS, X_XSS_PROTECTION,
CACHE_CONTROL, CONTENT_SECURITY_POLICY, LOCATION, REFERRER_POLICY, X_FRAME_OPTIONS,
X_XSS_PROTECTION,
};
use actix_web::http::{Method, StatusCode};
use actix_web::middleware::{DefaultHeaders, Logger};
@ -18,7 +19,7 @@ use askama::actix_web::TemplateIntoResponse;
use askama::Template;
use chrono::{DateTime, Duration, Utc};
use diesel::prelude::*;
use futures::future::{self, Either};
use futures::future;
use futures::prelude::*;
use lazy_static::lazy_static;
use log::info;
@ -26,7 +27,7 @@ use pulldown_cmark::{html, Options, Parser};
use rand::prelude::*;
use schema::{languages, pastes};
use serde::de::IgnoredAny;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use std::{env, io};
type AsyncResponse = Box<dyn Future<Item = HttpResponse, Error = actix_web::Error>>;
@ -41,8 +42,6 @@ struct Index {
struct Language {
id: i32,
name: String,
highlighter_mode: Option<String>,
mime: String,
}
fn index(db: State<Database<PgConnection>>) -> AsyncResponse {
@ -55,12 +54,7 @@ fn fetch_languages(
db: &Database<PgConnection>,
) -> impl Future<Item = Vec<Language>, Error = actix_web::Error> {
languages::table
.select((
languages::language_id,
languages::name,
languages::highlighter_mode,
languages::mime,
))
.select((languages::language_id, languages::name))
.order((languages::priority.asc(), languages::name.asc()))
.load_async(&db)
.map_err(|e| InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR).into())
@ -179,10 +173,10 @@ fn display_paste(
})
.map_err(|e| InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR).into())
.and_then(|(db, paste)| match paste {
None => Either::A(future::ok(
None => future::Either::A(future::ok(
HttpResponse::NotFound().body(PasteNotFound.render().unwrap()),
)),
Some(paste) => Either::B(fetch_languages(&db).and_then(|languages| {
Some(paste) => future::Either::B(fetch_languages(&db).and_then(|languages| {
DisplayPaste {
languages,
paste: paste.into_paste(),
@ -246,6 +240,28 @@ fn favicon(_: ()) -> io::Result<NamedFile> {
NamedFile::open("static/favicon.ico")
}
#[derive(Serialize, Queryable)]
#[serde(rename_all = "camelCase")]
struct ApiLanguage {
mode: Option<String>,
mime: String,
}
fn api_language(db: State<Database<PgConnection>>, id: Path<i32>) -> AsyncResponse {
languages::table
.find(id.into_inner())
.select((languages::highlighter_mode, languages::mime))
.get_optional_result_async(&db)
.map_err(|e| InternalError::new(e, StatusCode::INTERNAL_SERVER_ERROR).into())
.map(|json: Option<ApiLanguage>| match json {
Some(json) => HttpResponse::Ok()
.header(CACHE_CONTROL, "public, max-age=14400")
.json(json),
None => HttpResponse::NotFound().finish(),
})
.responder()
}
fn main() -> io::Result<()> {
env_logger::init();
let db = Database::open(env::var("DATABASE_URL").expect("DATABASE_URL required"));
@ -257,13 +273,10 @@ fn main() -> io::Result<()> {
.header(
CONTENT_SECURITY_POLICY,
concat!(
"default-src 'none'; ",
"script-src 'self'; ",
"style-src 'self'; ",
"default-src 'self'; ",
"img-src *; ",
"object-src 'none'; ",
"base-uri 'none'; ",
"form-action 'self'; ",
"frame-ancestors 'none'",
),
)
@ -281,6 +294,9 @@ fn main() -> io::Result<()> {
r.method(Method::GET).with(display_paste)
})
.resource("/{identifier}/raw", |r| r.method(Method::GET).with(raw))
.resource("/api/v0/language/{id}", |r| {
r.method(Method::GET).with(api_language)
})
})
.bind("127.0.0.1:8080")?
.run();

View File

@ -17,10 +17,23 @@ const editor = CodeMirror.fromTextArea(document.getElementById('code'), {
minLines: 40,
})
const language = document.getElementById('language')
function updateHighlighters() {
const futures = new Map()
function fetchLanguage(id) {
if (futures.has(id)) {
return futures.get(id)
}
const future = fetch(`/api/v0/language/${id}`).then(x => x.json())
futures.set(id, future)
return future
}
async function updateHighlighters() {
const option = language.selectedOptions[0]
editor.setOption('mode', option.getAttribute('data-mime'))
CodeMirror.autoLoadMode(editor, option.getAttribute('data-highlighter-mode'))
const initialValue = option.value
const { mime, mode } = await fetchLanguage(initialValue)
if (initialValue === option.value) {
editor.setOption('mode', mime)
CodeMirror.autoLoadMode(editor, mode)
}
}
language.addEventListener('change', updateHighlighters)
updateHighlighters()

View File

@ -1,16 +1,5 @@
<select id=language name=language>
{% for language in languages %}
<option
value="{{language.id}}"
{% match language.highlighter_mode %}
{% when Some with (highlighter_mode) %}
data-highlighter-mode="{{highlighter_mode}}"
{% when None %}
{% endmatch %}
data-mime="{{language.mime}}"
{% if language.id == selected_language %}
selected
{% endif %}
>{{language.name}}</option>
<option value="{{language.id}}">{{language.name}}</option>
{% endfor %}
</select>
</select>