mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-05-10 12:25:02 +00:00
Switch from ajv to io-ts for config validation; validate configs on save in the API/dashboard; start work on creating io-ts schemas for all plugins
This commit is contained in:
parent
b230a73a6f
commit
da114c0e60
14 changed files with 256 additions and 41 deletions
|
@ -3,6 +3,19 @@ const apiUrl = process.env.API_URL;
|
|||
|
||||
type QueryParamObject = { [key: string]: string | null };
|
||||
|
||||
export class ApiError extends Error {
|
||||
protected body: object;
|
||||
protected status: number;
|
||||
protected res: Response;
|
||||
|
||||
constructor(message: string, body: object, status: number, res: Response) {
|
||||
super(message);
|
||||
this.body = body;
|
||||
this.status = status;
|
||||
this.res = res;
|
||||
}
|
||||
}
|
||||
|
||||
function buildQueryString(params: QueryParamObject) {
|
||||
if (Object.keys(params).length === 0) return "";
|
||||
|
||||
|
@ -15,7 +28,14 @@ function buildQueryString(params: QueryParamObject) {
|
|||
}
|
||||
|
||||
export function request(resource, fetchOpts: RequestInit = {}) {
|
||||
return fetch(`${apiUrl}/${resource}`, fetchOpts).then(res => res.json());
|
||||
return fetch(`${apiUrl}/${resource}`, fetchOpts).then(async res => {
|
||||
if (!res.ok) {
|
||||
const body = await res.json();
|
||||
throw new ApiError(res.statusText, body, res.status, res);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
});
|
||||
}
|
||||
|
||||
export function get(resource: string, params: QueryParamObject = {}) {
|
||||
|
@ -32,7 +52,7 @@ export function post(resource: string, params: QueryParamObject = {}) {
|
|||
const headers: Record<string, string> = RootStore.state.auth.apiKey
|
||||
? { "X-Api-Key": RootStore.state.auth.apiKey }
|
||||
: {};
|
||||
return request(resource + buildQueryString(params), {
|
||||
return request(resource, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(params),
|
||||
headers: {
|
||||
|
|
|
@ -5,8 +5,17 @@
|
|||
<div v-else>
|
||||
<h2 class="title is-1">Config for {{ guild.name }}</h2>
|
||||
<codemirror v-model="editableConfig" :options="cmConfig"></codemirror>
|
||||
<button class="button" v-on:click="save" :disabled="saving">Save</button>
|
||||
<span v-if="saving">Saving...</span>
|
||||
<div class="editor-footer">
|
||||
<div class="actions">
|
||||
<button class="button" v-on:click="save" :disabled="saving">Save</button>
|
||||
</div>
|
||||
<div class="status">
|
||||
<div class="status-message" v-if="saving">Saving...</div>
|
||||
<div class="status-message error" v-if="errors.length">
|
||||
<div v-for="error in errors">{{ error }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -26,6 +35,7 @@
|
|||
import "codemirror/lib/codemirror.css";
|
||||
import "codemirror/theme/oceanic-next.css";
|
||||
import "codemirror/mode/yaml/yaml.js";
|
||||
import {ApiError} from "../api";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -47,6 +57,7 @@
|
|||
loading: true,
|
||||
saving: false,
|
||||
editableConfig: null,
|
||||
errors: [],
|
||||
cmConfig: {
|
||||
indentWithTabs: false,
|
||||
indentUnit: 2,
|
||||
|
@ -69,10 +80,23 @@
|
|||
methods: {
|
||||
async save() {
|
||||
this.saving = true;
|
||||
await this.$store.dispatch("guilds/saveConfig", {
|
||||
guildId: this.$route.params.guildId,
|
||||
config: this.editableConfig,
|
||||
});
|
||||
this.errors = [];
|
||||
|
||||
try {
|
||||
await this.$store.dispatch("guilds/saveConfig", {
|
||||
guildId: this.$route.params.guildId,
|
||||
config: this.editableConfig,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError && (e.status === 400 || e.status === 422)) {
|
||||
this.errors = e.body.errors || ['Error while saving config'];
|
||||
this.saving = false;
|
||||
return;
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
this.$router.push("/dashboard");
|
||||
},
|
||||
},
|
||||
|
|
|
@ -19,7 +19,6 @@ Vue.mixin({
|
|||
|
||||
import App from "./components/App.vue";
|
||||
import Login from "./components/Login.vue";
|
||||
import { get } from "./api";
|
||||
|
||||
const app = new Vue({
|
||||
router,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { get, hasApiKey, post, setApiKey } from "../api";
|
||||
import { get, post } from "../api";
|
||||
import { ActionTree, Module } from "vuex";
|
||||
import { AuthState, RootState } from "./types";
|
||||
|
||||
|
@ -16,13 +16,16 @@ export const AuthStore: Module<AuthState, RootState> = {
|
|||
|
||||
const storedKey = localStorage.getItem("apiKey");
|
||||
if (storedKey) {
|
||||
const result = await post("auth/validate-key", { key: storedKey });
|
||||
if (result.valid) {
|
||||
await dispatch("setApiKey", storedKey);
|
||||
} else {
|
||||
console.log("Unable to validate key, removing from localStorage");
|
||||
localStorage.removeItem("apiKey");
|
||||
}
|
||||
try {
|
||||
const result = await post("auth/validate-key", { key: storedKey });
|
||||
if (result.valid) {
|
||||
await dispatch("setApiKey", storedKey);
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
console.log("Unable to validate key, removing from localStorage");
|
||||
localStorage.removeItem("apiKey");
|
||||
}
|
||||
|
||||
commit("markInitialAuthLoaded");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue