diff --git a/cut-be/.example.env b/cut-be/.example.env new file mode 100644 index 0000000..c73c40e --- /dev/null +++ b/cut-be/.example.env @@ -0,0 +1,9 @@ +HOST=localhost +PORT=9090 +ENVIRONMENT=development + +OAUTH_ISSUER=https://ratify.daystram.com +CLIENT_SECRET=ratify_client_secret + +REDIS_HOST=localhost +REDIS_PORT=6379 \ No newline at end of file diff --git a/cut-be/.gitignore b/cut-be/.gitignore index c13cccd..175948b 100644 --- a/cut-be/.gitignore +++ b/cut-be/.gitignore @@ -1,6 +1,8 @@ # Created by https://www.toptal.com/developers/gitignore/api/rust,linux,macos,windows,intellij+all,visualstudiocode,vim # Edit at https://www.toptal.com/developers/gitignore?templates=rust,linux,macos,windows,intellij+all,visualstudiocode,vim +.env + ### Intellij+all ### # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 diff --git a/cut-be/Cargo.toml b/cut-be/Cargo.toml index b986da9..7a5dcd1 100644 --- a/cut-be/Cargo.toml +++ b/cut-be/Cargo.toml @@ -7,4 +7,12 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -actix-web = "3" +actix-web = { version = "3", features = ["openssl"] } +dotenv = "0.15.0" +env_logger = "0.8.2" +serde = "1.0.119" +log = "0.4.13" +redis = "0.19.0" +r2d2 = "0.8.9" +r2d2_redis = "0.13.0" +rand = "0.8.2" diff --git a/cut-be/src/app/constants.rs b/cut-be/src/app/constants.rs new file mode 100644 index 0000000..89b556d --- /dev/null +++ b/cut-be/src/app/constants.rs @@ -0,0 +1,4 @@ +pub const VARIANT_SNIPPET: &str = "snippet"; +pub const VARIANT_URL: &str = "url"; + +pub const HASH_LENGTH: usize = 8; diff --git a/cut-be/src/app/controllers/.keep b/cut-be/src/app/controllers/middlewares/mod.rs similarity index 100% rename from cut-be/src/app/controllers/.keep rename to cut-be/src/app/controllers/middlewares/mod.rs diff --git a/cut-be/src/app/controllers/mod.rs b/cut-be/src/app/controllers/mod.rs new file mode 100644 index 0000000..b2b1158 --- /dev/null +++ b/cut-be/src/app/controllers/mod.rs @@ -0,0 +1,2 @@ +pub mod middlewares; +pub mod v1; diff --git a/cut-be/src/app/controllers/v1/cut.rs b/cut-be/src/app/controllers/v1/cut.rs new file mode 100644 index 0000000..bb70413 --- /dev/null +++ b/cut-be/src/app/controllers/v1/cut.rs @@ -0,0 +1,64 @@ +use crate::app::{ + constants, + datatransfers::{ + auth::TokenInfo, + cut::{CreateResponse, Cut}, + }, + handlers, Module, +}; +use crate::core::error::HandlerErrorKind; +use actix_web::{get, post, web, HttpRequest, HttpResponse, Responder}; + +#[get("/{id}")] +pub async fn get_cut_raw(m: web::Data, req: HttpRequest) -> impl Responder { + let id: String = req.match_info().query("id").parse().unwrap(); + match handlers::cut::get_one(m, id) { + Ok(cut) => match cut.variant.as_str() { + constants::VARIANT_SNIPPET => HttpResponse::Ok().body(cut.data), + constants::VARIANT_URL => HttpResponse::TemporaryRedirect() + .header("Location", cut.data) + .finish(), + _ => HttpResponse::NotFound().finish(), + }, + Err(e) => match e.kind { + HandlerErrorKind::CutNotFoundError => HttpResponse::NotFound().finish(), + _ => HttpResponse::InternalServerError().body(format!("{:?}", e)), + }, + } +} + +#[get("/{id}")] +pub async fn get_cut(m: web::Data, req: HttpRequest) -> impl Responder { + let id: String = req.match_info().query("id").parse().unwrap(); + match handlers::cut::get_one(m, id) { + Ok(cut) => HttpResponse::Ok().json(cut), + Err(e) => match e.kind { + HandlerErrorKind::CutNotFoundError => HttpResponse::NotFound().finish(), + _ => HttpResponse::InternalServerError().body(format!("{:?}", e)), + }, + } +} + +#[post("")] +pub async fn post_snippet_create( + m: web::Data, + cut: web::Json, + req: HttpRequest, +) -> impl Responder { + let user: TokenInfo = match handlers::auth::authorize(&m, &req).await { + Ok(resp) => resp, + Err(e) => match e.kind { + HandlerErrorKind::UnauthorizedError => return HttpResponse::Unauthorized().finish(), + _ => return HttpResponse::InternalServerError().finish(), + }, + }; + match cut.variant.as_str() { + constants::VARIANT_SNIPPET => (), + constants::VARIANT_URL => (), + _ => return HttpResponse::BadRequest().finish(), + }; + match handlers::cut::insert(m, user.sub, cut.0) { + Ok(hash) => HttpResponse::Ok().json(CreateResponse { hash: hash }), + Err(e) => HttpResponse::InternalServerError().body(format!("{:?}", e)), + } +} diff --git a/cut-be/src/app/controllers/v1/mod.rs b/cut-be/src/app/controllers/v1/mod.rs new file mode 100644 index 0000000..7907e7f --- /dev/null +++ b/cut-be/src/app/controllers/v1/mod.rs @@ -0,0 +1,2 @@ +pub mod cut; +pub mod ping; diff --git a/cut-be/src/app/controllers/v1/ping.rs b/cut-be/src/app/controllers/v1/ping.rs new file mode 100644 index 0000000..b67f591 --- /dev/null +++ b/cut-be/src/app/controllers/v1/ping.rs @@ -0,0 +1,6 @@ +use actix_web::{get, HttpResponse, Responder}; + +#[get("")] +pub async fn get_ping() -> impl Responder { + HttpResponse::Ok().body("Pong!") +} diff --git a/cut-be/src/app/datatransfers/.keep b/cut-be/src/app/datatransfers/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/cut-be/src/app/datatransfers/auth.rs b/cut-be/src/app/datatransfers/auth.rs new file mode 100644 index 0000000..e5ca16d --- /dev/null +++ b/cut-be/src/app/datatransfers/auth.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct TokenInfo { + pub active: bool, + #[serde(default)] + pub sub: String, + #[serde(default)] + pub client_id: String, +} diff --git a/cut-be/src/app/datatransfers/cut.rs b/cut-be/src/app/datatransfers/cut.rs new file mode 100644 index 0000000..b69edd6 --- /dev/null +++ b/cut-be/src/app/datatransfers/cut.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Cut { + pub name: String, + #[serde(skip)] + pub owner: String, + pub variant: String, + pub metadata: String, + pub data: String, + #[serde(skip_deserializing)] + pub created_at: u64, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CreateResponse { + pub hash: String, +} diff --git a/cut-be/src/app/datatransfers/mod.rs b/cut-be/src/app/datatransfers/mod.rs new file mode 100644 index 0000000..664488d --- /dev/null +++ b/cut-be/src/app/datatransfers/mod.rs @@ -0,0 +1,2 @@ +pub mod auth; +pub mod cut; diff --git a/cut-be/src/app/handlers/.keep b/cut-be/src/app/handlers/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/cut-be/src/app/handlers/auth.rs b/cut-be/src/app/handlers/auth.rs new file mode 100644 index 0000000..0d55e64 --- /dev/null +++ b/cut-be/src/app/handlers/auth.rs @@ -0,0 +1,31 @@ +use crate::app::{datatransfers::auth::TokenInfo, Module}; +use crate::core::error::{HandlerError, HandlerErrorKind}; +use actix_web::{client::Client, web, HttpRequest}; + +pub async fn authorize(m: &web::Data, req: &HttpRequest) -> Result { + let access_token: &str = match req.headers().get("Authorization") { + Some(header_value) => match header_value.to_str() { + Ok(access_token) => access_token.strip_prefix("Bearer ").unwrap_or_else(|| access_token), + Err(_) => return Err(HandlerErrorKind::GeneralError.into()), + }, + _ => return Err(HandlerErrorKind::UnauthorizedError.into()), + }; + match Client::default() + .post(format!("{}/oauth/introspect", m.config.oauth_issuer)) + .header("Content-Type", "application/x-www-form-urlencoded") + .send_body(format!( + "token={}&client_id={}&client_secret={}&token_type_hint=access_token", + access_token, m.config.client_id, m.config.client_secret + )) + .await + { + Ok(mut res) => match res.json::().await { + Ok(token_info) => match token_info.active { + true => Ok(token_info), + false => Err(HandlerErrorKind::UnauthorizedError.into()), + }, + Err(_) => Err(HandlerErrorKind::UnauthorizedError.into()), + }, + Err(_) => Err(HandlerErrorKind::GeneralError.into()), + } +} diff --git a/cut-be/src/app/handlers/cut.rs b/cut-be/src/app/handlers/cut.rs new file mode 100644 index 0000000..aad82f0 --- /dev/null +++ b/cut-be/src/app/handlers/cut.rs @@ -0,0 +1,74 @@ +use crate::app::{constants::HASH_LENGTH, datatransfers::cut::Cut, Module}; +use crate::core::error::{HandlerError, HandlerErrorKind}; +use crate::utils::hash; +use actix_web::web; +use r2d2_redis::redis::Commands; +use std::{ + collections::HashMap, + time::{SystemTime, UNIX_EPOCH}, +}; + +pub fn get_one(m: web::Data, id: String) -> Result { + let rd = &mut m.rd_pool.get()?; + match rd.hgetall::>(id) { + Ok(res) => { + if res.is_empty() { + return Err(HandlerErrorKind::CutNotFoundError.into()); + } + Ok(Cut { + name: res + .get("name") + .ok_or(HandlerErrorKind::CutNotFoundError)? + .to_string(), + owner: res + .get("owner") + .ok_or(HandlerErrorKind::CutNotFoundError)? + .to_string(), + variant: res + .get("variant") + .ok_or(HandlerErrorKind::CutNotFoundError)? + .to_string(), + metadata: res + .get("metadata") + .ok_or(HandlerErrorKind::CutNotFoundError)? + .to_string(), + data: res + .get("data") + .ok_or(HandlerErrorKind::CutNotFoundError)? + .to_string(), + created_at: res + .get("created_at") + .ok_or(HandlerErrorKind::CutNotFoundError)? + .parse()?, + }) + } + Err(e) => Err(e.into()), + } +} + +pub fn insert( + m: web::Data, + user_subject: String, + cut: Cut, +) -> Result { + let rd = &mut m.rd_pool.get()?; + let hash: String = hash::generate(HASH_LENGTH).into(); + let created_at = match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(duration) => duration.as_secs(), + Err(_) => return Err(HandlerErrorKind::GeneralError.into()), + }; + match rd.hset_multiple::( + hash.clone(), + &[ + ("name", cut.name), + ("owner", user_subject), + ("variant", cut.variant), + ("metadata", cut.metadata), + ("data", cut.data), + ("created_at", created_at.to_string()), + ], + ) { + Ok(_) => Ok(hash), + Err(e) => Err(e.into()), + } +} diff --git a/cut-be/src/app/handlers/mod.rs b/cut-be/src/app/handlers/mod.rs new file mode 100644 index 0000000..664488d --- /dev/null +++ b/cut-be/src/app/handlers/mod.rs @@ -0,0 +1,2 @@ +pub mod auth; +pub mod cut; diff --git a/cut-be/src/app/middlewares/.keep b/cut-be/src/app/middlewares/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/cut-be/src/app/mod.rs b/cut-be/src/app/mod.rs new file mode 100644 index 0000000..958ec7a --- /dev/null +++ b/cut-be/src/app/mod.rs @@ -0,0 +1,47 @@ +use crate::core::config; +use actix_web::{middleware::Logger, App, HttpServer}; +use r2d2::Pool; +use r2d2_redis::RedisConnectionManager; + +pub mod constants; +pub mod controllers; +pub mod datatransfers; +pub mod handlers; +pub mod router; + +pub struct Module { + pub config: config::AppConfig, + pub rd_pool: r2d2::Pool, +} + +pub async fn start() -> std::io::Result<()> { + // init AppConfig + let config = config::init(); + + // init Redis + let rd_manager = RedisConnectionManager::new(format!( + "redis://{}:{}/", + config.redis_host, config.redis_port + )) + .unwrap(); + let rd_pool = Pool::builder().max_size(15).build(rd_manager).unwrap(); + log::info!("[INIT] Redis initialized!"); + + // run server + HttpServer::new(move || { + App::new() + .data(Module { + config: config.clone(), + rd_pool: rd_pool.clone(), + }) + .wrap(Logger::default()) + .configure(router::init) + }) + .bind(format!( + "{}:{}", + std::env::var("HOST").expect("HOST is required"), + std::env::var("PORT").expect("PORT is required") + ))? + .run() + .await +} diff --git a/cut-be/src/app/router.rs b/cut-be/src/app/router.rs new file mode 100644 index 0000000..771c58f --- /dev/null +++ b/cut-be/src/app/router.rs @@ -0,0 +1,15 @@ +use crate::app::controllers::v1; +use actix_web::web; + +pub fn init(app: &mut web::ServiceConfig) { + app.service(web::scope("/raw").service(v1::cut::get_cut_raw)) + .service( + web::scope("/api/v1") + .service(web::scope("/ping").service(v1::ping::get_ping)) + .service( + web::scope("/cut") + .service(v1::cut::get_cut) + .service(v1::cut::post_snippet_create), + ), + ); +} diff --git a/cut-be/src/core/config.rs b/cut-be/src/core/config.rs index e69de29..2a74497 100644 --- a/cut-be/src/core/config.rs +++ b/cut-be/src/core/config.rs @@ -0,0 +1,20 @@ +use std::env; + +#[derive(Clone)] +pub struct AppConfig { + pub oauth_issuer: String, + pub client_id: String, + pub client_secret: String, + pub redis_host: String, + pub redis_port: String, +} + +pub fn init() -> AppConfig { + return AppConfig { + oauth_issuer: env::var("OAUTH_ISSUER").expect("OAUTH_ISSUER is required"), + client_id: env::var("CLIENT_ID").expect("CLIENT_ID is required"), + client_secret: env::var("CLIENT_SECRET").expect("CLIENT_SECRET is required"), + redis_host: env::var("REDIS_HOST").expect("REDIS_HOST is required"), + redis_port: env::var("REDIS_PORT").expect("REDIS_PORT is required"), + }; +} diff --git a/cut-be/src/core/error.rs b/cut-be/src/core/error.rs new file mode 100644 index 0000000..6d20656 --- /dev/null +++ b/cut-be/src/core/error.rs @@ -0,0 +1,59 @@ +#[derive(Debug, Copy, Clone)] +pub enum HandlerErrorKind { + UnauthorizedError, + CutNotFoundError, + RedisError, + GeneralError, +} + +#[derive(Debug)] +pub struct HandlerError { + pub kind: HandlerErrorKind, + pub message: String, +} + +impl From for HandlerError { + fn from(error: HandlerErrorKind) -> Self { + match error { + HandlerErrorKind::UnauthorizedError => HandlerError { + kind: HandlerErrorKind::UnauthorizedError, + message: "request unauthorized".into(), + }, + HandlerErrorKind::CutNotFoundError => HandlerError { + kind: HandlerErrorKind::CutNotFoundError, + message: "cut not found".into(), + }, + _ => HandlerError { + kind: HandlerErrorKind::GeneralError, + message: format!("an error has occurred. {:?}", error), + }, + } + } +} + +impl From for HandlerError { + fn from(error: std::num::ParseIntError) -> Self { + HandlerError { + kind: HandlerErrorKind::GeneralError, + message: error.to_string(), + } + } +} + +impl From for HandlerError { + fn from(error: r2d2::Error) -> Self { + HandlerError { + kind: HandlerErrorKind::RedisError, + message: error.to_string(), + } + } +} + +impl From for HandlerError { + fn from(error: r2d2_redis::redis::RedisError) -> Self { + HandlerError { + kind: HandlerErrorKind::RedisError, + message: error.to_string(), + } + } +} diff --git a/cut-be/src/core/mod.rs b/cut-be/src/core/mod.rs new file mode 100644 index 0000000..7404805 --- /dev/null +++ b/cut-be/src/core/mod.rs @@ -0,0 +1,2 @@ +pub mod config; +pub mod error; diff --git a/cut-be/src/main.rs b/cut-be/src/main.rs index 3b1ca80..726350c 100644 --- a/cut-be/src/main.rs +++ b/cut-be/src/main.rs @@ -1,28 +1,14 @@ -use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder}; - -#[get("/")] -async fn hello() -> impl Responder { - HttpResponse::Ok().body("Hello world!") -} - -#[post("/echo")] -async fn echo(req_body: String) -> impl Responder { - HttpResponse::Ok().body(req_body) -} - -async fn manual_hello() -> impl Responder { - HttpResponse::Ok().body("Hey there!") -} +mod app; +mod core; +mod utils; #[actix_web::main] async fn main() -> std::io::Result<()> { - HttpServer::new(|| { - App::new() - .service(hello) - .service(echo) - .route("/hey", web::get().to(manual_hello)) - }) - .bind("0.0.0.0:9090")? - .run() - .await + dotenv::dotenv().ok(); + if std::env::var("RUST_LOG").ok().is_none() { + std::env::set_var("RUST_LOG", "info,actix_web=info"); + } + env_logger::init(); + + app::start().await } diff --git a/cut-be/src/utils/.keep b/cut-be/src/utils/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/cut-be/src/utils/hash.rs b/cut-be/src/utils/hash.rs new file mode 100644 index 0000000..130be61 --- /dev/null +++ b/cut-be/src/utils/hash.rs @@ -0,0 +1,10 @@ +use rand::distributions::Alphanumeric; +use rand::{thread_rng, Rng}; + +pub fn generate(length: usize) -> String { + thread_rng() + .sample_iter(&Alphanumeric) + .take(length) + .map(char::from) + .collect() +} diff --git a/cut-be/src/utils/mod.rs b/cut-be/src/utils/mod.rs new file mode 100644 index 0000000..ec5d33c --- /dev/null +++ b/cut-be/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod hash; diff --git a/cut-fe/babel.config.js b/cut-fe/babel.config.js index 397abca..6d41090 100644 --- a/cut-fe/babel.config.js +++ b/cut-fe/babel.config.js @@ -1,3 +1,64 @@ module.exports = { - presets: ["@vue/cli-plugin-babel/preset"] + presets: ["@vue/cli-plugin-babel/preset"], + plugins: [ + [ + "prismjs", + { + languages: [ + "html", + "css", + "js", + "applescript", + "shell", + "basic", + "brainfuck", + "c", + "cs", + "cpp", + "coffee", + "d", + "dart", + "docker", + "erlang", + "fortran", + "go", + "graphql", + "groovy", + "hs", + "hlsl", + "java", + "json", + "julia", + "kt", + "tex", + "less", + "lisp", + "lua", + "makefile", + "md", + "matlab", + "objc", + "pascal", + "perl", + "php", + "powershell", + "python", + "r", + "rb", + "rust", + "sass", + "scala", + "sql", + "swift", + "toml", + "ts", + "vb", + "wasm", + "xml", + "yml" + ], + plugins: ["line-numbers"] + } + ] + ] }; diff --git a/cut-fe/package.json b/cut-fe/package.json index 221c880..5142f92 100644 --- a/cut-fe/package.json +++ b/cut-fe/package.json @@ -8,21 +8,24 @@ "lint": "vue-cli-service lint" }, "dependencies": { + "@daystram/ratify-client": "^1.2.0", "axios": "^0.21.1", - "core-js": "^3.6.5", - "jwt-decode": "^3.1.2", - "pkce-challenge": "^2.1.0", + "babel-plugin-prismjs": "^2.0.1", + "prismjs": "^1.23.0", "register-service-worker": "^1.7.1", - "uuid": "^8.3.2", "vue": "^2.6.11", "vue-class-component": "^7.2.3", + "vue-prism-editor": "^1.2.2", "vue-property-decorator": "^9.1.2", "vue-router": "^3.2.0", + "vuelidate": "^0.7.6", "vuetify": "^2.2.11", "vuex": "^3.4.0" }, "devDependencies": { + "@types/prismjs": "^1.16.2", "@types/uuid": "^8.3.0", + "@types/vuelidate": "^0.7.13", "@typescript-eslint/eslint-plugin": "^2.33.0", "@typescript-eslint/parser": "^2.33.0", "@vue/cli-plugin-babel": "~4.5.0", diff --git a/cut-fe/src/apis/api.ts b/cut-fe/src/apis/api.ts index 4e007b8..f74f8fd 100644 --- a/cut-fe/src/apis/api.ts +++ b/cut-fe/src/apis/api.ts @@ -1,10 +1,14 @@ -import axios, { AxiosInstance } from "axios"; +import axios, { AxiosInstance, AxiosResponse } from "axios"; +import { ACCESS_TOKEN } from "@daystram/ratify-client"; import { authManager, refreshAuth } from "@/auth"; -import { ACCESS_TOKEN } from "@/auth/AuthManager"; import router from "@/router"; const apiClient: AxiosInstance = axios.create({ - baseURL: "/api/v1/" + baseURL: `${ + process.env.NODE_ENV === "development" + ? process.env.VUE_APP_DEV_BASE_API + : "" + }/api/v1/` }); apiClient.interceptors.response.use( @@ -25,4 +29,13 @@ const withAuth = () => ({ } }); -export default {}; +export default { + cut: { + get: function(hash: string): Promise { + return apiClient.get(`cut/${hash}`); + }, + create: function(cut: object): Promise { + return apiClient.post(`cut`, cut, withAuth()); + } + } +}; diff --git a/cut-fe/src/auth/AuthManager.ts b/cut-fe/src/auth/AuthManager.ts deleted file mode 100644 index fcc9e5f..0000000 --- a/cut-fe/src/auth/AuthManager.ts +++ /dev/null @@ -1,179 +0,0 @@ -import qs from "qs"; -import pkceChallenge from "pkce-challenge"; -import { v4 as uuidv4 } from "uuid"; -import axios, { AxiosResponse } from "axios"; -import jwtDecode from "jwt-decode"; - -export const KEY_STATE = "state"; -export const KEY_CODE = "code"; -export const KEY_TOKEN = "token"; -export const ACCESS_TOKEN = "access_token"; -export const ID_TOKEN = "id_token"; - -interface AuthManagerOptions { - clientId: string; - redirectUri: string; - issuer: string; - storage: TokenStorage; -} - -interface OAuthClient { - token: (tokenRequest: object) => Promise; - logout: (logoutRequest: object) => Promise; -} - -interface User { - subject: string; - given_name: string; - family_name: string; - preferred_username: string; - is_superuser: boolean; -} - -interface TokenStorage { - getItem: (key: string) => string | null; - setItem: (key: string, value: string) => void; - removeItem: (key: string) => void; -} - -export class MemoryStorage implements TokenStorage { - private tokens: { [key: string]: string }; - - constructor() { - this.tokens = {}; - } - - getItem(key: string): string | null { - return this.tokens[key]; - } - - setItem(key: string, value: string): void { - this.tokens[key] = value; - } - - removeItem(key: string): void { - delete this.tokens[key]; - } -} - -export class AuthManager { - private options: AuthManagerOptions; - private storageManager: TokenStorage; - private oauth: OAuthClient; - - constructor(opts: AuthManagerOptions) { - this.options = opts; - this.storageManager = opts.storage; - // code and state will still use sessionStorage (need to persist during page reloads) - const oauthClient = axios.create({ - baseURL: `${this.options.issuer}/oauth/` - }); - this.oauth = { - token: function(tokenRequest: object): Promise { - return oauthClient.post(`token`, qs.stringify(tokenRequest), { - headers: { - "Content-Type": "application/x-www-form-urlencoded" - } - }); - }, - logout: function(logoutRequest: object): Promise { - return oauthClient.post(`logout`, qs.stringify(logoutRequest), { - headers: { - "Content-Type": "application/x-www-form-urlencoded" - } - }); - } - }; - } - - isAuthenticated(): boolean { - return this.getToken(ACCESS_TOKEN) !== ""; - } - - getToken(tokenKey: string): string { - return ( - JSON.parse(this.storageManager.getItem(KEY_TOKEN) || "{}")[tokenKey] || "" - ); - } - - getUser(): User { - return jwtDecode(this.getToken(ID_TOKEN)); - } - - reset() { - this.storageManager.removeItem(KEY_TOKEN); - } - - authorize(immediate?: boolean, scopes?: string[]): void { - window.location.href = `${this.options.issuer}/authorize?${qs.stringify({ - /* eslint-disable @typescript-eslint/camelcase */ - client_id: this.options.clientId, - response_type: "code", - redirect_uri: this.options.redirectUri, - scope: - "openid profile" + (scopes || []).map(scope => " " + scope).join(""), - state: this.getState(), - code_challenge: this.getCodeChallenge(), - code_challenge_method: "S256", - immediate: immediate || false - /* eslint-enable @typescript-eslint/camelcase */ - })}`; - } - - redeemToken(authorizationCode: string): Promise { - return this.oauth - .token({ - /* eslint-disable @typescript-eslint/camelcase */ - client_id: this.options.clientId, - grant_type: "authorization_code", - code: authorizationCode, - code_verifier: this.getCodeVerifier() - /* eslint-enable @typescript-eslint/camelcase */ - }) - .then(response => { - this.storageManager.setItem(KEY_TOKEN, JSON.stringify(response.data)); - return response; - }); - } - - logout(global?: boolean) { - return this.oauth - .logout({ - /* eslint-disable @typescript-eslint/camelcase */ - access_token: this.getToken(ACCESS_TOKEN), - client_id: this.options.clientId, - global: global || false - /* eslint-enable @typescript-eslint/camelcase */ - }) - .then(() => { - this.reset(); - }) - .catch(() => { - this.reset(); - }); - } - - getState(): string { - const state = uuidv4(); - sessionStorage.setItem(KEY_STATE, state); - return state; - } - - checkState(state: string): boolean { - const temp = sessionStorage.getItem(KEY_STATE); - sessionStorage.removeItem(KEY_STATE); - return temp === state; - } - - getCodeChallenge() { - const pkce = pkceChallenge(); - sessionStorage.setItem(KEY_CODE, JSON.stringify(pkce)); - return pkce.code_challenge; - } - - getCodeVerifier() { - const pkce = JSON.parse(sessionStorage.getItem(KEY_CODE) || ""); - sessionStorage.removeItem(KEY_CODE); - return pkce.code_verifier; - } -} diff --git a/cut-fe/src/auth/index.ts b/cut-fe/src/auth/index.ts index afb9c2b..4e0bdcf 100644 --- a/cut-fe/src/auth/index.ts +++ b/cut-fe/src/auth/index.ts @@ -1,16 +1,16 @@ +import { RatifyClient } from "@daystram/ratify-client"; import router from "@/router"; -import { AuthManager, ACCESS_TOKEN } from "@/auth/AuthManager"; import { Route } from "vue-router"; const CLIENT_ID = process.env.VUE_APP_CLIENT_ID; const ISSUER = process.env.VUE_APP_OAUTH_ISSUER; const REDIRECT_URI = `${location.origin}/callback`; -const authManager = new AuthManager({ +const authManager = new RatifyClient({ clientId: CLIENT_ID, redirectUri: REDIRECT_URI, issuer: ISSUER, - storage: sessionStorage + storage: localStorage }); const login = function() { @@ -58,7 +58,7 @@ const refreshAuth = function(destinationPath: string) { }; const authenticatedOnly = function(to: Route, from: Route, next: () => void) { - if (authManager.getToken(ACCESS_TOKEN)) { + if (authManager.isAuthenticated()) { next(); } else { refreshAuth(to.fullPath); @@ -70,7 +70,7 @@ const unAuthenticatedOnly = function( from: object, next: () => void ) { - if (!authManager.getToken(ACCESS_TOKEN)) { + if (!authManager.isAuthenticated()) { next(); } else { router.push({ name: "manage:create" }); diff --git a/cut-fe/src/constants/languages.ts b/cut-fe/src/constants/languages.ts new file mode 100644 index 0000000..e6279cf --- /dev/null +++ b/cut-fe/src/constants/languages.ts @@ -0,0 +1,61 @@ +import Prism from "prismjs"; + +export const languages: { + [name: string]: { + grammar: Prism.Grammar; + language: string; + }; +} = { + Plaintext: { grammar: Prism.languages.text, language: "" }, + HTML: { grammar: Prism.languages.html, language: "html" }, + CSS: { grammar: Prism.languages.css, language: "css" }, + Javascript: { grammar: Prism.languages.js, language: "js" }, + AppleScript: { grammar: Prism.languages.applescript, language: "applescipt" }, + Bash: { grammar: Prism.languages.shell, language: "shell" }, + BASIC: { grammar: Prism.languages.basic, language: "basic" }, + Brainfuck: { grammar: Prism.languages.brainfuck, language: "brainfuck" }, + C: { grammar: Prism.languages.c, language: "c" }, + "C#": { grammar: Prism.languages.cs, language: "cs" }, + "C++": { grammar: Prism.languages.cpp, language: "cpp" }, + CoffeeScript: { grammar: Prism.languages.coffee, language: "coffee" }, + D: { grammar: Prism.languages.d, language: "d" }, + Dart: { grammar: Prism.languages.dart, language: "dart" }, + Docker: { grammar: Prism.languages.docker, language: "docker" }, + Erlang: { grammar: Prism.languages.erlang, language: "erlang" }, + Fortran: { grammar: Prism.languages.fortran, language: "fortran" }, + Go: { grammar: Prism.languages.go, language: "go" }, + GraphQL: { grammar: Prism.languages.graphql, language: "graphql" }, + Groovy: { grammar: Prism.languages.groovy, language: "groovy" }, + Haskell: { grammar: Prism.languages.hs, language: "hs" }, + HLSL: { grammar: Prism.languages.hlsl, language: "hlsl" }, + Java: { grammar: Prism.languages.java, language: "java" }, + JSON: { grammar: Prism.languages.json, language: "json" }, + Julia: { grammar: Prism.languages.julia, language: "julia" }, + Kotlin: { grammar: Prism.languages.kt, language: "kt" }, + LaTeX: { grammar: Prism.languages.tex, language: "tex" }, + Less: { grammar: Prism.languages.less, language: "less" }, + Lisp: { grammar: Prism.languages.lisp, language: "lisp" }, + Lua: { grammar: Prism.languages.lua, language: "lua" }, + Makefile: { grammar: Prism.languages.makefile, language: "makefile" }, + Markdown: { grammar: Prism.languages.md, language: "md" }, + Matlab: { grammar: Prism.languages.matlab, language: "matlab" }, + "Objective-C": { grammar: Prism.languages.objc, language: "objc" }, + Pascal: { grammar: Prism.languages.pascal, language: "pascal" }, + Perl: { grammar: Prism.languages.perl, language: "perl" }, + PHP: { grammar: Prism.languages.php, language: "php" }, + PowerShell: { grammar: Prism.languages.powershell, language: "powershell" }, + Python: { grammar: Prism.languages.python, language: "python" }, + R: { grammar: Prism.languages.r, language: "r" }, + Ruby: { grammar: Prism.languages.rb, language: "rb" }, + Rust: { grammar: Prism.languages.rust, language: "rust" }, + Sass: { grammar: Prism.languages.sass, language: "sass" }, + Scala: { grammar: Prism.languages.scala, language: "scala" }, + SQL: { grammar: Prism.languages.sql, language: "sql" }, + Swift: { grammar: Prism.languages.swift, language: "swift" }, + TOML: { grammar: Prism.languages.toml, language: "toml" }, + TypeScript: { grammar: Prism.languages.ts, language: "ts" }, + "Visual Basic": { grammar: Prism.languages.vb, language: "vb" }, + WebAssembly: { grammar: Prism.languages.wasm, language: "wasm" }, + XML: { grammar: Prism.languages.xml, language: "xml" }, + YAML: { grammar: Prism.languages.yml, language: "yml" } +}; diff --git a/cut-fe/src/main.ts b/cut-fe/src/main.ts index 227c149..e3d82ca 100644 --- a/cut-fe/src/main.ts +++ b/cut-fe/src/main.ts @@ -4,6 +4,8 @@ import "./registerServiceWorker"; import router from "./router"; import store from "./store"; import vuetify from "./plugins/vuetify"; +import "./plugins/prismjs"; +import "./plugins/vuelidate"; import { StatusMixin } from "@/constants/status"; import "@/styles/App.sass"; diff --git a/cut-fe/src/plugins/prismjs.ts b/cut-fe/src/plugins/prismjs.ts new file mode 100644 index 0000000..d8d612b --- /dev/null +++ b/cut-fe/src/plugins/prismjs.ts @@ -0,0 +1,5 @@ +import Vue from "vue"; +import { PrismEditor } from "vue-prism-editor"; +import "vue-prism-editor/dist/prismeditor.min.css"; + +Vue.component("PrismEditor", PrismEditor); diff --git a/cut-fe/src/plugins/vuelidate.ts b/cut-fe/src/plugins/vuelidate.ts new file mode 100644 index 0000000..497cc86 --- /dev/null +++ b/cut-fe/src/plugins/vuelidate.ts @@ -0,0 +1,4 @@ +import Vue from "vue"; +import Vuelidate from "vuelidate"; + +Vue.use(Vuelidate); diff --git a/cut-fe/src/router/index.ts b/cut-fe/src/router/index.ts index 7fd7102..c29ed03 100644 --- a/cut-fe/src/router/index.ts +++ b/cut-fe/src/router/index.ts @@ -8,7 +8,7 @@ import { logout, unAuthenticatedOnly } from "@/auth"; -import { Manage, Create } from "@/views"; +import { Manage, Create, View } from "@/views"; Vue.use(VueRouter); @@ -24,16 +24,24 @@ const routes: Array = [ }, { path: "/", - beforeEnter: authenticatedOnly, component: Manage, - redirect: "/create", children: [ { path: "create", name: "manage:create", + beforeEnter: authenticatedOnly, component: Create, meta: { - title: "Create | Cut" + title: "Create Cut | Cut" + } + }, + { + path: "list", + name: "manage:list", + beforeEnter: authenticatedOnly, + component: {}, + meta: { + title: "My Cuts | Cut" } } ] @@ -56,7 +64,20 @@ const routes: Array = [ beforeEnter: unAuthenticatedOnly, component: callback }, - { path: "*", redirect: { name: "home", query: {} } } + { + path: "*", + component: Manage, + children: [ + { + path: "/:hash", + name: "view", + component: View, + meta: { + title: "View Cut | Cut" + } + } + ] + } ]; const router = new VueRouter({ diff --git a/cut-fe/src/styles/Create.sass b/cut-fe/src/styles/Create.sass new file mode 100644 index 0000000..dc8fa7d --- /dev/null +++ b/cut-fe/src/styles/Create.sass @@ -0,0 +1,25 @@ +.snippet-editor + background: #2d2d2d + color: #ecf0ed + font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace + font-size: 14px + line-height: 1.5 + padding: 20px 5px + +#view-link, #raw-link + font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace + +.v-text-field__details + margin-bottom: 0 !important + +.prism-editor__textarea:focus + outline: none + +.prism-editor__textarea + width: 40960px !important + +.prism-editor__editor + white-space: pre !important + +.prism-editor__container + overflow-x: hidden !important diff --git a/cut-fe/src/styles/prism-atom-dark.css b/cut-fe/src/styles/prism-atom-dark.css new file mode 100644 index 0000000..eff090e --- /dev/null +++ b/cut-fe/src/styles/prism-atom-dark.css @@ -0,0 +1,143 @@ +/** + * atom-dark theme for `prism.js` + * Based on Atom's `atom-dark` theme: https://github.com/atom/atom-dark-syntax + * @author Joe Gibson (@gibsjose) + */ + +code[class*="language-"], +pre[class*="language-"] { + color: #ecf0ed; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + font-family: Inconsolata, Monaco, Consolas, 'Courier New', Courier, monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; + border-radius: 0.3em; +} + +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #1d1f21; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .1em; + border-radius: .3em; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #7C7C7C; +} + +.token.punctuation { + color: #c5c8c6; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.keyword, +.token.tag { + color: #96CBFE; +} + +.token.class-name { + color: #FFFFB6; + text-decoration: underline; +} + +.token.boolean, +.token.constant { + color: #99CC99; +} + +.token.symbol, +.token.deleted { + color: #f92672; +} + +.token.number { + color: #FF73FD; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #68ff70; +} + +.token.variable { + color: #C6C5FE; +} + +.token.operator { + color: #EDEDED; +} + +.token.entity { + color: #FFFFB6; + cursor: help; +} + +.token.url { + color: #96CBFE; +} + +.language-css .token.string, +.style .token.string { + color: #87C38A; +} + +.token.atrule, +.token.attr-value { + color: #F9EE98; +} + +.token.function { + color: #DAD085; +} + +.token.regex { + color: #E9C062; +} + +.token.important { + color: #fd971f; +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} diff --git a/cut-fe/src/views/View.vue b/cut-fe/src/views/View.vue new file mode 100644 index 0000000..987efdb --- /dev/null +++ b/cut-fe/src/views/View.vue @@ -0,0 +1,173 @@ + + + diff --git a/cut-fe/src/views/index.ts b/cut-fe/src/views/index.ts index f3f29f9..4c6c284 100644 --- a/cut-fe/src/views/index.ts +++ b/cut-fe/src/views/index.ts @@ -1,3 +1,4 @@ export const Create = () => import("./manage/Create.vue"); export const Home = () => import("./Home.vue"); export const Manage = () => import("./manage/Manage.vue"); +export const View = () => import("./View.vue"); diff --git a/cut-fe/src/views/manage/Create.vue b/cut-fe/src/views/manage/Create.vue index 8f287b8..bd8e41d 100644 --- a/cut-fe/src/views/manage/Create.vue +++ b/cut-fe/src/views/manage/Create.vue @@ -3,13 +3,188 @@

- Cuts + Create Cut

- MOCK CREATE + + + + Snippet + + + URL + + + File + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FILE EDITOR + + + + + + +
+ + Failed creating cut! + +
+
+ + Create + +
+
+
+
+ + + + + + Cut Created + + + + + + + + + + +
+ + +
+ Use the following link to share your cut. +
+
+ + +
+
+ To get view the raw cut, use the following. +
+
+ +
+
+
+
+
+
@@ -17,11 +192,155 @@ diff --git a/cut-fe/src/views/manage/Manage.vue b/cut-fe/src/views/manage/Manage.vue index 8ba20c8..11cc56f 100644 --- a/cut-fe/src/views/manage/Manage.vue +++ b/cut-fe/src/views/manage/Manage.vue @@ -2,6 +2,7 @@
@@ -9,13 +10,14 @@ + - + - + + + + + + + - + diff --git a/cut-fe/yarn.lock b/cut-fe/yarn.lock index 162e739..33ade11 100644 --- a/cut-fe/yarn.lock +++ b/cut-fe/yarn.lock @@ -846,6 +846,17 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@daystram/ratify-client@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@daystram/ratify-client/-/ratify-client-1.2.0.tgz#46253c3e866d60c255055e00eb5193ef4ad4000c" + integrity sha512-DqeQbGJSaDPZQ9DfixEweebOLp4p7QktzSw4r/K69FKuYoWeWZbR8ZtgilpF2yeGZj9ultT+CdxAEJc7MO8CKA== + dependencies: + axios "^0.21.1" + jwt-decode "^3.1.2" + pkce-challenge "^2.1.0" + qs "^6.9.6" + uuid "^8.3.2" + "@hapi/address@2.x.x": version "2.1.4" resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" @@ -1026,6 +1037,11 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/prismjs@^1.16.2": + version "1.16.2" + resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.16.2.tgz#c130c977191c988cb35e97585da5d580948cc2d2" + integrity sha512-1M/j21xgTde7RPtpJVQebW5rzrquj7S+wnqt4x9uWrIPpr0Ya/uXypcqC2aUQL5gtLXFCKSH7GnjfAijMdfbuA== + "@types/q@^1.5.1": version "1.5.4" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" @@ -1071,6 +1087,13 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f" integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ== +"@types/vuelidate@^0.7.13": + version "0.7.13" + resolved "https://registry.yarnpkg.com/@types/vuelidate/-/vuelidate-0.7.13.tgz#6e7249e1d6b72b3aed14f4439a082e14662c485e" + integrity sha512-CFyaS5yov3HjwoZrQyehiE1cNkyuN0/EDQfpeneWQFC9onJ0ieQL6dgFxN6zzPMHDZ87If3UH9WcgkPB4vbcoQ== + dependencies: + vue "^2.6.11" + "@types/webpack-dev-server@^3.11.0": version "3.11.1" resolved "https://registry.yarnpkg.com/@types/webpack-dev-server/-/webpack-dev-server-3.11.1.tgz#f8f4dac1da226d530bd15a1d5dc34b23ba766ccb" @@ -1955,6 +1978,11 @@ babel-plugin-dynamic-import-node@^2.3.3: dependencies: object.assign "^4.1.0" +babel-plugin-prismjs@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/babel-plugin-prismjs/-/babel-plugin-prismjs-2.0.1.tgz#b56095f423926662259de8f5ee50a7afbcf0fd92" + integrity sha512-GqQGa3xX3Z2ft97oDbGvEFoxD8nKqb3ZVszrOc5H7icnEUA56BIjVYm86hfZZA82uuHLwTIfCXbEKzKG1BzKzg== + babel-plugin-syntax-object-rest-spread@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" @@ -2565,6 +2593,15 @@ cli-width@^3.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== +clipboard@^2.0.0: + version "2.0.6" + resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.6.tgz#52921296eec0fdf77ead1749421b21c968647376" + integrity sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg== + dependencies: + good-listener "^1.2.2" + select "^1.1.2" + tiny-emitter "^2.0.0" + clipboardy@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-2.3.0.tgz#3c2903650c68e46a91b388985bc2774287dba290" @@ -3248,6 +3285,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +delegate@^3.1.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" + integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== + depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -4379,6 +4421,13 @@ globby@^9.2.0: pify "^4.0.1" slash "^2.0.0" +good-listener@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" + integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA= + dependencies: + delegate "^3.1.2" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" @@ -6898,6 +6947,13 @@ pretty-error@^2.0.2: lodash "^4.17.20" renderkid "^2.0.4" +prismjs@^1.23.0: + version "1.23.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.23.0.tgz#d3b3967f7d72440690497652a9d40ff046067f33" + integrity sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA== + optionalDependencies: + clipboard "^2.0.0" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -7003,6 +7059,11 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@^6.9.6: + version "6.9.6" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee" + integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ== + qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -7456,6 +7517,11 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= +select@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" + integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= + selfsigned@^1.10.8: version "1.10.8" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.8.tgz#0d17208b7d12c33f8eac85c41835f27fc3d81a30" @@ -8193,6 +8259,11 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +tiny-emitter@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -8671,6 +8742,11 @@ vue-loader@^15.9.2: vue-hot-reload-api "^2.3.0" vue-style-loader "^4.1.0" +vue-prism-editor@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/vue-prism-editor/-/vue-prism-editor-1.2.2.tgz#023cfd4329848f191aac851f2f5e6c7a8c2e059f" + integrity sha512-Lq2VgVygTx3Whn/tC8gD4m1ajA4lzSyCTqPLZA1Dq/ErbBaZA93FWRblwCoDR7AD2nXhGWuiTzb5ih3guzB7DA== + vue-property-decorator@^9.1.2: version "9.1.2" resolved "https://registry.yarnpkg.com/vue-property-decorator/-/vue-property-decorator-9.1.2.tgz#266a2eac61ba6527e2e68a6933cfb98fddab5457" @@ -8707,6 +8783,11 @@ vue@^2.6.11: resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.12.tgz#f5ebd4fa6bd2869403e29a896aed4904456c9123" integrity sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg== +vuelidate@^0.7.6: + version "0.7.6" + resolved "https://registry.yarnpkg.com/vuelidate/-/vuelidate-0.7.6.tgz#84100c13b943470660d0416642845cd2a1edf4b2" + integrity sha512-suzIuet1jGcyZ4oUSW8J27R2tNrJ9cIfklAh63EbAkFjE380iv97BAiIeolRYoB9bF9usBXCu4BxftWN1Dkn3g== + vuetify-loader@^1.3.0: version "1.6.0" resolved "https://registry.yarnpkg.com/vuetify-loader/-/vuetify-loader-1.6.0.tgz#05df0805b3ab2ff0de198109d34f9da3f69da667"