mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-01-18 14:14:50 +08:00
nuxt demo
This commit is contained in:
parent
45f5bd9460
commit
e29de1f156
1
client/.env.example
Normal file
1
client/.env.example
Normal file
@ -0,0 +1 @@
|
||||
BACKEND_URL="backendurl.com"
|
17
client/.eslintrc.js
Normal file
17
client/.eslintrc.js
Normal file
@ -0,0 +1,17 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
extends: ['@nuxtjs/eslint-config-typescript', 'prettier', 'prettier/vue', 'plugin:prettier/recommended', 'plugin:nuxt/recommended'],
|
||||
plugins: ['prettier'],
|
||||
// add your custom rules here
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-unused-vars': 'warn',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'vue/no-unused-components': 'warn',
|
||||
},
|
||||
};
|
90
client/.gitignore
vendored
Normal file
90
client/.gitignore
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Node template
|
||||
# Logs
|
||||
/logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# Nuxt generate
|
||||
dist
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
|
||||
# IDE / Editor
|
||||
.idea
|
||||
|
||||
# Service worker
|
||||
sw.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Vim swap files
|
||||
*.swp
|
6
client/.prettierrc
Normal file
6
client/.prettierrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 160
|
||||
}
|
20
client/README.md
Normal file
20
client/README.md
Normal file
@ -0,0 +1,20 @@
|
||||
# hangar-client
|
||||
|
||||
## Build Setup
|
||||
|
||||
```bash
|
||||
# install dependencies
|
||||
$ yarn install
|
||||
|
||||
# serve with hot reload at localhost:3000
|
||||
$ yarn dev
|
||||
|
||||
# build for production and launch server
|
||||
$ yarn build
|
||||
$ yarn start
|
||||
|
||||
# generate static project
|
||||
$ yarn generate
|
||||
```
|
||||
|
||||
For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org).
|
4
client/assets/variables.scss
Normal file
4
client/assets/variables.scss
Normal file
@ -0,0 +1,4 @@
|
||||
// Ref: https://github.com/nuxt-community/vuetify-module#customvariables
|
||||
//
|
||||
// The variables you want to modify
|
||||
// $font-size-root: 20px;
|
47
client/components/ProjectList.vue
Normal file
47
client/components/ProjectList.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<v-data-iterator :items="projects" :footer-props="{ itemsPerPageOptions: [5, 15, 25] }" :items-per-page="25">
|
||||
<template #default="props">
|
||||
<v-hover v-for="project in props.items" :key="project.id" v-slot="{ hover }" style="width: 100%; height: 78px" class="d-block mb-3">
|
||||
<NuxtLink :to="`/${project.namespace.owner}/${project.namespace.slug}`">
|
||||
<v-sheet :elevation="hover ? 24 : 0" height="100%" width="100%" tile color="accent" class="transition-swing grow-on-hover mb-3">
|
||||
<v-row no-gutters>
|
||||
<div class="flex-shrink-0">
|
||||
<v-img :src="project.icon_url" :alt="project.name" width="60px" height="60px" class="my-2 ml-2"></v-img>
|
||||
</div>
|
||||
<div class="ml-2">
|
||||
<span class="text-h6">{{ project.name }}</span>
|
||||
<br />
|
||||
<span class="text-subtitle-2">{{ project.description }}</span>
|
||||
</div>
|
||||
</v-row>
|
||||
</v-sheet>
|
||||
</NuxtLink>
|
||||
</v-hover>
|
||||
</template>
|
||||
</v-data-iterator>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { Project } from 'hangar-api';
|
||||
import { PropType } from 'vue';
|
||||
|
||||
@Component
|
||||
export default class ProjectList extends Vue {
|
||||
@Prop({ type: Array as PropType<Project[]>, required: true })
|
||||
projects!: Project[];
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.grow-on-hover {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.grow-on-hover:hover {
|
||||
transform: scale(1.015);
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
25
client/layouts/default.vue
Normal file
25
client/layouts/default.vue
Normal file
@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-app-bar fixed app>
|
||||
<v-toolbar-title v-text="title" />
|
||||
<v-spacer />
|
||||
</v-app-bar>
|
||||
<v-main>
|
||||
<v-container>
|
||||
<nuxt />
|
||||
</v-container>
|
||||
</v-main>
|
||||
<v-footer absolute app>
|
||||
<span>© {{ new Date().getFullYear() }}</span>
|
||||
</v-footer>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Component, Vue } from 'nuxt-property-decorator';
|
||||
|
||||
@Component
|
||||
export default class DefaultLayout extends Vue {
|
||||
title = 'Hangar';
|
||||
}
|
||||
</script>
|
41
client/layouts/error.vue
Normal file
41
client/layouts/error.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<v-app dark>
|
||||
<h1 v-if="error.statusCode === 404">
|
||||
{{ pageNotFound }}
|
||||
</h1>
|
||||
<h1 v-else>
|
||||
{{ otherError }}
|
||||
</h1>
|
||||
<NuxtLink to="/"> Home page </NuxtLink>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
layout: 'empty',
|
||||
props: {
|
||||
error: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pageNotFound: '404 Not Found',
|
||||
otherError: 'An error occurred',
|
||||
};
|
||||
},
|
||||
head() {
|
||||
const title = this.error.statusCode === 404 ? this.pageNotFound : this.otherError;
|
||||
return {
|
||||
title,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
</style>
|
8
client/middleware/README.md
Normal file
8
client/middleware/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
# MIDDLEWARE
|
||||
|
||||
**This directory is not required, you can delete it if you don't want to use it.**
|
||||
|
||||
This directory contains your application middleware.
|
||||
Middleware let you define custom functions that can be run before rendering either a page or a group of pages.
|
||||
|
||||
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware).
|
79
client/nuxt.config.js
Normal file
79
client/nuxt.config.js
Normal file
@ -0,0 +1,79 @@
|
||||
import colors from 'vuetify/es5/util/colors';
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
export default {
|
||||
// Global page headers: https://go.nuxtjs.dev/config-head
|
||||
head: {
|
||||
titleTemplate: '%s - hangar-client',
|
||||
title: 'hangar-client',
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{ hid: 'description', name: 'description', content: '' },
|
||||
],
|
||||
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
|
||||
},
|
||||
|
||||
// Global CSS: https://go.nuxtjs.dev/config-css
|
||||
css: [],
|
||||
|
||||
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
|
||||
plugins: ['~/plugins/api.ts'],
|
||||
|
||||
// Auto import components: https://go.nuxtjs.dev/config-components
|
||||
components: true,
|
||||
|
||||
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
|
||||
buildModules: [
|
||||
// https://go.nuxtjs.dev/typescript
|
||||
'@nuxt/typescript-build',
|
||||
// https://go.nuxtjs.dev/vuetify
|
||||
'@nuxtjs/vuetify',
|
||||
],
|
||||
|
||||
// Modules: https://go.nuxtjs.dev/config-modules
|
||||
modules: [
|
||||
// https://go.nuxtjs.dev/axios
|
||||
'@nuxtjs/axios',
|
||||
// https://go.nuxtjs.dev/pwa
|
||||
'@nuxtjs/pwa',
|
||||
'cookie-universal-nuxt',
|
||||
],
|
||||
|
||||
// Axios module configuration: https://go.nuxtjs.dev/config-axios
|
||||
axios: {
|
||||
baseURL: process.env.BACKEND_URL,
|
||||
},
|
||||
|
||||
// PWA module configuration: https://go.nuxtjs.dev/pwa
|
||||
pwa: {
|
||||
manifest: {
|
||||
lang: 'en',
|
||||
},
|
||||
},
|
||||
|
||||
// Vuetify module configuration: https://go.nuxtjs.dev/config-vuetify
|
||||
vuetify: {
|
||||
customVariables: ['~/assets/variables.scss'],
|
||||
theme: {
|
||||
dark: true,
|
||||
themes: {
|
||||
dark: {
|
||||
primary: colors.blue.darken2,
|
||||
accent: colors.grey.darken3,
|
||||
secondary: colors.amber.darken3,
|
||||
info: colors.teal.lighten1,
|
||||
warning: colors.amber.base,
|
||||
error: colors.deepOrange.accent4,
|
||||
success: colors.green.accent3,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Build Configuration: https://go.nuxtjs.dev/config-build
|
||||
build: {
|
||||
transpile: ['vuex-module-decorators'],
|
||||
},
|
||||
};
|
51
client/package.json
Normal file
51
client/package.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "hangar-client",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "nuxt-ts",
|
||||
"build": "nuxt-ts build",
|
||||
"start": "nuxt-ts start",
|
||||
"generate": "nuxt-ts generate",
|
||||
"lint:js": "eslint --ext \".js,.vue\" --ignore-path .gitignore .",
|
||||
"lint": "yarn lint:js"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,vue}": "eslint"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/typescript-runtime": "^2.0.1",
|
||||
"@nuxtjs/axios": "^5.12.5",
|
||||
"@nuxtjs/pwa": "^3.3.4",
|
||||
"cookie-universal-nuxt": "^2.1.4",
|
||||
"core-js": "^3.8.2",
|
||||
"nuxt": "^2.14.12",
|
||||
"nuxt-property-decorator": "^2.9.1",
|
||||
"vue": "^2.6.12",
|
||||
"vuex": "^3.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/types": "^2.14.12",
|
||||
"@nuxt/typescript-build": "^2.0.4",
|
||||
"@nuxtjs/eslint-config-typescript": "^5.0.0",
|
||||
"@nuxtjs/eslint-module": "^3.0.2",
|
||||
"@nuxtjs/vuetify": "^1.11.3",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.18.0",
|
||||
"eslint-config-prettier": "^7.1.0",
|
||||
"eslint-plugin-nuxt": "^2.0.0",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-vue": "^7.4.1",
|
||||
"husky": "^4.3.8",
|
||||
"lint-staged": "^10.5.3",
|
||||
"prettier": "^2.2.1",
|
||||
"typescript": "^4.1.3",
|
||||
"vuetify": "^2.4.2",
|
||||
"webpack": "^5.16.0"
|
||||
}
|
||||
}
|
20
client/pages/_author/_slug/index.vue
Normal file
20
client/pages/_author/_slug/index.vue
Normal file
@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div>{{ project.name }}</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'nuxt-property-decorator';
|
||||
import { Context } from '@nuxt/types';
|
||||
import { Project } from 'hangar-api';
|
||||
|
||||
@Component
|
||||
export default class ProjectPage extends Vue {
|
||||
project?: Project;
|
||||
|
||||
async asyncData({ $api, params }: Context): Promise<{ project: Project }> {
|
||||
return { project: await $api.request<Project>(`projects/${params.author}/${params.slug}`) };
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
22
client/pages/index.vue
Normal file
22
client/pages/index.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<v-row justify="center" align="center">
|
||||
<v-col cols="12" sm="8" md="6">
|
||||
<ProjectList :projects="projects.result"></ProjectList>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'nuxt-property-decorator';
|
||||
import { Context } from '@nuxt/types';
|
||||
import { PaginatedProjectList } from 'hangar-api';
|
||||
|
||||
@Component
|
||||
export default class Home extends Vue {
|
||||
projects?: PaginatedProjectList;
|
||||
|
||||
async asyncData({ $api }: Context): Promise<{ projects: PaginatedProjectList }> {
|
||||
return { projects: await $api.request<PaginatedProjectList>('projects', 'get', { limit: 25, offset: 0 }) };
|
||||
}
|
||||
}
|
||||
</script>
|
139
client/plugins/api.ts
Normal file
139
client/plugins/api.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import { Context } from '@nuxt/types';
|
||||
import { Inject } from '@nuxt/types/app';
|
||||
import { AxiosPromise } from 'axios';
|
||||
import { ApiSession } from 'hangar-api';
|
||||
import { NuxtAxiosInstance } from '@nuxtjs/axios';
|
||||
import { NuxtCookies } from 'cookie-universal-nuxt';
|
||||
import { Store } from 'vuex';
|
||||
import { ApiSessionType } from '~/types/enums';
|
||||
|
||||
const createApi = ($axios: NuxtAxiosInstance, $cookies: NuxtCookies, store: Store<any>) => {
|
||||
class API {
|
||||
getSession(): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let session: ApiSession;
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + 60000);
|
||||
|
||||
if (store.state.auth.authenticated) {
|
||||
session = $cookies.get('api_session');
|
||||
if (typeof session === 'undefined' || (!isNaN(new Date(session.expires).getTime()) && new Date(session.expires) < date)) {
|
||||
return $axios
|
||||
.post<object, ApiSession>('/api/v1/authenticate', {}, { headers: { 'Content-Type': 'application/json' } })
|
||||
.then((data) => {
|
||||
if (data.type !== 'user') {
|
||||
reject(new Error('Expected user session from user authentication'));
|
||||
} else {
|
||||
$cookies.set('api_session', JSON.stringify(data), {
|
||||
path: '/',
|
||||
maxAge: 60 * 60 * 24 * 7,
|
||||
});
|
||||
store.commit('auth/SET_AUTHED', true);
|
||||
resolve(data.session);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
resolve(session.session);
|
||||
}
|
||||
} else {
|
||||
session = $cookies.get('public_api_session');
|
||||
if (typeof session === 'undefined' || (!isNaN(new Date(session.expires).getTime()) && new Date(session.expires) < date)) {
|
||||
$axios
|
||||
.post<ApiSession>('/api/v1/authenticate', {}, { headers: { 'Content-Type': 'application/json' } })
|
||||
.then(({ data }) => {
|
||||
if (data.type !== ApiSessionType.PUBLIC) {
|
||||
reject(new Error('Expected public session from public authentication'));
|
||||
} else {
|
||||
$cookies.set('public_api_session', JSON.stringify(data), {
|
||||
path: '/',
|
||||
maxAge: 60 * 60 * 24 * 7 * 4,
|
||||
});
|
||||
resolve(data.session);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
resolve(session.session);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
invalidateSession(): void {
|
||||
if (store.state.auth.authenticated) {
|
||||
$cookies.remove('api_session', {
|
||||
path: '/',
|
||||
});
|
||||
} else {
|
||||
store.commit('auth/SET_AUTHED', false);
|
||||
$cookies.remove('public_api_session', {
|
||||
path: '/',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
request<T>(url: string, method: 'get' | 'post' = 'get', data: object = {}): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
return this.getSession().then((session) => {
|
||||
return ($axios({
|
||||
method,
|
||||
url: '/api/v1/' + url,
|
||||
headers: { Authorization: 'HangarApi session="' + session + '"' },
|
||||
data,
|
||||
}) as AxiosPromise<T>)
|
||||
.then(({ data }) => resolve(data))
|
||||
.catch((error) => {
|
||||
if (error.response && (error.response.error === 'Api session expired' || error.response.error === 'Invalid session')) {
|
||||
// This should never happen but just in case we catch it and invalidate the session to definitely get a new one
|
||||
this.invalidateSession();
|
||||
this.request<T>(url, method, data)
|
||||
.then((data) => {
|
||||
resolve(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
reject(error.response.statusText);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new API();
|
||||
};
|
||||
|
||||
type apiType = ReturnType<typeof createApi>;
|
||||
|
||||
declare module 'vue/types/vue' {
|
||||
interface Vue {
|
||||
$api: apiType;
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@nuxt/types' {
|
||||
interface NuxtAppOptions {
|
||||
$api: apiType;
|
||||
}
|
||||
|
||||
interface Context {
|
||||
$api: apiType;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'vuex/types/index' {
|
||||
interface Store<S> {
|
||||
$api: apiType;
|
||||
}
|
||||
}
|
||||
|
||||
export default ({ $axios, app: { $cookies }, store }: Context, inject: Inject) => {
|
||||
inject('api', createApi($axios, $cookies, store));
|
||||
};
|
BIN
client/static/favicon.ico
Normal file
BIN
client/static/favicon.ico
Normal file
Binary file not shown.
14
client/store/auth.ts
Normal file
14
client/store/auth.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { MutationTree } from 'vuex';
|
||||
import { User } from 'hangar-api';
|
||||
|
||||
export const state = () => ({
|
||||
authenticated: false,
|
||||
user: (null as unknown) as User,
|
||||
});
|
||||
|
||||
export type AuthState = ReturnType<typeof state>;
|
||||
|
||||
export const mutations: MutationTree<AuthState> = {
|
||||
SET_USER: (state, user: User) => (state.user = user),
|
||||
SET_AUTHED: (state, auth: boolean) => (state.authenticated = auth),
|
||||
};
|
40
client/tsconfig.json
Normal file
40
client/tsconfig.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2018",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"lib": [
|
||||
"ESNext",
|
||||
"ESNext.AsyncIterable",
|
||||
"DOM"
|
||||
],
|
||||
"esModuleInterop": true,
|
||||
"allowJs": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"experimentalDecorators": true,
|
||||
"baseUrl": ".",
|
||||
"importHelpers": true,
|
||||
"paths": {
|
||||
"~/*": [
|
||||
"./*"
|
||||
],
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
},
|
||||
"types": [
|
||||
"@nuxt/types",
|
||||
"@types/node",
|
||||
"@nuxtjs/axios",
|
||||
"cookie-universal-nuxt",
|
||||
"@nuxtjs/vuetify"
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".nuxt",
|
||||
"dist"
|
||||
]
|
||||
}
|
108
client/types/api.d.ts
vendored
Normal file
108
client/types/api.d.ts
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
/* eslint-disable camelcase */
|
||||
declare module 'hangar-api' {
|
||||
import { ApiSessionType, ProjectCategory, RoleCategory, Visibility } from '~/types/enums';
|
||||
|
||||
interface Model {
|
||||
id: number;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface Named {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Color {
|
||||
value: number;
|
||||
hex: string;
|
||||
}
|
||||
|
||||
interface TagColor {
|
||||
background: string;
|
||||
foreground: string;
|
||||
}
|
||||
|
||||
interface Role {
|
||||
value: string;
|
||||
role_id: number;
|
||||
category: RoleCategory;
|
||||
permission: bigint; // TODO maybe?
|
||||
title: string;
|
||||
color: Color;
|
||||
}
|
||||
|
||||
interface User extends Model, Named {
|
||||
tagline: string | null;
|
||||
joinDate: string;
|
||||
roles: Role[];
|
||||
}
|
||||
|
||||
interface ApiSession {
|
||||
session: string;
|
||||
expires: string;
|
||||
type: ApiSessionType;
|
||||
}
|
||||
|
||||
interface Pagination {
|
||||
limit: number;
|
||||
offset: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
interface ProjectNamespace {
|
||||
owner: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface ProjectStats {
|
||||
views: number;
|
||||
downloads: number;
|
||||
recent_views: number;
|
||||
recent_downloads: number;
|
||||
stars: number;
|
||||
waters: number;
|
||||
}
|
||||
|
||||
interface UserActions {
|
||||
starred: boolean;
|
||||
watching: boolean;
|
||||
}
|
||||
|
||||
interface ProjectSettings {
|
||||
homepage: string | null;
|
||||
issues: string | null;
|
||||
sources: string | null;
|
||||
support: string | null;
|
||||
license: string | null;
|
||||
forumSync: boolean;
|
||||
}
|
||||
|
||||
interface PromotedVersionTag extends Named {
|
||||
data: string;
|
||||
displayData: string;
|
||||
minecraft_version: string;
|
||||
color: TagColor;
|
||||
}
|
||||
|
||||
interface PromotedVersion {
|
||||
version: string;
|
||||
tags: PromotedVersionTag[];
|
||||
}
|
||||
|
||||
interface Project extends Model, Named {
|
||||
namespace: ProjectNamespace;
|
||||
promoted_versions: PromotedVersion[];
|
||||
stats: ProjectStats;
|
||||
category: ProjectCategory;
|
||||
description: string;
|
||||
last_updated: Date;
|
||||
visibility: Visibility;
|
||||
user_actions: UserActions;
|
||||
settings: ProjectSettings;
|
||||
icon_url: string;
|
||||
}
|
||||
|
||||
interface PaginatedProjectList {
|
||||
pagination: Pagination;
|
||||
result: Project[];
|
||||
}
|
||||
}
|
68
client/types/enums.ts
Normal file
68
client/types/enums.ts
Normal file
@ -0,0 +1,68 @@
|
||||
export enum RoleCategory {
|
||||
GLOBAL = 'global',
|
||||
PROJECT = 'project',
|
||||
ORGANIZATION = 'organization',
|
||||
}
|
||||
|
||||
export enum ApiSessionType {
|
||||
KEY = 'key',
|
||||
USER = 'user',
|
||||
PUBLIC = 'public',
|
||||
DEV = 'dev',
|
||||
}
|
||||
|
||||
export enum ProjectCategory {
|
||||
ADMIN_TOOLS = 'admin_tools',
|
||||
CHAT = 'chat',
|
||||
DEV_TOOLS = 'dev_tools',
|
||||
ECONOMY = 'economy',
|
||||
GAMEPLAY = 'gameplay',
|
||||
GAMES = 'games',
|
||||
PROTECTION = 'protection',
|
||||
ROLE_PLAYING = 'role_playing',
|
||||
WORLD_MANAGEMENT = 'world_management',
|
||||
MISC = 'misc',
|
||||
UNDEFINED = 'undefined',
|
||||
}
|
||||
|
||||
export enum Visibility {
|
||||
PUBLIC = 'public',
|
||||
NEW = 'new',
|
||||
NEEDS_CHANGES = 'needsChanges',
|
||||
NEEDS_APPROVAL = 'needsApproval',
|
||||
SOFT_DELETE = 'softDelete',
|
||||
}
|
||||
|
||||
// export class ProjectCategory {
|
||||
// private static values: Map<string, ProjectCategory>;
|
||||
// public static getValues(): IterableIterator<ProjectCategory> {
|
||||
// return this.values.values();
|
||||
// }
|
||||
//
|
||||
// public static getByName(name: string): ProjectCategory | undefined {
|
||||
// return this.values.get(name);
|
||||
// }
|
||||
//
|
||||
// public static ADMIN_TOOLS = ProjectCategory.create('admin_tools', 'Admin Tools', 'fa-server');
|
||||
// public static CHAT = ProjectCategory.create('chat', 'Chat', 'fa-comment');
|
||||
// public static DEV_TOOLS = ProjectCategory.create('dev_tools', 'Developer Tools', 'fa-wrench');
|
||||
// public static ECONOMY = ProjectCategory.create('economy', 'Economy', 'fa-money-bill-alt');
|
||||
// public static GAMEPLAY = ProjectCategory.create('gameplay', 'Gameplay', 'fa-puzzle-piece');
|
||||
// public static GAMES = ProjectCategory.create('games', 'Games', 'fa-gamepad');
|
||||
//
|
||||
// name: string;
|
||||
// title: string;
|
||||
// icon: string;
|
||||
//
|
||||
// constructor(name: string, title: string, icon: string) {
|
||||
// this.name = name;
|
||||
// this.title = title;
|
||||
// this.icon = icon;
|
||||
// }
|
||||
//
|
||||
// static create(name: string, title: string, icon: string): ProjectCategory {
|
||||
// const category = new ProjectCategory(name, title, icon);
|
||||
// this.values.set(name, category);
|
||||
// return category;
|
||||
// }
|
||||
// }
|
9753
client/yarn.lock
Normal file
9753
client/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,6 @@
|
||||
package io.papermc.hangar.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import freemarker.core.HTMLOutputFormat;
|
||||
import freemarker.template.TemplateException;
|
||||
import io.papermc.hangar.controller.converters.ColorHexConverter;
|
||||
@ -21,6 +20,7 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.filter.ShallowEtagHeaderFilter;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
|
||||
import org.springframework.web.servlet.resource.CssLinkResourceTransformer;
|
||||
@ -54,6 +54,11 @@ public class WebConfig extends WebMvcConfigurationSupport {
|
||||
return resolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**").allowedOrigins("http://localhost:3000");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FreeMarkerConfigurer freemarkerConfig() {
|
||||
FreeMarkerConfigurer freeMarkerConfigurer = new FreeMarkerConfigurer();
|
||||
|
Loading…
Reference in New Issue
Block a user