Editable groups
This commit is contained in:
parent
565f21429a
commit
bb44e59a14
@ -7,7 +7,6 @@ type GroupResult {
|
|||||||
type Group implements DbModel {
|
type Group implements DbModel {
|
||||||
id: ID
|
id: ID
|
||||||
name: String
|
name: String
|
||||||
description: String
|
|
||||||
|
|
||||||
deleted: Boolean
|
deleted: Boolean
|
||||||
editor: User
|
editor: User
|
||||||
@ -18,7 +17,6 @@ type Group implements DbModel {
|
|||||||
input GroupSort {
|
input GroupSort {
|
||||||
id: SortOrder
|
id: SortOrder
|
||||||
name: SortOrder
|
name: SortOrder
|
||||||
description: SortOrder
|
|
||||||
|
|
||||||
deleted: SortOrder
|
deleted: SortOrder
|
||||||
editorId: SortOrder
|
editorId: SortOrder
|
||||||
@ -29,7 +27,6 @@ input GroupSort {
|
|||||||
input GroupFilter {
|
input GroupFilter {
|
||||||
id: IntFilter
|
id: IntFilter
|
||||||
name: StringFilter
|
name: StringFilter
|
||||||
description: StringFilter
|
|
||||||
|
|
||||||
deleted: BooleanFilter
|
deleted: BooleanFilter
|
||||||
editor: IntFilter
|
editor: IntFilter
|
||||||
@ -37,8 +34,18 @@ input GroupFilter {
|
|||||||
updatedUtc: DateFilter
|
updatedUtc: DateFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
input GroupInput {
|
type GroupMutation {
|
||||||
id: ID
|
create(input: GroupCreateInput!): Group
|
||||||
name: String
|
update(input: GroupUpdateInput!): Group
|
||||||
description: String
|
delete(id: ID!): Boolean
|
||||||
|
restore(id: ID!): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input GroupCreateInput {
|
||||||
|
name: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input GroupUpdateInput {
|
||||||
|
id: ID!
|
||||||
|
name: String
|
||||||
|
}
|
@ -3,4 +3,7 @@ type Mutation {
|
|||||||
|
|
||||||
user: UserMutation
|
user: UserMutation
|
||||||
role: RoleMutation
|
role: RoleMutation
|
||||||
|
|
||||||
|
group: GroupMutation
|
||||||
|
shortUrl: ShortUrlMutation
|
||||||
}
|
}
|
@ -41,8 +41,22 @@ input ShortUrlFilter {
|
|||||||
updatedUtc: DateFilter
|
updatedUtc: DateFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
input ShortUrlInput {
|
type ShortUrlMutation {
|
||||||
id: ID
|
create(input: ShortUrlCreateInput!): ShortUrl
|
||||||
|
update(input: ShortUrlUpdateInput!): ShortUrl
|
||||||
|
delete(id: ID!): Boolean
|
||||||
|
restore(id: ID!): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
input ShortUrlCreateInput {
|
||||||
|
shortUrl: String!
|
||||||
|
targetUrl: String!
|
||||||
|
description: String
|
||||||
|
group: ID
|
||||||
|
}
|
||||||
|
|
||||||
|
input ShortUrlUpdateInput {
|
||||||
|
id: ID!
|
||||||
shortUrl: String
|
shortUrl: String
|
||||||
targetUrl: String
|
targetUrl: String
|
||||||
description: String
|
description: String
|
||||||
|
13
api/src/api_graphql/input/group_create_input.py
Normal file
13
api/src/api_graphql/input/group_create_input.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from api_graphql.abc.input_abc import InputABC
|
||||||
|
|
||||||
|
|
||||||
|
class GroupCreateInput(InputABC):
|
||||||
|
|
||||||
|
def __init__(self, src: dict):
|
||||||
|
InputABC.__init__(self, src)
|
||||||
|
|
||||||
|
self._name = self.option("name", str, required=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
18
api/src/api_graphql/input/group_update_input.py
Normal file
18
api/src/api_graphql/input/group_update_input.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from api_graphql.abc.input_abc import InputABC
|
||||||
|
|
||||||
|
|
||||||
|
class GroupUpdateInput(InputABC):
|
||||||
|
|
||||||
|
def __init__(self, src: dict):
|
||||||
|
InputABC.__init__(self, src)
|
||||||
|
|
||||||
|
self._id = self.option("id", int, required=True)
|
||||||
|
self._name = self.option("name", str)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self) -> int:
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return self._name
|
30
api/src/api_graphql/input/short_url_create_input.py
Normal file
30
api/src/api_graphql/input/short_url_create_input.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from api_graphql.abc.input_abc import InputABC
|
||||||
|
|
||||||
|
|
||||||
|
class ShortUrlCreateInput(InputABC):
|
||||||
|
|
||||||
|
def __init__(self, src: dict):
|
||||||
|
InputABC.__init__(self, src)
|
||||||
|
|
||||||
|
self._short_url = self.option("shortUrl", str, required=True)
|
||||||
|
self._target_url = self.option("targetUrl", str, required=True)
|
||||||
|
self._description = self.option("description", str)
|
||||||
|
self._group_id = self.option("groupId", int)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def short_url(self) -> str:
|
||||||
|
return self._short_url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_url(self) -> str:
|
||||||
|
return self._target_url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> Optional[str]:
|
||||||
|
return self._description
|
||||||
|
|
||||||
|
@property
|
||||||
|
def group_id(self) -> Optional[int]:
|
||||||
|
return self._group_id
|
35
api/src/api_graphql/input/short_url_update_input.py
Normal file
35
api/src/api_graphql/input/short_url_update_input.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from api_graphql.abc.input_abc import InputABC
|
||||||
|
|
||||||
|
|
||||||
|
class ShortUrlUpdateInput(InputABC):
|
||||||
|
|
||||||
|
def __init__(self, src: dict):
|
||||||
|
InputABC.__init__(self, src)
|
||||||
|
|
||||||
|
self._id = self.option("id", int, required=True)
|
||||||
|
self._short_url = self.option("shortUrl", str)
|
||||||
|
self._target_url = self.option("targetUrl", str)
|
||||||
|
self._description = self.option("description", str)
|
||||||
|
self._group_id = self.option("groupId", int)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def id(self) -> int:
|
||||||
|
return self._id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def short_url(self) -> Optional[str]:
|
||||||
|
return self._short_url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def target_url(self) -> Optional[str]:
|
||||||
|
return self._target_url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> Optional[str]:
|
||||||
|
return self._description
|
||||||
|
|
||||||
|
@property
|
||||||
|
def group_id(self) -> Optional[int]:
|
||||||
|
return self._group_id
|
@ -32,3 +32,13 @@ class Mutation(MutationABC):
|
|||||||
Permissions.users_delete,
|
Permissions.users_delete,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.add_mutation_type(
|
||||||
|
"group",
|
||||||
|
"Group",
|
||||||
|
require_any_permission=[
|
||||||
|
Permissions.groups_create,
|
||||||
|
Permissions.groups_update,
|
||||||
|
Permissions.groups_delete,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
73
api/src/api_graphql/mutations/group_mutation.py
Normal file
73
api/src/api_graphql/mutations/group_mutation.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
from api_graphql.abc.mutation_abc import MutationABC
|
||||||
|
from api_graphql.input.group_create_input import GroupCreateInput
|
||||||
|
from api_graphql.input.group_update_input import GroupUpdateInput
|
||||||
|
from core.logger import APILogger
|
||||||
|
from data.schemas.public.group import Group
|
||||||
|
from data.schemas.public.group_dao import groupDao
|
||||||
|
from service.permission.permissions_enum import Permissions
|
||||||
|
|
||||||
|
logger = APILogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class GroupMutation(MutationABC):
|
||||||
|
def __init__(self):
|
||||||
|
MutationABC.__init__(self, "Group")
|
||||||
|
|
||||||
|
self.mutation(
|
||||||
|
"create",
|
||||||
|
self.resolve_create,
|
||||||
|
GroupCreateInput,
|
||||||
|
require_any_permission=[Permissions.groups_create],
|
||||||
|
)
|
||||||
|
self.mutation(
|
||||||
|
"update",
|
||||||
|
self.resolve_update,
|
||||||
|
GroupUpdateInput,
|
||||||
|
require_any_permission=[Permissions.groups_update],
|
||||||
|
)
|
||||||
|
self.mutation(
|
||||||
|
"delete",
|
||||||
|
self.resolve_delete,
|
||||||
|
require_any_permission=[Permissions.groups_delete],
|
||||||
|
)
|
||||||
|
self.mutation(
|
||||||
|
"restore",
|
||||||
|
self.resolve_restore,
|
||||||
|
require_any_permission=[Permissions.groups_delete],
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def resolve_create(obj: GroupCreateInput, *_):
|
||||||
|
logger.debug(f"create group: {obj.__dict__}")
|
||||||
|
|
||||||
|
group = Group(
|
||||||
|
0,
|
||||||
|
obj.name,
|
||||||
|
)
|
||||||
|
nid = await groupDao.create(group)
|
||||||
|
return await groupDao.get_by_id(nid)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def resolve_update(obj: GroupUpdateInput, *_):
|
||||||
|
logger.debug(f"update group: {input}")
|
||||||
|
|
||||||
|
if obj.name is not None:
|
||||||
|
group = await groupDao.get_by_id(obj.id)
|
||||||
|
group.name = obj.name
|
||||||
|
await groupDao.update(group)
|
||||||
|
|
||||||
|
return await groupDao.get_by_id(obj.id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def resolve_delete(*_, id: str):
|
||||||
|
logger.debug(f"delete group: {id}")
|
||||||
|
group = await groupDao.get_by_id(id)
|
||||||
|
await groupDao.delete(group)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def resolve_restore(*_, id: str):
|
||||||
|
logger.debug(f"restore group: {id}")
|
||||||
|
group = await groupDao.get_by_id(id)
|
||||||
|
await groupDao.restore(group)
|
||||||
|
return True
|
92
api/src/api_graphql/mutations/short_url_mutation.py
Normal file
92
api/src/api_graphql/mutations/short_url_mutation.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
from werkzeug.exceptions import NotFound
|
||||||
|
|
||||||
|
from api_graphql.abc.mutation_abc import MutationABC
|
||||||
|
from api_graphql.input.short_url_create_input import ShortUrlCreateInput
|
||||||
|
from api_graphql.input.short_url_update_input import ShortUrlUpdateInput
|
||||||
|
from core.logger import APILogger
|
||||||
|
from data.schemas.public.group_dao import groupDao
|
||||||
|
from data.schemas.public.short_url import ShortUrl
|
||||||
|
from data.schemas.public.short_url_dao import shortUrlDao
|
||||||
|
from service.permission.permissions_enum import Permissions
|
||||||
|
|
||||||
|
logger = APILogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ShortUrlMutation(MutationABC):
|
||||||
|
def __init__(self):
|
||||||
|
MutationABC.__init__(self, "ShortUrl")
|
||||||
|
|
||||||
|
self.mutation(
|
||||||
|
"create",
|
||||||
|
self.resolve_create,
|
||||||
|
ShortUrlCreateInput,
|
||||||
|
require_any_permission=[Permissions.short_urls_create],
|
||||||
|
)
|
||||||
|
self.mutation(
|
||||||
|
"update",
|
||||||
|
self.resolve_update,
|
||||||
|
ShortUrlUpdateInput,
|
||||||
|
require_any_permission=[Permissions.short_urls_update],
|
||||||
|
)
|
||||||
|
self.mutation(
|
||||||
|
"delete",
|
||||||
|
self.resolve_delete,
|
||||||
|
require_any_permission=[Permissions.short_urls_delete],
|
||||||
|
)
|
||||||
|
self.mutation(
|
||||||
|
"restore",
|
||||||
|
self.resolve_restore,
|
||||||
|
require_any_permission=[Permissions.short_urls_delete],
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def resolve_create(obj: ShortUrlCreateInput, *_):
|
||||||
|
logger.debug(f"create short_url: {obj.__dict__}")
|
||||||
|
|
||||||
|
short_url = ShortUrl(
|
||||||
|
0,
|
||||||
|
obj.short_url,
|
||||||
|
obj.target_url,
|
||||||
|
obj.description,
|
||||||
|
obj.group_id,
|
||||||
|
)
|
||||||
|
nid = await shortUrlDao.create(short_url)
|
||||||
|
return await shortUrlDao.get_by_id(nid)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def resolve_update(obj: ShortUrlUpdateInput, *_):
|
||||||
|
logger.debug(f"update short_url: {input}")
|
||||||
|
|
||||||
|
short_url = await shortUrlDao.get_by_id(obj.id)
|
||||||
|
|
||||||
|
if obj.short_url is not None:
|
||||||
|
short_url.short_url = obj.short_url
|
||||||
|
|
||||||
|
if obj.target_url is not None:
|
||||||
|
short_url.target_url = obj.target_url
|
||||||
|
|
||||||
|
if obj.description is not None:
|
||||||
|
short_url.description = obj.description
|
||||||
|
|
||||||
|
if obj.group_id is not None:
|
||||||
|
group_by_id = await groupDao.find_by_id(obj.group_id)
|
||||||
|
if group_by_id is None:
|
||||||
|
raise NotFound(f"Group with id {obj.group_id} does not exist")
|
||||||
|
short_url.group_id = obj.group_id
|
||||||
|
|
||||||
|
await shortUrlDao.update(short_url)
|
||||||
|
return await shortUrlDao.get_by_id(obj.id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def resolve_delete(*_, id: str):
|
||||||
|
logger.debug(f"delete short_url: {id}")
|
||||||
|
short_url = await shortUrlDao.get_by_id(id)
|
||||||
|
await shortUrlDao.delete(short_url)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def resolve_restore(*_, id: str):
|
||||||
|
logger.debug(f"restore short_url: {id}")
|
||||||
|
short_url = await shortUrlDao.get_by_id(id)
|
||||||
|
await shortUrlDao.restore(short_url)
|
||||||
|
return True
|
@ -6,4 +6,3 @@ class GroupQuery(DbModelQueryABC):
|
|||||||
DbModelQueryABC.__init__(self, "Group")
|
DbModelQueryABC.__init__(self, "Group")
|
||||||
|
|
||||||
self.set_field("name", lambda x, *_: x.name)
|
self.set_field("name", lambda x, *_: x.name)
|
||||||
self.set_field("description", lambda x, *_: x.description)
|
|
||||||
|
@ -10,10 +10,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-center w-1/3" *ngIf="menu.length > 0">
|
|
||||||
<app-menu-bar class="w-full" [elements]="menu"></app-menu-bar>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<p-button
|
<p-button
|
||||||
|
@ -1,24 +1,29 @@
|
|||||||
import { Component } from '@angular/core';
|
import {Component, OnDestroy} from '@angular/core';
|
||||||
import { MenuElement } from 'src/app/model/view/menu-element';
|
import {MenuElement} from 'src/app/model/view/menu-element';
|
||||||
import { Subject } from 'rxjs';
|
import {Subject} from 'rxjs';
|
||||||
import { SidebarService } from 'src/app/service/sidebar.service';
|
import {SidebarService} from 'src/app/service/sidebar.service';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import {takeUntil} from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-sidebar',
|
selector: 'app-sidebar',
|
||||||
templateUrl: './sidebar.component.html',
|
templateUrl: './sidebar.component.html',
|
||||||
styleUrl: './sidebar.component.scss',
|
styleUrl: './sidebar.component.scss',
|
||||||
})
|
})
|
||||||
export class SidebarComponent {
|
export class SidebarComponent implements OnDestroy {
|
||||||
elements: MenuElement[] = [];
|
elements: MenuElement[] = [];
|
||||||
|
|
||||||
unsubscribe$ = new Subject<void>();
|
unsubscribe$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(private sidebar: SidebarService) {
|
constructor(private sidebar: SidebarService) {
|
||||||
this.sidebar.elements$
|
this.sidebar.elements$
|
||||||
.pipe(takeUntil(this.unsubscribe$))
|
.pipe(takeUntil(this.unsubscribe$))
|
||||||
.subscribe(elements => {
|
.subscribe(elements => {
|
||||||
this.elements = elements;
|
this.elements = elements;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.unsubscribe$.next();
|
||||||
|
this.unsubscribe$.complete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,30 @@
|
|||||||
export enum PermissionsEnum {
|
export enum PermissionsEnum {
|
||||||
// Administration
|
// Administration
|
||||||
apiKeys = 'api_keys',
|
apiKeys = "api_keys",
|
||||||
apiKeysCreate = 'api_keys.create',
|
apiKeysCreate = "api_keys.create",
|
||||||
apiKeysUpdate = 'api_keys.update',
|
apiKeysUpdate = "api_keys.update",
|
||||||
apiKeysDelete = 'api_keys.delete',
|
apiKeysDelete = "api_keys.delete",
|
||||||
|
|
||||||
// Users
|
// Users
|
||||||
users = 'users',
|
users = "users",
|
||||||
usersCreate = 'users.create',
|
usersCreate = "users.create",
|
||||||
usersUpdate = 'users.update',
|
usersUpdate = "users.update",
|
||||||
usersDelete = 'users.delete',
|
usersDelete = "users.delete",
|
||||||
|
|
||||||
// Permissions
|
// Permissions
|
||||||
roles = 'roles',
|
roles = "roles",
|
||||||
rolesCreate = 'roles.create',
|
rolesCreate = "roles.create",
|
||||||
rolesUpdate = 'roles.update',
|
rolesUpdate = "roles.update",
|
||||||
rolesDelete = 'roles.delete',
|
rolesDelete = "roles.delete",
|
||||||
|
|
||||||
// Public
|
// Public
|
||||||
news = 'news',
|
groups = "groups",
|
||||||
newsCreate = 'news.create',
|
groupsCreate = "groups.create",
|
||||||
newsUpdate = 'news.update',
|
groupsUpdate = "groups.update",
|
||||||
newsDelete = 'news.delete',
|
groupsDelete = "groups.delete",
|
||||||
|
|
||||||
// Utils
|
shortUrls = "short_urls",
|
||||||
ipList = 'ip_list',
|
shortUrlsCreate = "short_urls.create",
|
||||||
ipListCreate = 'ip_list.create',
|
shortUrlsUpdate = "short_urls.update",
|
||||||
ipListUpdate = 'ip_list.update',
|
shortUrlsDelete = "short_urls.delete",
|
||||||
ipListDelete = 'ip_list.delete',
|
|
||||||
}
|
}
|
||||||
|
15
web/src/app/model/entities/group.ts
Normal file
15
web/src/app/model/entities/group.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { DbModel } from "src/app/model/entities/db-model";
|
||||||
|
import { Permission } from "src/app/model/entities/role";
|
||||||
|
|
||||||
|
export interface Group extends DbModel {
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GroupCreateInput {
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GroupUpdateInput {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
@ -1,21 +1,21 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from "@angular/core";
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from "@angular/common";
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from "@angular/router";
|
||||||
import { SharedModule } from 'src/app/modules/shared/shared.module';
|
import { SharedModule } from "src/app/modules/shared/shared.module";
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'public',
|
path: "groups",
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import('src/app/modules/admin/public/public.module').then(
|
import("src/app/modules/admin/groups/groups.module").then(
|
||||||
m => m.PublicModule
|
(m) => m.GroupsModule,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'administration',
|
path: "administration",
|
||||||
loadChildren: () =>
|
loadChildren: () =>
|
||||||
import('src/app/modules/admin/administration/administration.module').then(
|
import("src/app/modules/admin/administration/administration.module").then(
|
||||||
m => m.AdministrationModule
|
(m) => m.AdministrationModule,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
<app-form-page
|
||||||
|
*ngIf="node"
|
||||||
|
[formGroup]="form"
|
||||||
|
[isUpdate]="isUpdate"
|
||||||
|
(onSave)="save()"
|
||||||
|
(onClose)="close()">
|
||||||
|
<ng-template formPageHeader let-isUpdate>
|
||||||
|
<h2>
|
||||||
|
{{ 'common.group' | translate }}
|
||||||
|
{{
|
||||||
|
(isUpdate ? 'sidebar.header.update' : 'sidebar.header.create')
|
||||||
|
| translate
|
||||||
|
}}
|
||||||
|
</h2>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template formPageContent>
|
||||||
|
<div class="form-page-input">
|
||||||
|
<p class="label">{{ 'common.id' | translate }}</p>
|
||||||
|
<input pInputText class="value" type="number" formControlName="id"/>
|
||||||
|
</div>
|
||||||
|
<div class="form-page-input">
|
||||||
|
<p class="label">{{ 'common.name' | translate }}</p>
|
||||||
|
<input
|
||||||
|
pInputText
|
||||||
|
class="value"
|
||||||
|
type="text"
|
||||||
|
formControlName="name"/>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</app-form-page>
|
@ -0,0 +1,50 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { RoleFormPageComponent } from 'src/app/modules/admin/administration/roles/form-page/role-form-page.component';
|
||||||
|
import { SharedModule } from 'src/app/modules/shared/shared.module';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { AuthService } from 'src/app/service/auth.service';
|
||||||
|
import { ErrorHandlingService } from 'src/app/service/error-handling.service';
|
||||||
|
import { ToastService } from 'src/app/service/toast.service';
|
||||||
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { ApiKeysDataService } from 'src/app/modules/admin/administration/api-keys/api-keys.data.service';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
describe('ApiKeyFormpageComponent', () => {
|
||||||
|
let component: RoleFormPageComponent;
|
||||||
|
let fixture: ComponentFixture<RoleFormPageComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [RoleFormPageComponent],
|
||||||
|
imports: [
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
SharedModule,
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
AuthService,
|
||||||
|
ErrorHandlingService,
|
||||||
|
ToastService,
|
||||||
|
MessageService,
|
||||||
|
ConfirmationService,
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: {
|
||||||
|
snapshot: { params: of({}) },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ApiKeysDataService,
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(RoleFormPageComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,93 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
import { FormControl, FormGroup, Validators } from "@angular/forms";
|
||||||
|
import { ToastService } from "src/app/service/toast.service";
|
||||||
|
import { FormPageBase } from "src/app/core/base/form-page-base";
|
||||||
|
import {
|
||||||
|
Group,
|
||||||
|
GroupCreateInput,
|
||||||
|
GroupUpdateInput,
|
||||||
|
} from "src/app/model/entities/group";
|
||||||
|
import { GroupsDataService } from "src/app/modules/admin/groups/groups.data.service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-group-form-page",
|
||||||
|
templateUrl: "./group-form-page.component.html",
|
||||||
|
styleUrl: "./group-form-page.component.scss",
|
||||||
|
})
|
||||||
|
export class GroupFormPageComponent extends FormPageBase<
|
||||||
|
Group,
|
||||||
|
GroupCreateInput,
|
||||||
|
GroupUpdateInput,
|
||||||
|
GroupsDataService
|
||||||
|
> {
|
||||||
|
constructor(private toast: ToastService) {
|
||||||
|
super();
|
||||||
|
if (!this.nodeId) {
|
||||||
|
this.node = this.new();
|
||||||
|
this.setForm(this.node);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dataService
|
||||||
|
.load([{ id: { equal: this.nodeId } }])
|
||||||
|
.subscribe((apiKey) => {
|
||||||
|
this.node = apiKey.nodes[0];
|
||||||
|
this.setForm(this.node);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
new(): Group {
|
||||||
|
return {} as Group;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildForm() {
|
||||||
|
this.form = new FormGroup({
|
||||||
|
id: new FormControl<number | undefined>(undefined),
|
||||||
|
name: new FormControl<string | undefined>(undefined, Validators.required),
|
||||||
|
});
|
||||||
|
this.form.controls["id"].disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
setForm(node?: Group) {
|
||||||
|
this.form.controls["id"].setValue(node?.id);
|
||||||
|
this.form.controls["name"].setValue(node?.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCreateInput(): GroupCreateInput {
|
||||||
|
return {
|
||||||
|
name: this.form.controls["name"].pristine
|
||||||
|
? undefined
|
||||||
|
: (this.form.controls["name"].value ?? undefined),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getUpdateInput(): GroupUpdateInput {
|
||||||
|
if (!this.node?.id) {
|
||||||
|
throw new Error("Node id is missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: this.form.controls["id"].value,
|
||||||
|
name: this.form.controls["name"].pristine
|
||||||
|
? undefined
|
||||||
|
: (this.form.controls["name"].value ?? undefined),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
create(apiKey: GroupCreateInput): void {
|
||||||
|
this.dataService.create(apiKey).subscribe(() => {
|
||||||
|
this.spinner.hide();
|
||||||
|
this.toast.success("action.created");
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
update(apiKey: GroupUpdateInput): void {
|
||||||
|
this.dataService.update(apiKey).subscribe(() => {
|
||||||
|
this.spinner.hide();
|
||||||
|
this.toast.success("action.created");
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
35
web/src/app/modules/admin/groups/groups.columns.ts
Normal file
35
web/src/app/modules/admin/groups/groups.columns.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { Injectable, Provider } from "@angular/core";
|
||||||
|
import {
|
||||||
|
DB_MODEL_COLUMNS,
|
||||||
|
ID_COLUMN,
|
||||||
|
PageColumns,
|
||||||
|
} from "src/app/core/base/page.columns";
|
||||||
|
import { TableColumn } from "src/app/modules/shared/components/table/table.model";
|
||||||
|
import { Group } from "src/app/model/entities/group";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GroupsColumns extends PageColumns<Group> {
|
||||||
|
get(): TableColumn<Group>[] {
|
||||||
|
return [
|
||||||
|
ID_COLUMN,
|
||||||
|
{
|
||||||
|
name: "name",
|
||||||
|
label: "common.name",
|
||||||
|
type: "text",
|
||||||
|
filterable: true,
|
||||||
|
value: (row: Group) => row.name,
|
||||||
|
},
|
||||||
|
...DB_MODEL_COLUMNS,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
static provide(): Provider[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
provide: PageColumns,
|
||||||
|
useClass: GroupsColumns,
|
||||||
|
},
|
||||||
|
GroupsColumns,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
232
web/src/app/modules/admin/groups/groups.data.service.ts
Normal file
232
web/src/app/modules/admin/groups/groups.data.service.ts
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
import { Injectable, Provider } from "@angular/core";
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
import {
|
||||||
|
Create,
|
||||||
|
Delete,
|
||||||
|
PageDataService,
|
||||||
|
Restore,
|
||||||
|
Update,
|
||||||
|
} from "src/app/core/base/page.data.service";
|
||||||
|
import { Filter } from "src/app/model/graphql/filter/filter.model";
|
||||||
|
import { Sort } from "src/app/model/graphql/filter/sort.model";
|
||||||
|
import { Apollo, gql } from "apollo-angular";
|
||||||
|
import { QueryResult } from "src/app/model/entities/query-result";
|
||||||
|
import { DB_MODEL_FRAGMENT } from "src/app/model/graphql/db-model.query";
|
||||||
|
import { catchError, map } from "rxjs/operators";
|
||||||
|
import { SpinnerService } from "src/app/service/spinner.service";
|
||||||
|
import {
|
||||||
|
Group,
|
||||||
|
GroupCreateInput,
|
||||||
|
GroupUpdateInput,
|
||||||
|
} from "src/app/model/entities/group";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GroupsDataService
|
||||||
|
extends PageDataService<Group>
|
||||||
|
implements
|
||||||
|
Create<Group, GroupCreateInput>,
|
||||||
|
Update<Group, GroupUpdateInput>,
|
||||||
|
Delete<Group>,
|
||||||
|
Restore<Group>
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private spinner: SpinnerService,
|
||||||
|
private apollo: Apollo,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
load(
|
||||||
|
filter?: Filter[] | undefined,
|
||||||
|
sort?: Sort[] | undefined,
|
||||||
|
skip?: number | undefined,
|
||||||
|
take?: number | undefined,
|
||||||
|
): Observable<QueryResult<Group>> {
|
||||||
|
return this.apollo
|
||||||
|
.query<{ groups: QueryResult<Group> }>({
|
||||||
|
query: gql`
|
||||||
|
query getGroups(
|
||||||
|
$filter: [GroupFilter]
|
||||||
|
$sort: [GroupSort]
|
||||||
|
$skip: Int
|
||||||
|
$take: Int
|
||||||
|
) {
|
||||||
|
groups(filter: $filter, sort: $sort, skip: $skip, take: $take) {
|
||||||
|
count
|
||||||
|
totalCount
|
||||||
|
nodes {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
|
||||||
|
...DB_MODEL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${DB_MODEL_FRAGMENT}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
filter: filter,
|
||||||
|
sort: sort,
|
||||||
|
skip: skip,
|
||||||
|
take: take,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError((err) => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.pipe(map((result) => result.data.groups));
|
||||||
|
}
|
||||||
|
|
||||||
|
loadById(id: number): Observable<Group> {
|
||||||
|
return this.apollo
|
||||||
|
.query<{ groups: QueryResult<Group> }>({
|
||||||
|
query: gql`
|
||||||
|
query getGroup($id: Int) {
|
||||||
|
group(filter: { id: { equal: $id } }) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
|
||||||
|
...DB_MODEL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${DB_MODEL_FRAGMENT}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
id: id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError((err) => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.pipe(map((result) => result.data.groups.nodes[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
create(object: GroupCreateInput): Observable<Group | undefined> {
|
||||||
|
return this.apollo
|
||||||
|
.mutate<{ group: { create: Group } }>({
|
||||||
|
mutation: gql`
|
||||||
|
mutation createGroup($input: GroupCreateInput!) {
|
||||||
|
group {
|
||||||
|
create(input: $input) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
|
||||||
|
...DB_MODEL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${DB_MODEL_FRAGMENT}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
name: object.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError((err) => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.pipe(map((result) => result.data?.group.create));
|
||||||
|
}
|
||||||
|
|
||||||
|
update(object: GroupUpdateInput): Observable<Group | undefined> {
|
||||||
|
return this.apollo
|
||||||
|
.mutate<{ group: { update: Group } }>({
|
||||||
|
mutation: gql`
|
||||||
|
mutation updateGroup($input: GroupUpdateInput!) {
|
||||||
|
group {
|
||||||
|
update(input: $input) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
|
||||||
|
...DB_MODEL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${DB_MODEL_FRAGMENT}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
id: object.id,
|
||||||
|
name: object.name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError((err) => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.pipe(map((result) => result.data?.group.update));
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(object: Group): Observable<boolean> {
|
||||||
|
return this.apollo
|
||||||
|
.mutate<{ group: { delete: boolean } }>({
|
||||||
|
mutation: gql`
|
||||||
|
mutation deleteGroup($id: ID!) {
|
||||||
|
group {
|
||||||
|
delete(id: $id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
id: object.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError((err) => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.pipe(map((result) => result.data?.group.delete ?? false));
|
||||||
|
}
|
||||||
|
|
||||||
|
restore(object: Group): Observable<boolean> {
|
||||||
|
return this.apollo
|
||||||
|
.mutate<{ group: { restore: boolean } }>({
|
||||||
|
mutation: gql`
|
||||||
|
mutation restoreGroup($id: ID!) {
|
||||||
|
group {
|
||||||
|
restore(id: $id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: {
|
||||||
|
id: object.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
catchError((err) => {
|
||||||
|
this.spinner.hide();
|
||||||
|
throw err;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.pipe(map((result) => result.data?.group.restore ?? false));
|
||||||
|
}
|
||||||
|
|
||||||
|
static provide(): Provider[] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
provide: PageDataService,
|
||||||
|
useClass: GroupsDataService,
|
||||||
|
},
|
||||||
|
GroupsDataService,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
43
web/src/app/modules/admin/groups/groups.module.ts
Normal file
43
web/src/app/modules/admin/groups/groups.module.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { NgModule } from "@angular/core";
|
||||||
|
import { CommonModule } from "@angular/common";
|
||||||
|
import { SharedModule } from "src/app/modules/shared/shared.module";
|
||||||
|
import { RouterModule, Routes } from "@angular/router";
|
||||||
|
import { PermissionGuard } from "src/app/core/guard/permission.guard";
|
||||||
|
import { PermissionsEnum } from "src/app/model/auth/permissionsEnum";
|
||||||
|
import { GroupsPage } from "src/app/modules/admin/groups/groups.page";
|
||||||
|
import { GroupFormPageComponent } from "src/app/modules/admin/groups/form-page/group-form-page.component";
|
||||||
|
import { GroupsDataService } from "src/app/modules/admin/groups/groups.data.service";
|
||||||
|
import { GroupsColumns } from "src/app/modules/admin/groups/groups.columns";
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: "",
|
||||||
|
title: "Groups",
|
||||||
|
component: GroupsPage,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: "create",
|
||||||
|
component: GroupFormPageComponent,
|
||||||
|
canActivate: [PermissionGuard],
|
||||||
|
data: {
|
||||||
|
permissions: [PermissionsEnum.apiKeysCreate],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "edit/:id",
|
||||||
|
component: GroupFormPageComponent,
|
||||||
|
canActivate: [PermissionGuard],
|
||||||
|
data: {
|
||||||
|
permissions: [PermissionsEnum.apiKeysUpdate],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [GroupsPage, GroupFormPageComponent],
|
||||||
|
imports: [CommonModule, SharedModule, RouterModule.forChild(routes)],
|
||||||
|
providers: [GroupsDataService.provide(), GroupsColumns.provide()],
|
||||||
|
})
|
||||||
|
export class GroupsModule {}
|
19
web/src/app/modules/admin/groups/groups.page.html
Normal file
19
web/src/app/modules/admin/groups/groups.page.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<app-table
|
||||||
|
[rows]="result.nodes"
|
||||||
|
[columns]="columns"
|
||||||
|
[rowsPerPageOptions]="rowsPerPageOptions"
|
||||||
|
[totalCount]="result.totalCount"
|
||||||
|
[requireAnyPermissions]="requiredPermissions"
|
||||||
|
countHeaderTranslation="group.count_header"
|
||||||
|
[loading]="loading"
|
||||||
|
[(filter)]="filter"
|
||||||
|
[(sort)]="sort"
|
||||||
|
[(skip)]="skip"
|
||||||
|
[(take)]="take"
|
||||||
|
(load)="load()"
|
||||||
|
[create]="true"
|
||||||
|
[update]="true"
|
||||||
|
(delete)="delete($event)"
|
||||||
|
(restore)="restore($event)"></app-table>
|
||||||
|
|
||||||
|
<router-outlet></router-outlet>
|
0
web/src/app/modules/admin/groups/groups.page.scss
Normal file
0
web/src/app/modules/admin/groups/groups.page.scss
Normal file
51
web/src/app/modules/admin/groups/groups.page.spec.ts
Normal file
51
web/src/app/modules/admin/groups/groups.page.spec.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { ApiKeysPage } from 'src/app/modules/admin/administration/api-keys/api-keys.page';
|
||||||
|
import { SharedModule } from 'src/app/modules/shared/shared.module';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { AuthService } from 'src/app/service/auth.service';
|
||||||
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
|
import { ErrorHandlingService } from 'src/app/service/error-handling.service';
|
||||||
|
import { ToastService } from 'src/app/service/toast.service';
|
||||||
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { PageDataService } from 'src/app/core/base/page.data.service';
|
||||||
|
import { ApiKeysDataService } from 'src/app/modules/admin/administration/api-keys/api-keys.data.service';
|
||||||
|
|
||||||
|
describe('ApiKeysComponent', () => {
|
||||||
|
let component: ApiKeysPage;
|
||||||
|
let fixture: ComponentFixture<ApiKeysPage>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ApiKeysPage],
|
||||||
|
imports: [SharedModule, TranslateModule.forRoot()],
|
||||||
|
providers: [
|
||||||
|
AuthService,
|
||||||
|
KeycloakService,
|
||||||
|
ErrorHandlingService,
|
||||||
|
ToastService,
|
||||||
|
MessageService,
|
||||||
|
ConfirmationService,
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: {
|
||||||
|
snapshot: { params: of({}) },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: PageDataService,
|
||||||
|
useClass: ApiKeysDataService,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ApiKeysPage);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
72
web/src/app/modules/admin/groups/groups.page.ts
Normal file
72
web/src/app/modules/admin/groups/groups.page.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { Component } from "@angular/core";
|
||||||
|
import { PageBase } from "src/app/core/base/page-base";
|
||||||
|
import { ToastService } from "src/app/service/toast.service";
|
||||||
|
import { ConfirmationDialogService } from "src/app/service/confirmation-dialog.service";
|
||||||
|
import { PermissionsEnum } from "src/app/model/auth/permissionsEnum";
|
||||||
|
import { Group } from "src/app/model/entities/group";
|
||||||
|
import { GroupsDataService } from "src/app/modules/admin/groups/groups.data.service";
|
||||||
|
import { GroupsColumns } from "src/app/modules/admin/groups/groups.columns";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: "app-groups",
|
||||||
|
templateUrl: "./groups.page.html",
|
||||||
|
styleUrl: "./groups.page.scss",
|
||||||
|
})
|
||||||
|
export class GroupsPage extends PageBase<
|
||||||
|
Group,
|
||||||
|
GroupsDataService,
|
||||||
|
GroupsColumns
|
||||||
|
> {
|
||||||
|
constructor(
|
||||||
|
private toast: ToastService,
|
||||||
|
private confirmation: ConfirmationDialogService,
|
||||||
|
) {
|
||||||
|
super(true, {
|
||||||
|
read: [PermissionsEnum.groups],
|
||||||
|
create: [PermissionsEnum.groupsCreate],
|
||||||
|
update: [PermissionsEnum.groupsUpdate],
|
||||||
|
delete: [PermissionsEnum.groupsDelete],
|
||||||
|
restore: [PermissionsEnum.groupsDelete],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
load(): void {
|
||||||
|
this.loading = true;
|
||||||
|
this.dataService
|
||||||
|
.load(this.filter, this.sort, this.skip, this.take)
|
||||||
|
.subscribe((result) => {
|
||||||
|
this.result = result;
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(group: Group): void {
|
||||||
|
this.confirmation.confirmDialog({
|
||||||
|
header: "dialog.delete.header",
|
||||||
|
message: "dialog.delete.message",
|
||||||
|
accept: () => {
|
||||||
|
this.loading = true;
|
||||||
|
this.dataService.delete(group).subscribe(() => {
|
||||||
|
this.toast.success("action.deleted");
|
||||||
|
this.load();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
messageParams: { entity: group.name },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
restore(group: Group): void {
|
||||||
|
this.confirmation.confirmDialog({
|
||||||
|
header: "dialog.restore.header",
|
||||||
|
message: "dialog.restore.message",
|
||||||
|
accept: () => {
|
||||||
|
this.loading = true;
|
||||||
|
this.dataService.restore(group).subscribe(() => {
|
||||||
|
this.toast.success("action.restored");
|
||||||
|
this.load();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
messageParams: { entity: group.name },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,30 +1,29 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from "@angular/core";
|
||||||
import { User } from 'src/app/model/auth/user';
|
import { User } from "src/app/model/auth/user";
|
||||||
import { BehaviorSubject, firstValueFrom, Observable } from 'rxjs';
|
import { BehaviorSubject, firstValueFrom, Observable } from "rxjs";
|
||||||
import { Apollo, gql } from 'apollo-angular';
|
import { Apollo, gql } from "apollo-angular";
|
||||||
import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
|
import { PermissionsEnum } from "src/app/model/auth/permissionsEnum";
|
||||||
import { KeycloakService } from 'keycloak-angular';
|
import { KeycloakService } from "keycloak-angular";
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from "rxjs/operators";
|
||||||
import { Logger } from 'src/app/service/logger.service';
|
import { Logger } from "src/app/service/logger.service";
|
||||||
|
|
||||||
const log = new Logger('AuthService');
|
const log = new Logger("AuthService");
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: "root",
|
||||||
})
|
})
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
protected anyPermissionForAdminPage = [
|
protected anyPermissionForAdminPage = [
|
||||||
PermissionsEnum.apiKeys,
|
PermissionsEnum.apiKeys,
|
||||||
PermissionsEnum.users,
|
PermissionsEnum.users,
|
||||||
PermissionsEnum.roles,
|
PermissionsEnum.roles,
|
||||||
PermissionsEnum.news,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
user$ = new BehaviorSubject<User | null>(null);
|
user$ = new BehaviorSubject<User | null>(null);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private apollo: Apollo,
|
private apollo: Apollo,
|
||||||
private keycloakService: KeycloakService
|
private keycloakService: KeycloakService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private requestUser() {
|
private requestUser() {
|
||||||
@ -61,11 +60,11 @@ export class AuthService {
|
|||||||
permission,
|
permission,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.pipe(map(result => result.data.userHasPermission));
|
.pipe(map((result) => result.data.userHasPermission));
|
||||||
}
|
}
|
||||||
|
|
||||||
private userHasAnyPermission(
|
private userHasAnyPermission(
|
||||||
permissions: PermissionsEnum[]
|
permissions: PermissionsEnum[],
|
||||||
): Observable<boolean> {
|
): Observable<boolean> {
|
||||||
return this.apollo
|
return this.apollo
|
||||||
.query<{ userHasAnyPermission: boolean }>({
|
.query<{ userHasAnyPermission: boolean }>({
|
||||||
@ -78,7 +77,7 @@ export class AuthService {
|
|||||||
permissions,
|
permissions,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.pipe(map(result => result.data.userHasAnyPermission));
|
.pipe(map((result) => result.data.userHasAnyPermission));
|
||||||
}
|
}
|
||||||
|
|
||||||
loadUser() {
|
loadUser() {
|
||||||
@ -89,8 +88,8 @@ export class AuthService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.requestUser().subscribe(result => {
|
this.requestUser().subscribe((result) => {
|
||||||
log.info('User loaded');
|
log.info("User loaded");
|
||||||
this.user$.next(result.data.user);
|
this.user$.next(result.data.user);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -112,10 +111,10 @@ export class AuthService {
|
|||||||
if (!this.user$.value) return false;
|
if (!this.user$.value) return false;
|
||||||
|
|
||||||
const userPermissions = this.user$.value.roles
|
const userPermissions = this.user$.value.roles
|
||||||
.map(role => (role.permissions ?? []).map(p => p.name))
|
.map((role) => (role.permissions ?? []).map((p) => p.name))
|
||||||
.flat();
|
.flat();
|
||||||
return permissions.every(permission =>
|
return permissions.every((permission) =>
|
||||||
userPermissions.includes(permission)
|
userPermissions.includes(permission),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +130,7 @@ export class AuthService {
|
|||||||
if (!this.user$.value) return false;
|
if (!this.user$.value) return false;
|
||||||
|
|
||||||
const permissions = this.user$.value.roles
|
const permissions = this.user$.value.roles
|
||||||
.map(role => (role.permissions ?? []).map(p => p.name))
|
.map((role) => (role.permissions ?? []).map((p) => p.name))
|
||||||
.flat();
|
.flat();
|
||||||
return permissions.includes(permission);
|
return permissions.includes(permission);
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,8 @@ export class ErrorHandlingService implements ErrorHandler {
|
|||||||
this.handleHttpError(error.networkError as HttpErrorResponse);
|
this.handleHttpError(error.networkError as HttpErrorResponse);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.handleHttpError(error);
|
console.error(error);
|
||||||
|
// this.handleHttpError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleHttpError(e: HttpErrorResponse) {
|
private handleHttpError(e: HttpErrorResponse) {
|
||||||
|
@ -1,99 +1,83 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import {Injectable} from '@angular/core';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import {BehaviorSubject} from 'rxjs';
|
||||||
import { MenuElement } from 'src/app/model/view/menu-element';
|
import {MenuElement} from 'src/app/model/view/menu-element';
|
||||||
import { Router } from '@angular/router';
|
import {AuthService} from 'src/app/service/auth.service';
|
||||||
import { AuthService } from 'src/app/service/auth.service';
|
import {PermissionsEnum} from 'src/app/model/auth/permissionsEnum';
|
||||||
import { PermissionsEnum } from 'src/app/model/auth/permissionsEnum';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class SidebarService {
|
export class SidebarService {
|
||||||
visible$ = new BehaviorSubject<boolean>(false);
|
visible$ = new BehaviorSubject<boolean>(true);
|
||||||
elements$ = new BehaviorSubject<MenuElement[]>([]);
|
elements$ = new BehaviorSubject<MenuElement[]>([]);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private auth: AuthService
|
||||||
private auth: AuthService
|
) {
|
||||||
) {
|
this.auth.user$.subscribe(user => {
|
||||||
this.router.events.subscribe(() => {
|
if (user) {
|
||||||
if (this.router.url.startsWith('/admin')) {
|
this.setElements().then();
|
||||||
this.show();
|
}
|
||||||
} else {
|
});
|
||||||
this.hide();
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.auth.user$.subscribe(user => {
|
show() {
|
||||||
if (user) {
|
this.visible$.next(true);
|
||||||
this.setElements().then();
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
show() {
|
hide() {
|
||||||
this.visible$.next(true);
|
this.visible$.next(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
// trust me, you'll need this async
|
||||||
this.visible$.next(false);
|
async setElements() {
|
||||||
}
|
const elements: MenuElement[] = [
|
||||||
|
{
|
||||||
|
label: 'common.groups',
|
||||||
|
icon: 'pi pi-tags',
|
||||||
|
routerLink: ['/admin/groups'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.urls',
|
||||||
|
icon: 'pi pi-tag',
|
||||||
|
routerLink: ['/admin/urls'],
|
||||||
|
},
|
||||||
|
await this.groupAdministration(),
|
||||||
|
];
|
||||||
|
this.elements$.next(elements);
|
||||||
|
}
|
||||||
|
|
||||||
// trust me, you'll need this async
|
async groupAdministration() {
|
||||||
async setElements() {
|
return {
|
||||||
const elements: MenuElement[] = [
|
label: 'sidebar.administration',
|
||||||
await this.groupPublic(),
|
icon: 'pi pi-wrench',
|
||||||
await this.groupAdministration(),
|
expanded: true,
|
||||||
];
|
items: [
|
||||||
this.elements$.next(elements);
|
{
|
||||||
}
|
label: 'sidebar.users',
|
||||||
|
icon: 'pi pi-user',
|
||||||
async groupPublic() {
|
routerLink: ['/admin/administration/users'],
|
||||||
return {
|
visible: await this.auth.hasAnyPermissionLazy([
|
||||||
label: 'sidebar.public',
|
PermissionsEnum.users,
|
||||||
icon: 'pi pi-globe',
|
]),
|
||||||
expanded: true,
|
},
|
||||||
items: [
|
{
|
||||||
{
|
label: 'sidebar.roles',
|
||||||
label: 'common.news',
|
icon: 'pi pi-user-edit',
|
||||||
icon: 'pi pi-file',
|
routerLink: ['/admin/administration/roles'],
|
||||||
routerLink: ['/admin/public/news'],
|
visible: await this.auth.hasAnyPermissionLazy([
|
||||||
},
|
PermissionsEnum.roles,
|
||||||
],
|
]),
|
||||||
};
|
},
|
||||||
}
|
{
|
||||||
|
label: 'sidebar.api_keys',
|
||||||
async groupAdministration() {
|
icon: 'pi pi-key',
|
||||||
return {
|
routerLink: ['/admin/administration/api-keys'],
|
||||||
label: 'sidebar.administration',
|
visible: await this.auth.hasAnyPermissionLazy([
|
||||||
icon: 'pi pi-wrench',
|
PermissionsEnum.apiKeys,
|
||||||
expanded: true,
|
]),
|
||||||
items: [
|
},
|
||||||
{
|
],
|
||||||
label: 'sidebar.users',
|
};
|
||||||
icon: 'pi pi-user',
|
}
|
||||||
routerLink: ['/admin/administration/users'],
|
|
||||||
visible: await this.auth.hasAnyPermissionLazy([
|
|
||||||
PermissionsEnum.users,
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'sidebar.roles',
|
|
||||||
icon: 'pi pi-user-edit',
|
|
||||||
routerLink: ['/admin/administration/roles'],
|
|
||||||
visible: await this.auth.hasAnyPermissionLazy([
|
|
||||||
PermissionsEnum.roles,
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'sidebar.api_keys',
|
|
||||||
icon: 'pi pi-key',
|
|
||||||
routerLink: ['/admin/administration/api-keys'],
|
|
||||||
visible: await this.auth.hasAnyPermissionLazy([
|
|
||||||
PermissionsEnum.apiKeys,
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,15 @@
|
|||||||
|
|
||||||
export const environment = {
|
export const environment = {
|
||||||
production: false,
|
production: false,
|
||||||
|
// auth: {
|
||||||
|
// url: 'https://auth.sh-edraft.de',
|
||||||
|
// realm: 'dev',
|
||||||
|
// clientId: 'open-redirect-client',
|
||||||
|
// },
|
||||||
auth: {
|
auth: {
|
||||||
url: 'https://auth.sh-edraft.de',
|
url: 'https://keycloak.maxlan.de',
|
||||||
realm: 'dev',
|
realm: 'develop',
|
||||||
clientId: 'open-redirect-client',
|
clientId: 'lan-maestro-client-local',
|
||||||
},
|
},
|
||||||
api: {
|
api: {
|
||||||
url: 'http://localhost:5000/api',
|
url: 'http://localhost:5000/api',
|
||||||
|
Loading…
Reference in New Issue
Block a user