axios test (#705)
* add version controller * use axios to get version * allow typescript * use version api service
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace ErsatzTV.Controllers.Api;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
public class VersionController
|
||||||
|
{
|
||||||
|
private static readonly string Version;
|
||||||
|
|
||||||
|
static VersionController()
|
||||||
|
{
|
||||||
|
Version = Assembly.GetEntryAssembly()?
|
||||||
|
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?
|
||||||
|
.InformationalVersion ?? "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("/api/version")]
|
||||||
|
public string GetVersion() => Version;
|
||||||
|
}
|
||||||
+11
-2
@@ -68,10 +68,16 @@ namespace ErsatzTV;
|
|||||||
|
|
||||||
public class Startup
|
public class Startup
|
||||||
{
|
{
|
||||||
public Startup(IConfiguration configuration) => Configuration = configuration;
|
public Startup(IConfiguration configuration, IWebHostEnvironment env)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
CurrentEnvironment = env;
|
||||||
|
}
|
||||||
|
|
||||||
public IConfiguration Configuration { get; }
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
private IWebHostEnvironment CurrentEnvironment { get; }
|
||||||
|
|
||||||
public void ConfigureServices(IServiceCollection services)
|
public void ConfigureServices(IServiceCollection services)
|
||||||
{
|
{
|
||||||
BugsnagConfiguration bugsnagConfig = Configuration.GetSection("Bugsnag").Get<BugsnagConfiguration>();
|
BugsnagConfiguration bugsnagConfig = Configuration.GetSection("Bugsnag").Get<BugsnagConfiguration>();
|
||||||
@@ -131,7 +137,10 @@ public class Startup
|
|||||||
options.ImplicitlyValidateChildProperties = true;
|
options.ImplicitlyValidateChildProperties = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddSpaStaticFiles(options => options.RootPath = "wwwroot/v2");
|
if (!CurrentEnvironment.IsDevelopment())
|
||||||
|
{
|
||||||
|
services.AddSpaStaticFiles(options => options.RootPath = "wwwroot/v2");
|
||||||
|
}
|
||||||
|
|
||||||
services.AddMemoryCache();
|
services.AddMemoryCache();
|
||||||
|
|
||||||
|
|||||||
Generated
+1061
-49
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdi/font": "5.9.55",
|
"@mdi/font": "5.9.55",
|
||||||
|
"axios": "^0.26.1",
|
||||||
"core-js": "^3.8.3",
|
"core-js": "^3.8.3",
|
||||||
"pinia": "^2.0.11",
|
"pinia": "^2.0.11",
|
||||||
"roboto-fontface": "*",
|
"roboto-fontface": "*",
|
||||||
@@ -19,16 +20,21 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.16",
|
"@babel/core": "^7.12.16",
|
||||||
"@babel/eslint-parser": "^7.12.16",
|
"@babel/eslint-parser": "^7.12.16",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.4.0",
|
||||||
|
"@typescript-eslint/parser": "^5.4.0",
|
||||||
"@vue/cli-plugin-babel": "~5.0.0",
|
"@vue/cli-plugin-babel": "~5.0.0",
|
||||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||||
|
"@vue/cli-plugin-typescript": "~5.0.0",
|
||||||
"@vue/cli-service": "~5.0.0",
|
"@vue/cli-service": "~5.0.0",
|
||||||
"@vue/composition-api": "^1.4.9",
|
"@vue/composition-api": "^1.4.9",
|
||||||
|
"@vue/eslint-config-typescript": "^9.1.0",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"eslint-plugin-vue": "^8.0.3",
|
"eslint-plugin-vue": "^8.0.3",
|
||||||
"sass": "~1.32.0",
|
"sass": "~1.32.0",
|
||||||
"sass-loader": "^10.0.0",
|
"sass-loader": "^10.0.0",
|
||||||
|
"typescript": "~4.5.5",
|
||||||
"vue-cli-plugin-vuetify": "~2.4.7",
|
"vue-cli-plugin-vuetify": "~2.4.7",
|
||||||
"vue-template-compiler": "^2.6.14",
|
"vue-template-compiler": "^2.6.14",
|
||||||
"vuetify-loader": "^1.7.0"
|
"vuetify-loader": "^1.7.0"
|
||||||
@@ -41,14 +47,16 @@
|
|||||||
"extends": [
|
"extends": [
|
||||||
"plugin:vue/essential",
|
"plugin:vue/essential",
|
||||||
"plugin:prettier/recommended",
|
"plugin:prettier/recommended",
|
||||||
"eslint:recommended"
|
"eslint:recommended",
|
||||||
|
"@vue/typescript"
|
||||||
],
|
],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"parser": "@babel/eslint-parser"
|
"parser": "@typescript-eslint/parser"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"prettier/prettier": [
|
"prettier/prettier": [
|
||||||
"error", {
|
"error",
|
||||||
|
{
|
||||||
"endOfLine": "auto",
|
"endOfLine": "auto",
|
||||||
"tabWidth": 4
|
"tabWidth": 4
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ import { applicationState } from '@/stores/applicationState';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SideBarVersion',
|
name: 'SideBarVersion',
|
||||||
|
setup() {
|
||||||
|
applicationState().getVersion();
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(applicationState, [
|
...mapState(applicationState, [
|
||||||
'currentServerVersion',
|
'currentServerVersion',
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Vuetify from 'vuetify/lib/framework';
|
import Vuetify from 'vuetify';
|
||||||
import colors from 'vuetify/lib/util/colors';
|
import colors from 'vuetify/lib/util/colors';
|
||||||
|
|
||||||
Vue.use(Vuetify);
|
Vue.use(Vuetify);
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import type { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export function isAxiosError(value: any): value is AxiosError {
|
||||||
|
return typeof value?.response === 'object';
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class AbstractApiService {
|
||||||
|
protected readonly http: AxiosInstance;
|
||||||
|
|
||||||
|
protected constructor(
|
||||||
|
protected readonly path?: string,
|
||||||
|
protected readonly baseURL: string = '/'
|
||||||
|
) {
|
||||||
|
if (path) {
|
||||||
|
this.baseURL += path;
|
||||||
|
}
|
||||||
|
this.http = axios.create({
|
||||||
|
baseURL
|
||||||
|
// ... further stuff, e.g. `withCredentials: true`
|
||||||
|
});
|
||||||
|
this.http.defaults.headers.common['Accept'] =
|
||||||
|
'application/json;charset=UTF-8';
|
||||||
|
this.http.defaults.headers.common['Content-Type'] =
|
||||||
|
'application/json;charset=UTF-8';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createParams(record: Record<string, any>): URLSearchParams {
|
||||||
|
const params: URLSearchParams = new URLSearchParams();
|
||||||
|
for (const key in record) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(record, key)) {
|
||||||
|
const value: any = record[key];
|
||||||
|
if (value !== null && value !== undefined) {
|
||||||
|
params.append(key, value);
|
||||||
|
} else {
|
||||||
|
console.debug(
|
||||||
|
`Param key '${key}' was null or undefined and will be ignored`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleResponse<T>(response: AxiosResponse<T>): T {
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleError(error: unknown): never {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
if (isAxiosError(error)) {
|
||||||
|
if (error.response) {
|
||||||
|
console.log(error.response.data);
|
||||||
|
console.log(error.response.status);
|
||||||
|
console.log(error.response.headers);
|
||||||
|
throw error;
|
||||||
|
} else if (error.request) {
|
||||||
|
// The request was made but no response was received
|
||||||
|
// `error.request` is an instance of XMLHttpRequest in the browser
|
||||||
|
console.log(error.request);
|
||||||
|
throw new Error(error as any);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Something happened in setting up the request that triggered an Error
|
||||||
|
console.log('Error', error.message);
|
||||||
|
throw new Error(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(error as any);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { AbstractApiService } from './AbstractApiService';
|
||||||
|
|
||||||
|
class VersionApiService extends AbstractApiService {
|
||||||
|
public constructor() {
|
||||||
|
super('/api/version');
|
||||||
|
}
|
||||||
|
|
||||||
|
public version(): Promise<string> {
|
||||||
|
return this.http
|
||||||
|
.get('')
|
||||||
|
.then(this.handleResponse)
|
||||||
|
.catch(this.handleError.bind(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const versionApiService: VersionApiService = new VersionApiService();
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
import Vue, { VNode } from 'vue';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace JSX {
|
||||||
|
interface Element extends VNode {}
|
||||||
|
interface ElementClass extends Vue {}
|
||||||
|
interface IntrinsicElements {
|
||||||
|
[elem: string]: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
declare module '*.vue' {
|
||||||
|
import Vue from 'vue';
|
||||||
|
export default Vue;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
import { versionApiService } from '@/services/VersionService';
|
||||||
|
|
||||||
const originURL = `${window.location.origin}`;
|
const originURL = `${window.location.origin}`;
|
||||||
|
|
||||||
@@ -6,7 +7,7 @@ export const applicationState = defineStore('appState', {
|
|||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
miniMenu: false,
|
miniMenu: false,
|
||||||
currentVersion: '0.4.3-7cd2f9a-docker-nvidia', // Needs to be pulled from API with an action when ready
|
currentVersion: 'unknown',
|
||||||
m3uURL: originURL + '/iptv/channels.m3u', // this will need to be fixed for reverse proxies
|
m3uURL: originURL + '/iptv/channels.m3u', // this will need to be fixed for reverse proxies
|
||||||
xmlURL: originURL + '/iptv/xmltv.xml', // this will need to be fixed for reverse proxies
|
xmlURL: originURL + '/iptv/xmltv.xml', // this will need to be fixed for reverse proxies
|
||||||
documentationURL: 'https://ersatztv.org/',
|
documentationURL: 'https://ersatztv.org/',
|
||||||
@@ -37,6 +38,9 @@ export const applicationState = defineStore('appState', {
|
|||||||
},
|
},
|
||||||
disableMiniNavigation() {
|
disableMiniNavigation() {
|
||||||
this.miniMenu = false;
|
this.miniMenu = false;
|
||||||
|
},
|
||||||
|
async getVersion() {
|
||||||
|
this.currentVersion = await versionApiService.version();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "esnext",
|
||||||
|
"strict": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"types": [
|
||||||
|
"webpack-env",
|
||||||
|
"vuetify"
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"scripthost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"src/**/*.vue",
|
||||||
|
"tests/**/*.ts",
|
||||||
|
"tests/**/*.tsx"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user