Editable short urls
This commit is contained in:
parent
bb44e59a14
commit
7b8e818339
@ -52,7 +52,7 @@ input ShortUrlCreateInput {
|
||||
shortUrl: String!
|
||||
targetUrl: String!
|
||||
description: String
|
||||
group: ID
|
||||
groupId: ID
|
||||
}
|
||||
|
||||
input ShortUrlUpdateInput {
|
||||
@ -60,5 +60,5 @@ input ShortUrlUpdateInput {
|
||||
shortUrl: String
|
||||
targetUrl: String
|
||||
description: String
|
||||
group: ID
|
||||
groupId: ID
|
||||
}
|
||||
|
@ -42,3 +42,12 @@ class Mutation(MutationABC):
|
||||
Permissions.groups_delete,
|
||||
],
|
||||
)
|
||||
self.add_mutation_type(
|
||||
"shortUrl",
|
||||
"ShortUrl",
|
||||
require_any_permission=[
|
||||
Permissions.short_urls_create,
|
||||
Permissions.short_urls_update,
|
||||
Permissions.short_urls_delete,
|
||||
],
|
||||
)
|
||||
|
@ -85,7 +85,7 @@ class Query(QueryABC):
|
||||
.with_dao(groupDao)
|
||||
.with_filter(GroupFilter)
|
||||
.with_sort(Sort[Group])
|
||||
.with_require_any_permission([Permissions.groups])
|
||||
.with_require_any_permission([Permissions.groups, Permissions.short_urls_create, Permissions.short_urls_update])
|
||||
)
|
||||
# partially public to load redirect if not resolved/redirected by api
|
||||
self.field(
|
||||
|
25
web/src/app/model/entities/short-url.ts
Normal file
25
web/src/app/model/entities/short-url.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { DbModel } from "src/app/model/entities/db-model";
|
||||
import { Permission } from "src/app/model/entities/role";
|
||||
import { Group } from "src/app/model/entities/group";
|
||||
|
||||
export interface ShortUrl extends DbModel {
|
||||
shortUrl: string;
|
||||
targetUrl: string;
|
||||
description: string;
|
||||
group?: Group;
|
||||
}
|
||||
|
||||
export interface ShortUrlCreateInput {
|
||||
shortUrl: string;
|
||||
targetUrl: string;
|
||||
description: string;
|
||||
groupId: number;
|
||||
}
|
||||
|
||||
export interface ShortUrlUpdateInput {
|
||||
id: number;
|
||||
shortUrl: string;
|
||||
targetUrl: string;
|
||||
description: string;
|
||||
groupId: number;
|
||||
}
|
@ -11,6 +11,13 @@ const routes: Routes = [
|
||||
(m) => m.GroupsModule,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "urls",
|
||||
loadChildren: () =>
|
||||
import("src/app/modules/admin/short-urls/short-urls.module").then(
|
||||
(m) => m.ShortUrlsModule,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "administration",
|
||||
loadChildren: () =>
|
||||
|
@ -0,0 +1,60 @@
|
||||
<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.short_url' | translate }}</p>
|
||||
<input
|
||||
pInputText
|
||||
class="value"
|
||||
type="text"
|
||||
formControlName="shortUrl"/>
|
||||
</div>
|
||||
<div class="form-page-input">
|
||||
<p class="label">{{ 'common.target_url' | translate }}</p>
|
||||
<input
|
||||
pInputText
|
||||
class="value"
|
||||
type="text"
|
||||
formControlName="targetUrl"/>
|
||||
</div>
|
||||
<div class="form-page-input">
|
||||
<p class="label">{{ 'common.description' | translate }}</p>
|
||||
<input
|
||||
pInputText
|
||||
class="value"
|
||||
type="text"
|
||||
formControlName="description"/>
|
||||
</div>
|
||||
<div class="form-page-input">
|
||||
<p class="label">{{ 'common.group' | translate }}</p>
|
||||
<p-dropdown
|
||||
class="value"
|
||||
[options]="groups"
|
||||
formControlName="groupId"
|
||||
[showClear]="true"
|
||||
[filter]="true"
|
||||
filterBy="name"
|
||||
optionLabel="name"
|
||||
optionValue="id"
|
||||
></p-dropdown>
|
||||
</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,130 @@
|
||||
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 {
|
||||
ShortUrl,
|
||||
ShortUrlCreateInput,
|
||||
ShortUrlUpdateInput,
|
||||
} from "src/app/model/entities/short-url";
|
||||
import { ShortUrlsDataService } from "src/app/modules/admin/short-urls/short-urls.data.service";
|
||||
import { Group } from "src/app/model/entities/group";
|
||||
|
||||
@Component({
|
||||
selector: "app-short-url-form-page",
|
||||
templateUrl: "./short-url-form-page.component.html",
|
||||
styleUrl: "./short-url-form-page.component.scss",
|
||||
})
|
||||
export class ShortUrlFormPageComponent extends FormPageBase<
|
||||
ShortUrl,
|
||||
ShortUrlCreateInput,
|
||||
ShortUrlUpdateInput,
|
||||
ShortUrlsDataService
|
||||
> {
|
||||
groups: Group[] = [];
|
||||
|
||||
constructor(private toast: ToastService) {
|
||||
super();
|
||||
this.dataService.getAllGroups().subscribe((groups) => {
|
||||
this.groups = groups;
|
||||
});
|
||||
|
||||
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(): ShortUrl {
|
||||
return {} as ShortUrl;
|
||||
}
|
||||
|
||||
buildForm() {
|
||||
this.form = new FormGroup({
|
||||
id: new FormControl<number | undefined>(undefined),
|
||||
shortUrl: new FormControl<string | undefined>(
|
||||
undefined,
|
||||
Validators.required,
|
||||
),
|
||||
targetUrl: new FormControl<string | undefined>(
|
||||
undefined,
|
||||
Validators.required,
|
||||
),
|
||||
description: new FormControl<string | undefined>(undefined),
|
||||
groupId: new FormControl<number | undefined>(undefined),
|
||||
});
|
||||
this.form.controls["id"].disable();
|
||||
}
|
||||
|
||||
setForm(node?: ShortUrl) {
|
||||
this.form.controls["id"].setValue(node?.id);
|
||||
this.form.controls["shortUrl"].setValue(node?.shortUrl);
|
||||
this.form.controls["targetUrl"].setValue(node?.targetUrl);
|
||||
this.form.controls["description"].setValue(node?.description);
|
||||
this.form.controls["groupId"].setValue(node?.group?.id);
|
||||
}
|
||||
|
||||
getCreateInput(): ShortUrlCreateInput {
|
||||
return {
|
||||
shortUrl: this.form.controls["shortUrl"].pristine
|
||||
? undefined
|
||||
: (this.form.controls["shortUrl"].value ?? undefined),
|
||||
targetUrl: this.form.controls["targetUrl"].pristine
|
||||
? undefined
|
||||
: (this.form.controls["targetUrl"].value ?? undefined),
|
||||
description: this.form.controls["description"].pristine
|
||||
? undefined
|
||||
: (this.form.controls["description"].value ?? undefined),
|
||||
groupId: this.form.controls["groupId"].pristine
|
||||
? undefined
|
||||
: (this.form.controls["groupId"].value ?? undefined),
|
||||
};
|
||||
}
|
||||
|
||||
getUpdateInput(): ShortUrlUpdateInput {
|
||||
if (!this.node?.id) {
|
||||
throw new Error("Node id is missing");
|
||||
}
|
||||
|
||||
return {
|
||||
id: this.form.controls["id"].value,
|
||||
shortUrl: this.form.controls["shortUrl"].pristine
|
||||
? undefined
|
||||
: (this.form.controls["shortUrl"].value ?? undefined),
|
||||
targetUrl: this.form.controls["targetUrl"].pristine
|
||||
? undefined
|
||||
: (this.form.controls["targetUrl"].value ?? undefined),
|
||||
description: this.form.controls["description"].pristine
|
||||
? undefined
|
||||
: (this.form.controls["description"].value ?? undefined),
|
||||
groupId: this.form.controls["groupId"].pristine
|
||||
? undefined
|
||||
: (this.form.controls["groupId"].value ?? undefined),
|
||||
};
|
||||
}
|
||||
|
||||
create(apiKey: ShortUrlCreateInput): void {
|
||||
this.dataService.create(apiKey).subscribe(() => {
|
||||
this.spinner.hide();
|
||||
this.toast.success("action.created");
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
|
||||
update(apiKey: ShortUrlUpdateInput): void {
|
||||
this.dataService.update(apiKey).subscribe(() => {
|
||||
this.spinner.hide();
|
||||
this.toast.success("action.created");
|
||||
this.close();
|
||||
});
|
||||
}
|
||||
}
|
56
web/src/app/modules/admin/short-urls/short-urls.columns.ts
Normal file
56
web/src/app/modules/admin/short-urls/short-urls.columns.ts
Normal file
@ -0,0 +1,56 @@
|
||||
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 { ShortUrl } from "src/app/model/entities/short-url";
|
||||
|
||||
@Injectable()
|
||||
export class ShortUrlsColumns extends PageColumns<ShortUrl> {
|
||||
get(): TableColumn<ShortUrl>[] {
|
||||
return [
|
||||
ID_COLUMN,
|
||||
{
|
||||
name: "short_url",
|
||||
label: "common.short_url",
|
||||
type: "text",
|
||||
filterable: true,
|
||||
value: (row: ShortUrl) => row.shortUrl,
|
||||
},
|
||||
{
|
||||
name: "target_url",
|
||||
label: "common.target_url",
|
||||
type: "text",
|
||||
filterable: true,
|
||||
value: (row: ShortUrl) => row.targetUrl,
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
label: "common.description",
|
||||
type: "text",
|
||||
filterable: true,
|
||||
value: (row: ShortUrl) => row.description,
|
||||
},
|
||||
{
|
||||
name: "group",
|
||||
label: "common.group",
|
||||
type: "text",
|
||||
filterable: true,
|
||||
value: (row: ShortUrl) => row.group?.name,
|
||||
},
|
||||
...DB_MODEL_COLUMNS,
|
||||
];
|
||||
}
|
||||
|
||||
static provide(): Provider[] {
|
||||
return [
|
||||
{
|
||||
provide: PageColumns,
|
||||
useClass: ShortUrlsColumns,
|
||||
},
|
||||
ShortUrlsColumns,
|
||||
];
|
||||
}
|
||||
}
|
274
web/src/app/modules/admin/short-urls/short-urls.data.service.ts
Normal file
274
web/src/app/modules/admin/short-urls/short-urls.data.service.ts
Normal file
@ -0,0 +1,274 @@
|
||||
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 {
|
||||
ShortUrl,
|
||||
ShortUrlCreateInput,
|
||||
ShortUrlUpdateInput,
|
||||
} from "src/app/model/entities/short-url";
|
||||
import { Group } from "src/app/model/entities/group";
|
||||
|
||||
@Injectable()
|
||||
export class ShortUrlsDataService
|
||||
extends PageDataService<ShortUrl>
|
||||
implements
|
||||
Create<ShortUrl, ShortUrlCreateInput>,
|
||||
Update<ShortUrl, ShortUrlUpdateInput>,
|
||||
Delete<ShortUrl>,
|
||||
Restore<ShortUrl>
|
||||
{
|
||||
constructor(
|
||||
private spinner: SpinnerService,
|
||||
private apollo: Apollo,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
load(
|
||||
filter?: Filter[] | undefined,
|
||||
sort?: Sort[] | undefined,
|
||||
skip?: number | undefined,
|
||||
take?: number | undefined,
|
||||
): Observable<QueryResult<ShortUrl>> {
|
||||
return this.apollo
|
||||
.query<{ shortUrls: QueryResult<ShortUrl> }>({
|
||||
query: gql`
|
||||
query getShortUrls(
|
||||
$filter: [ShortUrlFilter]
|
||||
$sort: [ShortUrlSort]
|
||||
$skip: Int
|
||||
$take: Int
|
||||
) {
|
||||
shortUrls(filter: $filter, sort: $sort, skip: $skip, take: $take) {
|
||||
count
|
||||
totalCount
|
||||
nodes {
|
||||
id
|
||||
shortUrl
|
||||
targetUrl
|
||||
description
|
||||
group {
|
||||
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.shortUrls));
|
||||
}
|
||||
|
||||
loadById(id: number): Observable<ShortUrl> {
|
||||
return this.apollo
|
||||
.query<{ shortUrls: QueryResult<ShortUrl> }>({
|
||||
query: gql`
|
||||
query getShortUrl($id: Int) {
|
||||
shortUrl(filter: { id: { equal: $id } }) {
|
||||
id
|
||||
shortUrl
|
||||
targetUrl
|
||||
description
|
||||
group {
|
||||
id
|
||||
name
|
||||
}
|
||||
|
||||
...DB_MODEL
|
||||
}
|
||||
}
|
||||
|
||||
${DB_MODEL_FRAGMENT}
|
||||
`,
|
||||
variables: {
|
||||
id: id,
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
)
|
||||
.pipe(map((result) => result.data.shortUrls.nodes[0]));
|
||||
}
|
||||
|
||||
create(object: ShortUrlCreateInput): Observable<ShortUrl | undefined> {
|
||||
return this.apollo
|
||||
.mutate<{ shortUrl: { create: ShortUrl } }>({
|
||||
mutation: gql`
|
||||
mutation createShortUrl($input: ShortUrlCreateInput!) {
|
||||
shortUrl {
|
||||
create(input: $input) {
|
||||
id
|
||||
description
|
||||
|
||||
...DB_MODEL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${DB_MODEL_FRAGMENT}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
shortUrl: object.shortUrl,
|
||||
targetUrl: object.targetUrl,
|
||||
description: object.description,
|
||||
groupId: object.groupId,
|
||||
},
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
)
|
||||
.pipe(map((result) => result.data?.shortUrl.create));
|
||||
}
|
||||
|
||||
update(object: ShortUrlUpdateInput): Observable<ShortUrl | undefined> {
|
||||
return this.apollo
|
||||
.mutate<{ shortUrl: { update: ShortUrl } }>({
|
||||
mutation: gql`
|
||||
mutation updateShortUrl($input: ShortUrlUpdateInput!) {
|
||||
shortUrl {
|
||||
update(input: $input) {
|
||||
id
|
||||
description
|
||||
|
||||
...DB_MODEL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${DB_MODEL_FRAGMENT}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
id: object.id,
|
||||
shortUrl: object.shortUrl,
|
||||
targetUrl: object.targetUrl,
|
||||
description: object.description,
|
||||
groupId: object.groupId,
|
||||
},
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
)
|
||||
.pipe(map((result) => result.data?.shortUrl.update));
|
||||
}
|
||||
|
||||
delete(object: ShortUrl): Observable<boolean> {
|
||||
return this.apollo
|
||||
.mutate<{ shortUrl: { delete: boolean } }>({
|
||||
mutation: gql`
|
||||
mutation deleteShortUrl($id: ID!) {
|
||||
shortUrl {
|
||||
delete(id: $id)
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: object.id,
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
)
|
||||
.pipe(map((result) => result.data?.shortUrl.delete ?? false));
|
||||
}
|
||||
|
||||
restore(object: ShortUrl): Observable<boolean> {
|
||||
return this.apollo
|
||||
.mutate<{ shortUrl: { restore: boolean } }>({
|
||||
mutation: gql`
|
||||
mutation restoreShortUrl($id: ID!) {
|
||||
shortUrl {
|
||||
restore(id: $id)
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: object.id,
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
)
|
||||
.pipe(map((result) => result.data?.shortUrl.restore ?? false));
|
||||
}
|
||||
|
||||
getAllGroups() {
|
||||
return this.apollo
|
||||
.query<{ groups: QueryResult<Group> }>({
|
||||
query: gql`
|
||||
query getGroups {
|
||||
groups {
|
||||
nodes {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
this.spinner.hide();
|
||||
throw err;
|
||||
}),
|
||||
)
|
||||
.pipe(map((result) => result.data.groups.nodes));
|
||||
}
|
||||
|
||||
static provide(): Provider[] {
|
||||
return [
|
||||
{
|
||||
provide: PageDataService,
|
||||
useClass: ShortUrlsDataService,
|
||||
},
|
||||
ShortUrlsDataService,
|
||||
];
|
||||
}
|
||||
}
|
43
web/src/app/modules/admin/short-urls/short-urls.module.ts
Normal file
43
web/src/app/modules/admin/short-urls/short-urls.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 { ShortUrlsPage } from "src/app/modules/admin/short-urls/short-urls.page";
|
||||
import { ShortUrlFormPageComponent } from "src/app/modules/admin/short-urls/form-page/short-url-form-page.component";
|
||||
import { ShortUrlsDataService } from "src/app/modules/admin/short-urls/short-urls.data.service";
|
||||
import { ShortUrlsColumns } from "src/app/modules/admin/short-urls/short-urls.columns";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
title: "ShortUrls",
|
||||
component: ShortUrlsPage,
|
||||
children: [
|
||||
{
|
||||
path: "create",
|
||||
component: ShortUrlFormPageComponent,
|
||||
canActivate: [PermissionGuard],
|
||||
data: {
|
||||
permissions: [PermissionsEnum.apiKeysCreate],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "edit/:id",
|
||||
component: ShortUrlFormPageComponent,
|
||||
canActivate: [PermissionGuard],
|
||||
data: {
|
||||
permissions: [PermissionsEnum.apiKeysUpdate],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [ShortUrlsPage, ShortUrlFormPageComponent],
|
||||
imports: [CommonModule, SharedModule, RouterModule.forChild(routes)],
|
||||
providers: [ShortUrlsDataService.provide(), ShortUrlsColumns.provide()],
|
||||
})
|
||||
export class ShortUrlsModule {}
|
19
web/src/app/modules/admin/short-urls/short-urls.page.html
Normal file
19
web/src/app/modules/admin/short-urls/short-urls.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>
|
51
web/src/app/modules/admin/short-urls/short-urls.page.spec.ts
Normal file
51
web/src/app/modules/admin/short-urls/short-urls.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/short-urls/short-urls.page.ts
Normal file
72
web/src/app/modules/admin/short-urls/short-urls.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 { ShortUrl } from "src/app/model/entities/short-url";
|
||||
import { ShortUrlsDataService } from "src/app/modules/admin/short-urls/short-urls.data.service";
|
||||
import { ShortUrlsColumns } from "src/app/modules/admin/short-urls/short-urls.columns";
|
||||
|
||||
@Component({
|
||||
selector: "app-short-urls",
|
||||
templateUrl: "./short-urls.page.html",
|
||||
styleUrl: "./short-urls.page.scss",
|
||||
})
|
||||
export class ShortUrlsPage extends PageBase<
|
||||
ShortUrl,
|
||||
ShortUrlsDataService,
|
||||
ShortUrlsColumns
|
||||
> {
|
||||
constructor(
|
||||
private toast: ToastService,
|
||||
private confirmation: ConfirmationDialogService,
|
||||
) {
|
||||
super(true, {
|
||||
read: [PermissionsEnum.shortUrls],
|
||||
create: [PermissionsEnum.shortUrlsCreate],
|
||||
update: [PermissionsEnum.shortUrlsUpdate],
|
||||
delete: [PermissionsEnum.shortUrlsDelete],
|
||||
restore: [PermissionsEnum.shortUrlsDelete],
|
||||
});
|
||||
}
|
||||
|
||||
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: ShortUrl): 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.shortUrl },
|
||||
});
|
||||
}
|
||||
|
||||
restore(group: ShortUrl): 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.shortUrl },
|
||||
});
|
||||
}
|
||||
}
|
@ -56,10 +56,18 @@ body {
|
||||
}
|
||||
|
||||
input,
|
||||
.p-checkbox-box {
|
||||
.p-checkbox-box,
|
||||
.p-dropdown {
|
||||
border: 1px solid $accentColor;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.p-dropdown {
|
||||
width: 100%;
|
||||
span {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
@ -216,19 +224,22 @@ footer {
|
||||
gap: 15px;
|
||||
|
||||
.form-page-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr;
|
||||
gap: 5px;
|
||||
|
||||
.label {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 2;
|
||||
grid-column: 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.form-page-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
Loading…
Reference in New Issue
Block a user