feat: Added the features service

radarr4k
tidusjar 3 years ago
parent 9b1a1062ac
commit 689a869507

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ombi.Settings.Settings.Models
{
public class FeatureSettings : Settings
{
public List<FeatureEnablement> Features { get; set; }
}
public class FeatureEnablement
{
public string Name { get; set; }
public bool Enabled { get; set; }
}
public static class FeatureNames
{
public const string Movie4KRequests = nameof(Movie4KRequests);
}
}

@ -1,5 +1,6 @@
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using Ombi.Store.Entities;
namespace Ombi.Store.Context

@ -21,6 +21,8 @@ import { CustomPageComponent } from "./custompage/custompage.component";
import { CustomizationState } from "./state/customization/customization.state";
import { DataViewModule } from "primeng/dataview";
import { DialogModule } from "primeng/dialog";
import { FEATURES_INITIALIZER } from "./state/features/features-initializer";
import { FeatureState } from "./state/features";
import { JwtModule } from "@auth0/angular-jwt";
import { LandingPageComponent } from "./landingpage/landingpage.component";
import { LandingPageService } from "./services";
@ -38,6 +40,8 @@ import { MatInputModule } from "@angular/material/input";
import { MatListModule } from '@angular/material/list';
import { MatMenuModule } from "@angular/material/menu";
import { MatNativeDateModule } from '@angular/material/core';
import { MatPaginatorI18n } from "./localization/MatPaginatorI18n";
import { MatPaginatorIntl } from "@angular/material/paginator";
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSlideToggleModule } from "@angular/material/slide-toggle";
@ -63,11 +67,9 @@ import { StorageService } from "./shared/storage/storage-service";
import { TokenResetPasswordComponent } from "./login/tokenresetpassword.component";
import { TooltipModule } from "primeng/tooltip";
import { TranslateHttpLoader } from "@ngx-translate/http-loader";
import { TranslateService } from "@ngx-translate/core";
import { UnauthorizedInterceptor } from "./auth/unauthorized.interceptor";
import { environment } from "../environments/environment";
import { MatPaginatorIntl } from "@angular/material/paginator";
import { TranslateService } from "@ngx-translate/core";
import { MatPaginatorI18n } from "./localization/MatPaginatorI18n";
const routes: Routes = [
{ path: "*", component: PageNotFoundComponent },
@ -162,10 +164,10 @@ export function JwtTokenGetter() {
}),
SidebarModule,
MatNativeDateModule, MatIconModule, MatSidenavModule, MatListModule, MatToolbarModule, LayoutModule, MatSlideToggleModule,
NgxsModule.forRoot([CustomizationState], {
NgxsModule.forRoot([CustomizationState, FeatureState], {
developmentMode: !environment.production,
}),
...environment.production ? [] :
...environment.production ? [] :
[
NgxsReduxDevtoolsPluginModule.forRoot(),
]
@ -205,6 +207,7 @@ export function JwtTokenGetter() {
StorageService,
RequestService,
SignalRNotificationService,
FEATURES_INITIALIZER,
CUSTOMIZATION_INITIALIZER,
{
provide: APP_BASE_HREF,

@ -146,3 +146,8 @@ export enum INotificationAgent {
Webhook = 9,
WhatsApp = 10
}
export interface IFeatureEnablement {
name: string;
enabled: boolean;
}

@ -0,0 +1,19 @@
import { APP_BASE_HREF } from "@angular/common";
import { Injectable, Inject } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs";
import { IFeatureEnablement } from "../interfaces";
import { ServiceHelpers } from "./service.helpers";
@Injectable({ providedIn: "root" })
export class FeatureService extends ServiceHelpers {
constructor(http: HttpClient, @Inject(APP_BASE_HREF) href:string) {
super(http, "/api/v2/Features/", href);
}
public getFeatures(): Observable<IFeatureEnablement[]> {
return this.http.get<IFeatureEnablement[]>(this.url, {headers: this.headers});
}
}

@ -6,7 +6,6 @@ import { ICustomizationSettings } from "../../interfaces";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";
import { SettingsService } from "../../services";
import { produce } from 'immer';
import { tap } from "rxjs/operators";
@State({

@ -0,0 +1,10 @@
import { APP_INITIALIZER } from "@angular/core";
import { FeaturesFacade } from "./features.facade";
import { Observable } from "rxjs";
export const FEATURES_INITIALIZER = {
provide: APP_INITIALIZER,
useFactory: (featureFacade: FeaturesFacade) => (): Observable<unknown> => featureFacade.loadFeatures(),
multi: true,
deps: [FeaturesFacade],
};

@ -0,0 +1,3 @@
export class LoadFeatures {
public static readonly type = '[Features] LoadAll';
}

@ -0,0 +1,21 @@
import { FeaturesSelectors } from "./features.selectors";
import { IFeatureEnablement } from "../../interfaces";
import { Injectable } from "@angular/core";
import { LoadFeatures } from "./features.actions";
import { Observable } from "rxjs";
import { Store } from "@ngxs/store";
@Injectable({
providedIn: 'root',
})
export class FeaturesFacade {
public constructor(private store: Store) {}
public features$ = (): Observable<IFeatureEnablement[]> => this.store.select(FeaturesSelectors.features);
public loadFeatures = (): Observable<unknown> => this.store.dispatch(new LoadFeatures());
public is4kEnabled = (): boolean => this.store.selectSnapshot(FeaturesSelectors.is4kEnabled);
}

@ -0,0 +1,18 @@
import { ICustomizationSettings, IFeatureEnablement } from "../../interfaces";
import { FEATURES_STATE_TOKEN } from "./types";
import { Selector } from "@ngxs/store";
export class FeaturesSelectors {
@Selector([FEATURES_STATE_TOKEN])
public static features(features: IFeatureEnablement[]): IFeatureEnablement[] {
return features;
}
@Selector([FeaturesSelectors.features])
public static is4kEnabled(features: IFeatureEnablement[]): boolean {
return features.filter(x => x.name === "Movie4KRequests")[0].enabled;
}
}

@ -0,0 +1,26 @@
import { Action, State, StateContext } from "@ngxs/store";
import { FEATURES_STATE_TOKEN } from "./types";
import { FeatureService } from "../../services/feature.service";
import { IFeatureEnablement } from "../../interfaces";
import { Injectable } from "@angular/core";
import { LoadFeatures } from "./features.actions";
import { Observable } from "rxjs";
import { tap } from "rxjs/operators";
@State({
name: FEATURES_STATE_TOKEN
})
@Injectable()
export class FeatureState {
constructor(private featuresService: FeatureService) { }
@Action(LoadFeatures)
public load({ setState }: StateContext<IFeatureEnablement[]>): Observable<IFeatureEnablement[]> {
return this.featuresService.getFeatures().pipe(
tap(features =>
setState(features)
)
);
}
}

@ -0,0 +1,4 @@
export * from './features.state';
export * from './features.actions';
export * from './features.facade';
export * from './features.selectors';

@ -0,0 +1,4 @@
import { IFeatureEnablement } from "../../interfaces";
import { StateToken } from "@ngxs/store";
export const FEATURES_STATE_TOKEN = new StateToken<IFeatureEnablement[]>('featureEnablement');

@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace Ombi.Controllers.V2
{
public class FeaturesController : V2Controller
{
private readonly ISettingsService<FeatureSettings> _features;
public FeaturesController(ISettingsService<FeatureSettings> features) => _features = features;
[HttpGet]
[AllowAnonymous]
public async Task<List<FeatureEnablement>> GetFeatures()
{
var features = await _features.GetSettingsAsync();
return PopulateFeatures(features?.Features ?? new List<FeatureEnablement>());
}
private List<FeatureEnablement> PopulateFeatures(List<FeatureEnablement> existingFeatures)
{
var supported = GetSupportedFeatures().ToList();
if (supported.Count == existingFeatures.Count)
{
return existingFeatures;
}
var diff = supported.Except(existingFeatures.Select(x => x.Name));
foreach (var feature in diff)
{
existingFeatures.Add(new FeatureEnablement
{
Name = feature
});
}
return existingFeatures;
}
private IEnumerable<string> GetSupportedFeatures()
{
FieldInfo[] fieldInfos = typeof(FeatureNames).GetFields(BindingFlags.Public |
BindingFlags.Static | BindingFlags.FlattenHierarchy);
return fieldInfos.Where(fi => fi.IsLiteral && !fi.IsInitOnly && fi.FieldType == typeof(string)).Select(x => (string)x.GetValue(x));
}
}
}
Loading…
Cancel
Save