Editable short urls
This commit is contained in:
parent
bb44e59a14
commit
7b8e818339
@ -52,7 +52,7 @@ input ShortUrlCreateInput {
|
|||||||
shortUrl: String!
|
shortUrl: String!
|
||||||
targetUrl: String!
|
targetUrl: String!
|
||||||
description: String
|
description: String
|
||||||
group: ID
|
groupId: ID
|
||||||
}
|
}
|
||||||
|
|
||||||
input ShortUrlUpdateInput {
|
input ShortUrlUpdateInput {
|
||||||
@ -60,5 +60,5 @@ input ShortUrlUpdateInput {
|
|||||||
shortUrl: String
|
shortUrl: String
|
||||||
targetUrl: String
|
targetUrl: String
|
||||||
description: String
|
description: String
|
||||||
group: ID
|
groupId: ID
|
||||||
}
|
}
|
||||||
|
@ -42,3 +42,12 @@ class Mutation(MutationABC):
|
|||||||
Permissions.groups_delete,
|
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_dao(groupDao)
|
||||||
.with_filter(GroupFilter)
|
.with_filter(GroupFilter)
|
||||||
.with_sort(Sort[Group])
|
.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
|
# partially public to load redirect if not resolved/redirected by api
|
||||||
self.field(
|
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,
|
(m) => m.GroupsModule,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "urls",
|
||||||
|
loadChildren: () =>
|
||||||
|
import("src/app/modules/admin/short-urls/short-urls.module").then(
|
||||||
|
(m) => m.ShortUrlsModule,
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "administration",
|
path: "administration",
|
||||||
loadChildren: () =>
|
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,
|
input,
|
||||||
.p-checkbox-box {
|
.p-checkbox-box,
|
||||||
|
.p-dropdown {
|
||||||
border: 1px solid $accentColor;
|
border: 1px solid $accentColor;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-dropdown {
|
||||||
|
width: 100%;
|
||||||
|
span {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
@ -216,19 +224,22 @@ footer {
|
|||||||
gap: 15px;
|
gap: 15px;
|
||||||
|
|
||||||
.form-page-input {
|
.form-page-input {
|
||||||
display: flex;
|
display: grid;
|
||||||
align-items: center;
|
grid-template-columns: 1fr 2fr;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
flex: 1;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
grid-column: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.value {
|
.value {
|
||||||
flex: 2;
|
grid-column: 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.form-page-section {
|
.form-page-section {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
Loading…
Reference in New Issue
Block a user