Edit subjects, work on user list, add authentication support
Improved type safety
This commit is contained in:
parent
a0bc7d3585
commit
38b06f54e6
20 changed files with 135 additions and 103 deletions
|
@ -18,6 +18,6 @@ fi
|
||||||
echo "Installing packages"
|
echo "Installing packages"
|
||||||
npm install
|
npm install
|
||||||
echo "Running application"
|
echo "Running application"
|
||||||
npm run rebuild
|
npm run clean
|
||||||
wait-for-it database:3306 -t 0
|
wait-for-it database:3306 -t 0
|
||||||
npm run start:watch
|
npm run start:watch
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { arg, authorized, GraphQLBindings, ID, mutation, query, resolver, ResolverData } from '@loopback/graphql';
|
import { arg, authorized, GraphQLBindings, ID, Int, mutation, query, resolver, ResolverData } from '@loopback/graphql';
|
||||||
import { User } from '../models';
|
import { User } from '../models';
|
||||||
import { repository } from '@loopback/repository';
|
import { repository } from '@loopback/repository';
|
||||||
import { RevTokenRepository, UserRepository } from '../repositories';
|
import { RevTokenRepository, UserRepository } from '../repositories';
|
||||||
|
@ -10,9 +10,10 @@ import { SecurityBindings, UserProfile } from '@loopback/security';
|
||||||
import { genSalt, hash } from 'bcryptjs';
|
import { genSalt, hash } from 'bcryptjs';
|
||||||
import { UserRegisterInput } from '../graphql-types/input/user-register.input';
|
import { UserRegisterInput } from '../graphql-types/input/user-register.input';
|
||||||
import { validated } from '../helpers';
|
import { validated } from '../helpers';
|
||||||
import { LoginResult } from '../graphql-types/user';
|
import { LoginResult, UserList } from '../graphql-types/user';
|
||||||
import { UserUpdateInput } from '../graphql-types/input/user-update.input';
|
import { UserUpdateInput } from '../graphql-types/input/user-update.input';
|
||||||
import { SzakdolgozatBindings } from '../bindings';
|
import { SzakdolgozatBindings } from '../bindings';
|
||||||
|
import { listResponse } from '../graphql-types/list';
|
||||||
|
|
||||||
@resolver(of => User)
|
@resolver(of => User)
|
||||||
export class UserResolver {
|
export class UserResolver {
|
||||||
|
@ -69,20 +70,26 @@ export class UserResolver {
|
||||||
|
|
||||||
@authorized()
|
@authorized()
|
||||||
@query(returns => User, {name: 'user'})
|
@query(returns => User, {name: 'user'})
|
||||||
async findById(@arg('id') id: number): Promise<User> {
|
async findById(@arg('id', returns => ID) id: number): Promise<User> {
|
||||||
return this.userRepository.findById(id);
|
return this.userRepository.findById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@authorized()
|
||||||
|
@query(returns => [User])
|
||||||
|
async users(@arg('limit', returns => Int) limit: number, @arg('offset', returns => Int) offset: number) {
|
||||||
|
return await listResponse(this.userRepository, offset, limit, UserList);
|
||||||
|
}
|
||||||
|
|
||||||
@authorized()
|
@authorized()
|
||||||
@mutation(returns => Boolean)
|
@mutation(returns => Boolean)
|
||||||
async userUpdate(@arg('id', returns => ID) id: number, @arg('user') user: UserUpdateInput): Promise<boolean> {
|
async userUpdate(@arg('user') user: UserUpdateInput): Promise<boolean> {
|
||||||
if (id === +this.user?.id) { //TODO: this.user
|
if (user.id === +this.user?.id) { //TODO: this.user
|
||||||
const loggedInUser = await this.userService.findUserById(this.user.id);
|
const loggedInUser = await this.userService.findUserById(this.user.id);
|
||||||
if (user.isAdmin !== undefined && loggedInUser.isAdmin !== user.isAdmin) {
|
if (user.isAdmin !== undefined && loggedInUser.isAdmin !== user.isAdmin) {
|
||||||
throw new Error('Cannot change admin status of self');
|
throw new Error('Cannot change admin status of self');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this.userRepository.updateById(id, user);
|
await this.userRepository.updateById(user.id, user);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import { User } from '../../models';
|
import { User } from '../../models';
|
||||||
import { field, inputType } from '@loopback/graphql';
|
import { field, ID, inputType } from '@loopback/graphql';
|
||||||
import { UserProperties } from '../user';
|
import { UserProperties } from '../user';
|
||||||
import { property } from '@loopback/repository';
|
import { property } from '@loopback/repository';
|
||||||
|
|
||||||
@inputType()
|
@inputType()
|
||||||
export class UserUpdateInput implements Partial<Pick<User, 'name' | 'email' | 'password' | 'isAdmin'>> {
|
export class UserUpdateInput implements Partial<Pick<User, 'id' | 'name' | 'email' | 'password' | 'isAdmin'>> {
|
||||||
|
@field(returns => ID)
|
||||||
|
id: number;
|
||||||
@field({nullable: true})
|
@field({nullable: true})
|
||||||
@property(UserProperties.email)
|
@property(UserProperties.email)
|
||||||
email?: string;
|
email?: string;
|
||||||
|
|
|
@ -14,7 +14,7 @@ export interface ListResponse<T> {
|
||||||
list: T[];
|
list: T[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listResponse<T extends Entity, U extends ListResponse<T>>(repo: DefaultCrudRepository<T, number>, offset: number, limit: number, listType: ClassType<U>) {
|
export async function listResponse<T extends Entity, U extends ListResponse<Partial<T>>>(repo: DefaultCrudRepository<T, number>, offset: number, limit: number, listType: ClassType<U>) {
|
||||||
const list = new listType();
|
const list = new listType();
|
||||||
list.list = await repo.find({offset, limit});
|
list.list = await repo.find({offset, limit});
|
||||||
list.count = (await repo.count()).count;
|
list.count = (await repo.count()).count;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { User } from '../models';
|
import { User } from '../models';
|
||||||
import { field, objectType } from '@loopback/graphql';
|
import { field, Int, objectType } from '@loopback/graphql';
|
||||||
|
import { ListResponse } from './list';
|
||||||
|
|
||||||
@objectType()
|
@objectType()
|
||||||
export class UserResult implements Pick<User, 'id' | 'email' | 'name' | 'isAdmin'> {
|
export class UserResult implements Pick<User, 'id' | 'email' | 'name' | 'isAdmin'> {
|
||||||
|
@ -21,6 +22,14 @@ export class LoginResult {
|
||||||
user: UserResult;
|
user: UserResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objectType()
|
||||||
|
export class UserList implements ListResponse<UserResult> {
|
||||||
|
@field(returns => Int)
|
||||||
|
count: number;
|
||||||
|
@field(returns => [UserResult])
|
||||||
|
list: UserResult[];
|
||||||
|
}
|
||||||
|
|
||||||
export const UserProperties = {
|
export const UserProperties = {
|
||||||
id: {
|
id: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { Observable } from 'rxjs';
|
||||||
import { filter, map, shareReplay } from 'rxjs/operators';
|
import { filter, map, shareReplay } from 'rxjs/operators';
|
||||||
import { LoginService } from './auth/login.service';
|
import { LoginService } from './auth/login.service';
|
||||||
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Route, Router, Routes } from '@angular/router';
|
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Route, Router, Routes } from '@angular/router';
|
||||||
import { ApiService } from './services/api.service';
|
|
||||||
import { RouteData } from './app-routing.module';
|
import { RouteData } from './app-routing.module';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
@ -26,13 +25,11 @@ export class AppComponent implements OnInit {
|
||||||
{path: 'subjects', requiredRole: 'admin'}
|
{path: 'subjects', requiredRole: 'admin'}
|
||||||
];
|
];
|
||||||
|
|
||||||
routeSegments: RouteSegment[];
|
|
||||||
|
|
||||||
pageTitle: string;
|
pageTitle: string;
|
||||||
|
|
||||||
private activeRouteTitle: string;
|
private activeRouteTitle: string;
|
||||||
|
|
||||||
constructor(private breakpointObserver: BreakpointObserver, public loginService: LoginService, private api: ApiService,
|
constructor(private breakpointObserver: BreakpointObserver, public loginService: LoginService,
|
||||||
private router: Router, private activeRoute: ActivatedRoute, private title: Title) {
|
private router: Router, private activeRoute: ActivatedRoute, private title: Title) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,51 @@
|
||||||
import { NgModule } from '@angular/core';
|
import { Injector, NgModule } from '@angular/core';
|
||||||
import { APOLLO_OPTIONS } from 'apollo-angular';
|
import { APOLLO_OPTIONS } from 'apollo-angular';
|
||||||
import { ApolloClientOptions, InMemoryCache } from '@apollo/client/core';
|
import { ApolloClientOptions, ApolloLink, InMemoryCache } from '@apollo/client/core';
|
||||||
import { HttpLink } from 'apollo-angular/http';
|
import { HttpLink } from 'apollo-angular/http';
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
|
import { setContext } from '@apollo/client/link/context';
|
||||||
|
import { onError } from '@apollo/client/link/error';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { LoginService } from './auth/login.service';
|
||||||
|
|
||||||
const uri = environment.backendUrl + '/graphql'; // <-- add the URL of the GraphQL server here
|
const uri = environment.backendUrl + '/graphql'; // <-- add the URL of the GraphQL server here
|
||||||
export function createApollo(httpLink: HttpLink): ApolloClientOptions<any> {
|
export function createApollo(httpLink: HttpLink, routerService: Router, injector: Injector): ApolloClientOptions<any> {
|
||||||
|
const auth = setContext((operation, context) => {
|
||||||
|
const token = injector.get(LoginService).token;
|
||||||
|
|
||||||
|
if (token === null) {
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
return {
|
return {
|
||||||
link: httpLink.create({uri}),
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorLink = onError(({graphQLErrors, networkError}) => {
|
||||||
|
if (graphQLErrors) {
|
||||||
|
graphQLErrors.map((graphqlError) => {
|
||||||
|
console.log(graphqlError);
|
||||||
|
if (graphqlError.message.startsWith('Error verifying token')) {
|
||||||
|
injector.get(LoginService).deleteToken();
|
||||||
|
routerService.navigateByUrl('/');
|
||||||
|
} else {
|
||||||
|
alert(graphqlError.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (networkError) {
|
||||||
|
const errorMessage = networkError.message;
|
||||||
|
console.log(errorMessage);
|
||||||
|
alert(errorMessage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
link: ApolloLink.from([auth, errorLink, httpLink.create({uri})]),
|
||||||
cache: new InMemoryCache(),
|
cache: new InMemoryCache(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -17,7 +55,7 @@ export function createApollo(httpLink: HttpLink): ApolloClientOptions<any> {
|
||||||
{
|
{
|
||||||
provide: APOLLO_OPTIONS,
|
provide: APOLLO_OPTIONS,
|
||||||
useFactory: createApollo,
|
useFactory: createApollo,
|
||||||
deps: [HttpLink],
|
deps: [HttpLink, Router, Injector],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,3 +16,7 @@ query Subject($id: ID!) {
|
||||||
description
|
description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutation EditSubject($input: SubjectUpdateInput!) {
|
||||||
|
subjectUpdate(subject: $input)
|
||||||
|
}
|
||||||
|
|
|
@ -13,3 +13,25 @@ mutation Login($email: String!, $password: String!) {
|
||||||
mutation Logout {
|
mutation Logout {
|
||||||
logout
|
logout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query UserList($limit: Int!, $offset: Int!) {
|
||||||
|
users(limit: $limit, offset: $offset) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
email
|
||||||
|
isAdmin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query User($id: ID!) {
|
||||||
|
user(id: $id) {
|
||||||
|
id
|
||||||
|
isAdmin
|
||||||
|
email
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation EditUser($input: UserUpdateInput!) {
|
||||||
|
userUpdate(user: $input)
|
||||||
|
}
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { ApiService } from './api.service';
|
|
||||||
|
|
||||||
describe('ApiService', () => {
|
|
||||||
let service: ApiService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
TestBed.configureTestingModule({});
|
|
||||||
service = TestBed.inject(ApiService);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be created', () => {
|
|
||||||
expect(service).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,45 +0,0 @@
|
||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { LoginService } from '../auth/login.service';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { Apollo } from 'apollo-angular';
|
|
||||||
import { gql } from '@apollo/client';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class ApiService {
|
|
||||||
|
|
||||||
constructor(private apollo: Apollo, private loginService: LoginService, private router: Router) {
|
|
||||||
}
|
|
||||||
|
|
||||||
request(method: 'post' | 'get' | 'delete' | 'patch', url: string, body: any): Promise<any> {
|
|
||||||
const asd = this.apollo.query({
|
|
||||||
query: gql`
|
|
||||||
{
|
|
||||||
__typename
|
|
||||||
}
|
|
||||||
`
|
|
||||||
// headers: {Authorization: 'Bearer ' + this.loginService.token}
|
|
||||||
}).toPromise().then(res => res);
|
|
||||||
return asd.catch(e => {
|
|
||||||
if (e.status === 401) {
|
|
||||||
this.loginService.deleteToken();
|
|
||||||
return this.router.navigateByUrl('/auth/login');
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
requestPage<T>(url: string, limit: number, page: number): Promise<T[]> {
|
|
||||||
const c = url.indexOf('?') === -1 ? '?' : '&';
|
|
||||||
return this.request('get', url + c + 'filter=' + encodeURI(JSON.stringify({
|
|
||||||
limit,
|
|
||||||
offset: (page - 1) * limit
|
|
||||||
})), {});
|
|
||||||
}
|
|
||||||
|
|
||||||
requestItemCount(url: string): Promise<number> {
|
|
||||||
return this.request('get', url + '/count', {}).then(count => count.count);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +1,25 @@
|
||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { ApiService } from '../../services/api.service';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
|
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
|
||||||
import { Query } from 'apollo-angular';
|
import { Mutation, Query } from 'apollo-angular';
|
||||||
|
import { noop } from 'rxjs';
|
||||||
|
import { HasID, MutationInput, QueryResult } from '../../utility/types';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-edit',
|
selector: 'app-edit',
|
||||||
templateUrl: './edit.component.html',
|
templateUrl: './edit.component.html',
|
||||||
styleUrls: ['./edit.component.css']
|
styleUrls: ['./edit.component.css']
|
||||||
})
|
})
|
||||||
export class EditComponent<T extends { id: number }, U> implements OnInit {
|
export class EditComponent<T extends HasID, QT extends QueryResult<T>, UT extends QueryResult<T>, CT extends QueryResult<T>, MI extends Partial<T>>
|
||||||
|
implements OnInit {
|
||||||
|
|
||||||
item?: T;
|
item?: T;
|
||||||
creating = false;
|
creating = false;
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
|
|
||||||
@Input() gql: Query<U, {}>;
|
@Input() gql: Query<QT, HasID>;
|
||||||
|
@Input() updateMutation: Mutation<UT, MutationInput<MI, T>>;
|
||||||
|
@Input() createMutation: Mutation<CT, MutationInput<MI, T>>;
|
||||||
@Input() fields: { title: string, name: keyof T, readonly?: (item: T) => boolean }[];
|
@Input() fields: { title: string, name: keyof T, readonly?: (item: T) => boolean }[];
|
||||||
@Input() itemType: T;
|
@Input() itemType: T;
|
||||||
/**
|
/**
|
||||||
|
@ -24,15 +28,20 @@ export class EditComponent<T extends { id: number }, U> implements OnInit {
|
||||||
@Input() beforeSubmit: (item: T) => Partial<T>;
|
@Input() beforeSubmit: (item: T) => Partial<T>;
|
||||||
formGroup: FormGroup;
|
formGroup: FormGroup;
|
||||||
|
|
||||||
constructor(private api: ApiService, private router: Router, private fb: FormBuilder, private route: ActivatedRoute) {
|
private key: string;
|
||||||
|
private id: string;
|
||||||
|
|
||||||
|
constructor(private router: Router, private fb: FormBuilder, private route: ActivatedRoute) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this.item = JSON.parse(window.localStorage.getItem(this.router.url));
|
|
||||||
window.localStorage.removeItem(this.router.url);
|
window.localStorage.removeItem(this.router.url);
|
||||||
const url = this.route.snapshot.url;
|
const url = this.route.snapshot.url;
|
||||||
|
this.id = this.route.snapshot.url[this.route.snapshot.url.length - 1] + '';
|
||||||
if (!this.item && url[url.length - 1].path !== 'new') {
|
if (!this.item && url[url.length - 1].path !== 'new') {
|
||||||
//this.item = await this.api.request('get', this.apiPath + '/' + this.route.snapshot.url[this.route.snapshot.url.length - 1], {}); - TODO
|
const data = (await this.gql.fetch({id: this.id}).toPromise()).data;
|
||||||
|
this.key = Object.keys(data).filter(k => k !== '__typename')[0];
|
||||||
|
this.item = data[this.key];
|
||||||
}
|
}
|
||||||
this.formGroup = this.fb.group(this.fields.reduce((pv, cv) => {
|
this.formGroup = this.fb.group(this.fields.reduce((pv, cv) => {
|
||||||
const control = new FormControl();
|
const control = new FormControl();
|
||||||
|
@ -52,17 +61,17 @@ export class EditComponent<T extends { id: number }, U> implements OnInit {
|
||||||
|
|
||||||
async submit(): Promise<void> {
|
async submit(): Promise<void> {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
const value = Object.assign({}, this.formGroup.value, this.beforeSubmit(this.item) ?? {});
|
const input = Object.assign({}, this.formGroup.value, (this.beforeSubmit ?? noop)(this.item) ?? {}, {id: this.id}) as MI;
|
||||||
try {
|
try {
|
||||||
if (this.item && !this.creating) {
|
if (this.item && !this.creating) {
|
||||||
//await this.api.request('patch', this.apiPath + '/' + this.item.id, value); - TODO
|
await this.updateMutation.mutate({input}).toPromise();
|
||||||
} else {
|
} else {
|
||||||
//await this.api.request('post', this.apiPath, value); - TODO
|
await this.createMutation.mutate({input}).toPromise();
|
||||||
}
|
}
|
||||||
await this.router.navigate(['..'], {relativeTo: this.route});
|
await this.router.navigate(['..'], {relativeTo: this.route});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(e.message);
|
alert(e.message);
|
||||||
}
|
} // TODO: Clear/update cache
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { PageEvent } from '@angular/material/paginator';
|
import { PageEvent } from '@angular/material/paginator';
|
||||||
import { PaginationData } from '../../utility/pagination-data';
|
import { PaginationData } from '../../utility/pagination-data';
|
||||||
import { ApiService } from '../../services/api.service';
|
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { Query } from 'apollo-angular';
|
import { Query } from 'apollo-angular';
|
||||||
|
|
||||||
|
@ -23,7 +22,7 @@ export class ListComponent<T extends { id: number }, U extends { [entityName: st
|
||||||
items: T[] = [];
|
items: T[] = [];
|
||||||
loading = false;
|
loading = false;
|
||||||
|
|
||||||
constructor(private api: ApiService, private router: Router) {
|
constructor(private router: Router) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<app-edit [gql]="itemGQL" [itemType]="itemType" [fields]="[
|
<app-edit [gql]="itemGQL" [itemType]="itemType" [fields]="[
|
||||||
{title: 'Név', name: 'name'},
|
{title: 'Név', name: 'name'},
|
||||||
{title: 'Leirás', name: 'description'}
|
{title: 'Leirás', name: 'description'}
|
||||||
]"></app-edit>
|
]" [updateMutation]="updateGQL"></app-edit>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { Subject, SubjectGQL } from '../../services/graphql';
|
import { EditSubjectGQL, Subject, SubjectGQL } from '../../services/graphql';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-subject-edit',
|
selector: 'app-subject-edit',
|
||||||
|
@ -9,7 +9,7 @@ import { Subject, SubjectGQL } from '../../services/graphql';
|
||||||
export class SubjectEditComponent implements OnInit {
|
export class SubjectEditComponent implements OnInit {
|
||||||
itemType: Subject;
|
itemType: Subject;
|
||||||
|
|
||||||
constructor(public itemGQL: SubjectGQL) {
|
constructor(public itemGQL: SubjectGQL, public updateGQL: EditSubjectGQL) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<app-edit [itemType]="itemType" [fields]="[
|
<app-edit [itemType]="itemType" [gql]="itemGQL" [updateMutation]="updateGQL" [fields]="[
|
||||||
{title: 'E-mail', name: 'email'},
|
{title: 'E-mail', name: 'email'},
|
||||||
{title: 'Név', name: 'name'},
|
{title: 'Név', name: 'name'},
|
||||||
{title: 'Admin', name: 'isAdmin', readonly: isEditingSelf}
|
{title: 'Admin', name: 'isAdmin', readonly: isEditingSelf}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { LoginService } from '../../auth/login.service';
|
import { LoginService } from '../../auth/login.service';
|
||||||
import { UserResult } from '../../services/graphql';
|
import { EditUserGQL, UserGQL, UserResult } from '../../services/graphql';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-user-edit',
|
selector: 'app-user-edit',
|
||||||
|
@ -11,7 +11,8 @@ export class UserEditComponent implements OnInit {
|
||||||
itemType: UserResult;
|
itemType: UserResult;
|
||||||
isEditingSelf = user => user.id === this.userService.user.id;
|
isEditingSelf = user => user.id === this.userService.user.id;
|
||||||
|
|
||||||
constructor(private userService: LoginService) { }
|
constructor(private userService: LoginService, public itemGQL: UserGQL, public updateGQL: EditUserGQL) {
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<app-list apiPath="/users" [itemType]="itemType" [columns]="[
|
<app-list [itemType]="itemType" [gql]="listGQL" [columns]="[
|
||||||
{title: 'Név', prop: 'name'},
|
{title: 'Név', prop: 'name'},
|
||||||
{title: 'Admin', prop: 'isAdmin'}
|
{title: 'Admin', prop: 'isAdmin'}
|
||||||
]"></app-list>
|
]"></app-list>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { UserResult } from '../../services/graphql';
|
import { UserListGQL, UserResult } from '../../services/graphql';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-user-list',
|
selector: 'app-user-list',
|
||||||
|
@ -9,7 +9,7 @@ import { UserResult } from '../../services/graphql';
|
||||||
export class UserListComponent implements OnInit {
|
export class UserListComponent implements OnInit {
|
||||||
itemType: UserResult;
|
itemType: UserResult;
|
||||||
|
|
||||||
constructor() {
|
constructor(public listGQL: UserListGQL) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
|
5
frontend/src/app/utility/types.ts
Normal file
5
frontend/src/app/utility/types.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { Scalars } from '../services/graphql';
|
||||||
|
|
||||||
|
export type HasID = { id: Scalars['ID'] };
|
||||||
|
export type QueryResult<T extends HasID> = { [entityName: string]: T };
|
||||||
|
export type MutationInput<T extends Partial<U>, U extends HasID> = { input: T };
|
Loading…
Reference in a new issue