Add queries for subjects and courses, list subjects on frontend
Also added logout mutation
This commit is contained in:
parent
8471825e5d
commit
a0bc7d3585
28 changed files with 218 additions and 66 deletions
|
@ -10,9 +10,11 @@ import {
|
||||||
UserServiceBindings
|
UserServiceBindings
|
||||||
} from '@loopback/authentication-jwt';
|
} from '@loopback/authentication-jwt';
|
||||||
import { SzakdolgozatUserService } from './services';
|
import { SzakdolgozatUserService } from './services';
|
||||||
import { GraphQLBindings, GraphQLServer } from '@loopback/graphql';
|
import { GraphQLBindings, GraphQLServer, GraphQLServerOptions } from '@loopback/graphql';
|
||||||
import { UserResolver } from './graphql-resolvers/user-resolver';
|
import { UserResolver } from './graphql-resolvers/user.resolver';
|
||||||
import { SzakdolgozatAuthChecker } from './szakdolgozat-auth-checker';
|
import { SzakdolgozatAuthChecker } from './szakdolgozat-auth-checker';
|
||||||
|
import { CourseResolver } from './graphql-resolvers/course.resolver';
|
||||||
|
import { SubjectResolver } from './graphql-resolvers/subject.resolver';
|
||||||
|
|
||||||
export { ApplicationConfig };
|
export { ApplicationConfig };
|
||||||
|
|
||||||
|
@ -25,10 +27,15 @@ export class SzakdolgozatBackendApplication extends BootMixin(
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
const server = this.server(GraphQLServer);
|
const server = this.server(GraphQLServer);
|
||||||
this.configure(server.key).to({host: process.env.HOST ?? '0.0.0.0', port: process.env.PORT ?? 3000});
|
this.configure(server.key).to(<GraphQLServerOptions> {
|
||||||
|
host: process.env.HOST ?? '0.0.0.0',
|
||||||
|
port: process.env.PORT ?? 3000
|
||||||
|
});
|
||||||
this.getServer(GraphQLServer).then(s => {
|
this.getServer(GraphQLServer).then(s => {
|
||||||
this.gqlServer = s;
|
this.gqlServer = s;
|
||||||
s.resolver(UserResolver);
|
s.resolver(UserResolver);
|
||||||
|
s.resolver(CourseResolver);
|
||||||
|
s.resolver(SubjectResolver);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
|
|
31
backend/src/graphql-resolvers/course.resolver.ts
Normal file
31
backend/src/graphql-resolvers/course.resolver.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { repository } from '@loopback/repository';
|
||||||
|
import { CourseRepository } from '../repositories';
|
||||||
|
import { arg, ID, Int, mutation, query, resolver } from '@loopback/graphql';
|
||||||
|
import { Course } from '../models';
|
||||||
|
import { CourseUpdateInput } from '../graphql-types/input/course-update.input';
|
||||||
|
import { listResponse, ListResponse } from '../graphql-types/list';
|
||||||
|
import { CourseList } from '../graphql-types/course';
|
||||||
|
|
||||||
|
@resolver(of => Course)
|
||||||
|
export class CourseResolver {
|
||||||
|
constructor(
|
||||||
|
@repository('CourseRepository') private courseRepo: CourseRepository
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@query(returns => CourseList)
|
||||||
|
async courses(@arg('offset', returns => Int) offset: number, @arg('limit', returns => Int) limit: number): Promise<ListResponse<Course>> {
|
||||||
|
return listResponse(this.courseRepo, offset, limit, CourseList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@query(returns => Course)
|
||||||
|
async course(@arg('id', returns => ID) id: number): Promise<Course> {
|
||||||
|
return await this.courseRepo.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@mutation(returns => Boolean)
|
||||||
|
async courseUpdate(@arg('course') input: CourseUpdateInput): Promise<boolean> {
|
||||||
|
await this.courseRepo.updateById(input.id, input);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
29
backend/src/graphql-resolvers/subject.resolver.ts
Normal file
29
backend/src/graphql-resolvers/subject.resolver.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { arg, ID, Int, mutation, query, resolver } from '@loopback/graphql';
|
||||||
|
import { Subject } from '../models';
|
||||||
|
import { SubjectRepository } from '../repositories';
|
||||||
|
import { repository } from '@loopback/repository';
|
||||||
|
import { listResponse, ListResponse } from '../graphql-types/list';
|
||||||
|
import { SubjectList } from '../graphql-types/subject';
|
||||||
|
import { SubjectUpdateInput } from '../graphql-types/input/subject-update.input';
|
||||||
|
|
||||||
|
@resolver(of => Subject)
|
||||||
|
export class SubjectResolver {
|
||||||
|
constructor(@repository('SubjectRepository') private subjectRepo: SubjectRepository) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@query(returns => SubjectList)
|
||||||
|
async subjects(@arg('offset', returns => Int) offset: number, @arg('limit', returns => Int) limit: number): Promise<ListResponse<Subject>> {
|
||||||
|
return listResponse(this.subjectRepo, offset, limit, SubjectList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@query(returns => Subject)
|
||||||
|
async subject(@arg('id', returns => ID) id: number): Promise<Subject> {
|
||||||
|
return await this.subjectRepo.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@mutation(returns => Boolean)
|
||||||
|
async subjectUpdate(@arg('subject') input: SubjectUpdateInput): Promise<boolean> {
|
||||||
|
await this.subjectRepo.updateById(input.id, input);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { arg, authorized, GraphQLBindings, Int, mutation, query, resolver, ResolverData } from '@loopback/graphql';
|
import { arg, authorized, GraphQLBindings, ID, 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';
|
||||||
|
@ -63,19 +63,19 @@ export class UserResolver {
|
||||||
|
|
||||||
@authorized()
|
@authorized()
|
||||||
@query(returns => [User])
|
@query(returns => [User])
|
||||||
async find(user: Partial<User>): Promise<User[]> {
|
async findUser(user: Partial<User>): Promise<User[]> {
|
||||||
return this.userRepository.find({}); //TODO
|
return this.userRepository.find({}); //TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
@authorized()
|
@authorized()
|
||||||
@query(returns => User)
|
@query(returns => User, {name: 'user'})
|
||||||
async findById(@arg('id', returns => Int) id: number): Promise<User> {
|
async findById(@arg('id') id: number): Promise<User> {
|
||||||
return this.userRepository.findById(id);
|
return this.userRepository.findById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@authorized()
|
@authorized()
|
||||||
@mutation(returns => Boolean)
|
@mutation(returns => Boolean)
|
||||||
async updateById(@arg('id', returns => Int) id: number, @arg('user') user: UserUpdateInput): Promise<boolean> {
|
async userUpdate(@arg('id', returns => ID) id: number, @arg('user') user: UserUpdateInput): Promise<boolean> {
|
||||||
if (id === +this.user?.id) { //TODO: this.user
|
if (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) {
|
||||||
|
@ -88,7 +88,7 @@ export class UserResolver {
|
||||||
|
|
||||||
@authorized()
|
@authorized()
|
||||||
@mutation(returns => Boolean)
|
@mutation(returns => Boolean)
|
||||||
async deleteById(id: number): Promise<Boolean> {
|
async userDelete(id: number): Promise<Boolean> {
|
||||||
await this.userRepository.deleteById(id);
|
await this.userRepository.deleteById(id);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
11
backend/src/graphql-types/course.ts
Normal file
11
backend/src/graphql-types/course.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { ListResponse } from './list';
|
||||||
|
import { Course } from '../models';
|
||||||
|
import { field, Int, objectType } from '@loopback/graphql';
|
||||||
|
|
||||||
|
@objectType()
|
||||||
|
export class CourseList implements ListResponse<Course> {
|
||||||
|
@field(returns => Int)
|
||||||
|
count: number;
|
||||||
|
@field(returns => [Course])
|
||||||
|
list: Course[];
|
||||||
|
}
|
15
backend/src/graphql-types/input/course-update.input.ts
Normal file
15
backend/src/graphql-types/input/course-update.input.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { field, ID, inputType } from '@loopback/graphql';
|
||||||
|
import { Course } from '../../models';
|
||||||
|
import { DataObject } from '@loopback/repository';
|
||||||
|
|
||||||
|
@inputType()
|
||||||
|
export class CourseUpdateInput implements Pick<DataObject<Course>, 'id' | 'semester' | 'alias' | 'subjectId'> {
|
||||||
|
@field(returns => ID)
|
||||||
|
id: number;
|
||||||
|
@field()
|
||||||
|
semester?: string;
|
||||||
|
@field()
|
||||||
|
alias?: string;
|
||||||
|
@field()
|
||||||
|
subjectId?: number;
|
||||||
|
}
|
13
backend/src/graphql-types/input/subject-update.input.ts
Normal file
13
backend/src/graphql-types/input/subject-update.input.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { field, ID, inputType } from '@loopback/graphql';
|
||||||
|
import { DataObject } from '@loopback/repository';
|
||||||
|
import { Subject } from '../../models';
|
||||||
|
|
||||||
|
@inputType()
|
||||||
|
export class SubjectUpdateInput implements Pick<DataObject<Subject>, 'id' | 'name' | 'description'> {
|
||||||
|
@field(returns => ID)
|
||||||
|
id: number;
|
||||||
|
@field()
|
||||||
|
name?: string;
|
||||||
|
@field()
|
||||||
|
description?: string;
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { field, inputType, objectType } from '@loopback/graphql';
|
import { ClassType, field, inputType } from '@loopback/graphql';
|
||||||
|
import { DefaultCrudRepository, Entity } from '@loopback/repository';
|
||||||
|
|
||||||
@inputType()
|
@inputType()
|
||||||
export class ListInput {
|
export class ListInput {
|
||||||
|
@ -8,10 +9,14 @@ export class ListInput {
|
||||||
limit: number;
|
limit: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@objectType()
|
export interface ListResponse<T> {
|
||||||
export class ListResponse<T> {
|
|
||||||
@field()
|
|
||||||
count: number;
|
count: number;
|
||||||
@field()
|
|
||||||
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>) {
|
||||||
|
const list = new listType();
|
||||||
|
list.list = await repo.find({offset, limit});
|
||||||
|
list.count = (await repo.count()).count;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
11
backend/src/graphql-types/subject.ts
Normal file
11
backend/src/graphql-types/subject.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { ListResponse } from './list';
|
||||||
|
import { Subject } from '../models';
|
||||||
|
import { field, Int, objectType } from '@loopback/graphql';
|
||||||
|
|
||||||
|
@objectType()
|
||||||
|
export class SubjectList implements ListResponse<Subject> {
|
||||||
|
@field(returns => Int)
|
||||||
|
count: number;
|
||||||
|
@field(returns => [Subject])
|
||||||
|
list: Subject[];
|
||||||
|
}
|
|
@ -3,29 +3,35 @@ import { Subject } from './subject.model';
|
||||||
import { User } from './user.model';
|
import { User } from './user.model';
|
||||||
import { CourseUser } from './course-user.model';
|
import { CourseUser } from './course-user.model';
|
||||||
import { Requirement } from './requirement.model';
|
import { Requirement } from './requirement.model';
|
||||||
|
import { field, objectType } from '@loopback/graphql';
|
||||||
|
|
||||||
@model()
|
@model()
|
||||||
|
@objectType()
|
||||||
export class Course extends Entity {
|
export class Course extends Entity {
|
||||||
@property({
|
@property({
|
||||||
type: 'number',
|
type: 'number',
|
||||||
id: true,
|
id: true,
|
||||||
generated: true,
|
generated: true,
|
||||||
})
|
})
|
||||||
|
@field()
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
})
|
})
|
||||||
|
@field()
|
||||||
semester: string;
|
semester: string;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
})
|
})
|
||||||
|
@field()
|
||||||
alias: string;
|
alias: string;
|
||||||
|
|
||||||
@belongsTo(() => Subject)
|
@belongsTo(() => Subject)
|
||||||
|
@field()
|
||||||
subjectId: number;
|
subjectId: number;
|
||||||
|
|
||||||
@hasMany(() => User, {through: {model: () => CourseUser}})
|
@hasMany(() => User, {through: {model: () => CourseUser}})
|
||||||
|
|
|
@ -1,24 +1,29 @@
|
||||||
import { Entity, model, property, hasMany } from '@loopback/repository';
|
import { Entity, hasMany, model, property } from '@loopback/repository';
|
||||||
import { Course } from './course.model';
|
import { Course } from './course.model';
|
||||||
|
import { field, objectType } from '@loopback/graphql';
|
||||||
|
|
||||||
@model()
|
@model()
|
||||||
|
@objectType()
|
||||||
export class Subject extends Entity {
|
export class Subject extends Entity {
|
||||||
@property({
|
@property({
|
||||||
type: 'number',
|
type: 'number',
|
||||||
id: true,
|
id: true,
|
||||||
generated: true,
|
generated: true,
|
||||||
})
|
})
|
||||||
|
@field()
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
})
|
})
|
||||||
|
@field()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@property({
|
@property({
|
||||||
type: 'string',
|
type: 'string',
|
||||||
})
|
})
|
||||||
|
@field()
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
||||||
@hasMany(() => Course)
|
@hasMany(() => Course)
|
||||||
|
|
|
@ -33,8 +33,7 @@ export class AppComponent implements OnInit {
|
||||||
private activeRouteTitle: string;
|
private activeRouteTitle: string;
|
||||||
|
|
||||||
constructor(private breakpointObserver: BreakpointObserver, public loginService: LoginService, private api: ApiService,
|
constructor(private breakpointObserver: BreakpointObserver, public loginService: LoginService, private api: ApiService,
|
||||||
private router: Router, private login: LoginService, private activeRoute: ActivatedRoute,
|
private router: Router, private activeRoute: ActivatedRoute, private title: Title) {
|
||||||
private title: Title) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
@ -115,12 +114,12 @@ export class AppComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
async logout(): Promise<void> {
|
async logout(): Promise<void> {
|
||||||
await this.api.logout();
|
await this.loginService.logout();
|
||||||
await this.router.navigate(['/']);
|
await this.router.navigate(['/']);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMenuItems(): MenuItem[] {
|
getMenuItems(): MenuItem[] {
|
||||||
return this.menu.filter(item => item.requiredRole === 'admin' ? this.login.user?.isAdmin : true); // TODO: Roles
|
return this.menu.filter(item => item.requiredRole === 'admin' ? this.loginService.user?.isAdmin : true); // TODO: Roles
|
||||||
}
|
}
|
||||||
|
|
||||||
routeActivated($event: any): void {
|
routeActivated($event: any): void {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
import { LoginGQL, UserResult } from '../services/graphql';
|
import { LoginGQL, LogoutGQL, UserResult } from '../services/graphql';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
@ -19,7 +19,7 @@ export class LoginService {
|
||||||
return this.userP;
|
return this.userP;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private http: HttpClient, private loginService: LoginGQL) {
|
constructor(private http: HttpClient, private loginGQL: LoginGQL, private logoutGQL: LogoutGQL) {
|
||||||
this.tokenP = window.localStorage.getItem('token');
|
this.tokenP = window.localStorage.getItem('token');
|
||||||
this.userP = JSON.parse(window.localStorage.getItem('user'));
|
this.userP = JSON.parse(window.localStorage.getItem('user'));
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ export class LoginService {
|
||||||
|
|
||||||
async login(email: string, password: string): Promise<boolean> {
|
async login(email: string, password: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const resp = await this.loginService.mutate({email, password}).toPromise();
|
const resp = await this.loginGQL.mutate({email, password}).toPromise();
|
||||||
this.tokenP = resp.data.login.token;
|
this.tokenP = resp.data.login.token;
|
||||||
this.userP = resp.data.login.user;
|
this.userP = resp.data.login.user;
|
||||||
window.localStorage.setItem('token', this.tokenP);
|
window.localStorage.setItem('token', this.tokenP);
|
||||||
|
@ -50,4 +50,9 @@ export class LoginService {
|
||||||
window.localStorage.removeItem('token');
|
window.localStorage.removeItem('token');
|
||||||
window.localStorage.removeItem('user');
|
window.localStorage.removeItem('user');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async logout(): Promise<void> {
|
||||||
|
await this.logoutGQL.mutate().toPromise();
|
||||||
|
this.deleteToken();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
18
frontend/src/app/graphql/subject.graphql
Normal file
18
frontend/src/app/graphql/subject.graphql
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
query SubjectList($limit: Int!, $offset: Int!) {
|
||||||
|
subjects(limit: $limit, offset: $offset) {
|
||||||
|
list {
|
||||||
|
name
|
||||||
|
id
|
||||||
|
description
|
||||||
|
}
|
||||||
|
count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query Subject($id: ID!) {
|
||||||
|
subject(id: $id) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
description
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,3 +9,7 @@ mutation Login($email: String!, $password: String!) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutation Logout {
|
||||||
|
logout
|
||||||
|
}
|
||||||
|
|
|
@ -42,9 +42,4 @@ export class ApiService {
|
||||||
requestItemCount(url: string): Promise<number> {
|
requestItemCount(url: string): Promise<number> {
|
||||||
return this.request('get', url + '/count', {}).then(count => count.count);
|
return this.request('get', url + '/count', {}).then(count => count.count);
|
||||||
}
|
}
|
||||||
|
|
||||||
async logout(): Promise<void> {
|
|
||||||
await this.request('post', '/users/logout', '');
|
|
||||||
this.loginService.deleteToken();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
import { Component, Input, OnInit, Type } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { Model } from '../../model/model';
|
|
||||||
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
|
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
|
||||||
|
import { Query } from 'apollo-angular';
|
||||||
|
|
||||||
@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 Model> implements OnInit {
|
export class EditComponent<T extends { id: number }, U> implements OnInit {
|
||||||
|
|
||||||
item?: T;
|
item?: T;
|
||||||
creating = false;
|
creating = false;
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
|
|
||||||
@Input() apiPath: string;
|
@Input() gql: Query<U, {}>;
|
||||||
@Input() fields: { title: string, name: keyof T, readonly?: (item: T) => boolean }[];
|
@Input() fields: { title: string, name: keyof T, readonly?: (item: T) => boolean }[];
|
||||||
@Input() itemType: Type<T>;
|
@Input() itemType: T;
|
||||||
/**
|
/**
|
||||||
* Beküldés előtt extra adat hozzáadása
|
* Beküldés előtt extra adat hozzáadása
|
||||||
*/
|
*/
|
||||||
|
@ -32,7 +32,7 @@ export class EditComponent<T extends Model> implements OnInit {
|
||||||
window.localStorage.removeItem(this.router.url);
|
window.localStorage.removeItem(this.router.url);
|
||||||
const url = this.route.snapshot.url;
|
const url = this.route.snapshot.url;
|
||||||
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], {});
|
//this.item = await this.api.request('get', this.apiPath + '/' + this.route.snapshot.url[this.route.snapshot.url.length - 1], {}); - TODO
|
||||||
}
|
}
|
||||||
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();
|
||||||
|
@ -44,7 +44,7 @@ export class EditComponent<T extends Model> implements OnInit {
|
||||||
if (this.item) {
|
if (this.item) {
|
||||||
this.formGroup.patchValue(this.item);
|
this.formGroup.patchValue(this.item);
|
||||||
} else {
|
} else {
|
||||||
this.item = new this.itemType();
|
this.item = {} as T;
|
||||||
this.creating = true;
|
this.creating = true;
|
||||||
}
|
}
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
|
@ -55,9 +55,9 @@ export class EditComponent<T extends Model> implements OnInit {
|
||||||
const value = Object.assign({}, this.formGroup.value, this.beforeSubmit(this.item) ?? {});
|
const value = Object.assign({}, this.formGroup.value, this.beforeSubmit(this.item) ?? {});
|
||||||
try {
|
try {
|
||||||
if (this.item && !this.creating) {
|
if (this.item && !this.creating) {
|
||||||
await this.api.request('patch', this.apiPath + '/' + this.item.id, value);
|
//await this.api.request('patch', this.apiPath + '/' + this.item.id, value); - TODO
|
||||||
} else {
|
} else {
|
||||||
await this.api.request('post', this.apiPath, value);
|
//await this.api.request('post', this.apiPath, value); - TODO
|
||||||
}
|
}
|
||||||
await this.router.navigate(['..'], {relativeTo: this.route});
|
await this.router.navigate(['..'], {relativeTo: this.route});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -3,16 +3,17 @@ 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 { ApiService } from '../../services/api.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
import { Query } from 'apollo-angular';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-list',
|
selector: 'app-list',
|
||||||
templateUrl: './list.component.html',
|
templateUrl: './list.component.html',
|
||||||
styleUrls: ['./list.component.css']
|
styleUrls: ['./list.component.css']
|
||||||
})
|
})
|
||||||
export class ListComponent<T extends { id: number }> implements OnInit {
|
export class ListComponent<T extends { id: number }, U extends { [entityName: string]: { count: number; list: T[] } }> implements OnInit {
|
||||||
|
|
||||||
@Input() apiPath: string;
|
@Input() gql: Query<U, { limit: number; offset: number }>;
|
||||||
@Input() itemType: T;
|
@Input() itemType: T; // TODO: Remove
|
||||||
@Input() columns: { title: string, prop: keyof T }[];
|
@Input() columns: { title: string, prop: keyof T }[];
|
||||||
@Input() allowNew = false;
|
@Input() allowNew = false;
|
||||||
@Input() customActions: { icon: string, label: string, action: (model: T) => void }[] = [];
|
@Input() customActions: { icon: string, label: string, action: (model: T) => void }[] = [];
|
||||||
|
@ -36,19 +37,19 @@ export class ListComponent<T extends { id: number }> implements OnInit {
|
||||||
async getItems(limit: number, page: number): Promise<void> {
|
async getItems(limit: number, page: number): Promise<void> {
|
||||||
try {
|
try {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
if (!this.paginationData.total) {
|
const {data} = await this.gql.fetch({limit, offset: (page - 1) * limit}).toPromise(); // TODO: Watch
|
||||||
this.paginationData.total = await this.api.requestItemCount(this.apiPath);
|
const key = Object.keys(data).filter(k => k !== '__typename')[0];
|
||||||
}
|
this.paginationData.total = data[key].count;
|
||||||
this.paginationData.page = page;
|
this.paginationData.page = page;
|
||||||
this.paginationData.limit = limit;
|
this.paginationData.limit = limit;
|
||||||
this.items = await this.api.requestPage<T>(this.apiPath, limit, page);
|
this.items = data[key].list;
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async editItem(item: T): Promise<void> {
|
async editItem(item: T): Promise<void> {
|
||||||
window.localStorage.setItem(this.router.url + '/' + item.id, JSON.stringify(item));
|
window.localStorage.setItem(this.router.url + '/' + item.id, JSON.stringify(item)); // TODO: Apollo cache
|
||||||
await this.router.navigate([this.router.url, item.id]);
|
await this.router.navigate([this.router.url, item.id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<app-edit #edit [itemType]="itemType" apiPath="/courses" [beforeSubmit]="beforeSubmit" [fields]="[
|
<!-- <app-edit [beforeSubmit]="beforeSubmit" [fields]="[
|
||||||
{title: 'Félév', name: 'semester'},
|
{title: 'Félév', name: 'semester'},
|
||||||
{title: 'Név', name: 'alias'}
|
{title: 'Név', name: 'alias'}
|
||||||
]"></app-edit>
|
]"></app-edit> -->
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { ActivatedRoute } from '@angular/router';
|
||||||
styleUrls: ['./course-edit.component.css']
|
styleUrls: ['./course-edit.component.css']
|
||||||
})
|
})
|
||||||
export class CourseEditComponent implements OnInit {
|
export class CourseEditComponent implements OnInit {
|
||||||
itemType: Course;
|
|
||||||
beforeSubmit = () => ({subjectId: +this.route.snapshot.params.subjectId});
|
beforeSubmit = () => ({subjectId: +this.route.snapshot.params.subjectId});
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute) {
|
constructor(private route: ActivatedRoute) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<app-list #list [apiPath]="'/subjects/'+subjectId+'/courses'" [itemType]="itemType" allowNew="true" [columns]="[
|
<app-list [itemType]="itemType" allowNew="true" [columns]="[
|
||||||
{prop: 'semester', title: 'Félév'},
|
{prop: 'semester', title: 'Félév'},
|
||||||
{prop: 'alias', title: 'Név'}
|
{prop: 'alias', title: 'Név'}
|
||||||
]"></app-list>
|
]"></app-list>
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Course } from '../../../../model/course.model';
|
|
||||||
import { ListComponent } from '../../../../shared-components/list/list.component';
|
|
||||||
import { CustomTitleComponent } from '../../../../app.component';
|
import { CustomTitleComponent } from '../../../../app.component';
|
||||||
|
import { Course, SubjectGQL } from '../../../../services/graphql';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-courses',
|
selector: 'app-courses',
|
||||||
|
@ -11,10 +10,9 @@ import { CustomTitleComponent } from '../../../../app.component';
|
||||||
})
|
})
|
||||||
export class CourseListComponent implements OnInit, CustomTitleComponent {
|
export class CourseListComponent implements OnInit, CustomTitleComponent {
|
||||||
subjectId: string;
|
subjectId: string;
|
||||||
itemType = Course;
|
itemType: Course;
|
||||||
@ViewChild('list') list: ListComponent<Course>;
|
|
||||||
|
|
||||||
constructor(route: ActivatedRoute) {
|
constructor(route: ActivatedRoute, public listGQL: SubjectGQL) {
|
||||||
this.subjectId = route.snapshot.params.subjectId;
|
this.subjectId = route.snapshot.params.subjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +20,6 @@ export class CourseListComponent implements OnInit, CustomTitleComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
getPageTitle(): string {
|
getPageTitle(): string {
|
||||||
return 'Custom title'; //TODO
|
return 'Custom title'; //TODO: SubjectGQL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<app-edit [apiPath]="'/subjects'" [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>
|
]"></app-edit>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { Subject } from '../../model/subject.model';
|
import { Subject, SubjectGQL } from '../../services/graphql';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-subject-edit',
|
selector: 'app-subject-edit',
|
||||||
|
@ -7,9 +7,10 @@ import { Subject } from '../../model/subject.model';
|
||||||
styleUrls: ['./subject-edit.component.css']
|
styleUrls: ['./subject-edit.component.css']
|
||||||
})
|
})
|
||||||
export class SubjectEditComponent implements OnInit {
|
export class SubjectEditComponent implements OnInit {
|
||||||
itemType = Subject;
|
itemType: Subject;
|
||||||
|
|
||||||
constructor() { }
|
constructor(public itemGQL: SubjectGQL) {
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<app-list apiPath="/subjects" [itemType]="itemType" allowNew="true" [columns]="[
|
<app-list [gql]="listGQL" allowNew="true" [columns]="[
|
||||||
{title: 'Név', prop: 'name'},
|
{title: 'Név', prop: 'name'},
|
||||||
{title: 'Leirás', prop: 'description'}
|
{title: 'Leirás', prop: 'description'}
|
||||||
]" [customActions]="[
|
]" [customActions]="[
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { Subject } from '../../model/subject.model';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { Subject, SubjectListGQL } from '../../services/graphql';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-subject-list',
|
selector: 'app-subject-list',
|
||||||
|
@ -8,9 +8,8 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||||
styleUrls: ['./subject-list.component.css']
|
styleUrls: ['./subject-list.component.css']
|
||||||
})
|
})
|
||||||
export class SubjectListComponent implements OnInit {
|
export class SubjectListComponent implements OnInit {
|
||||||
itemType = Subject;
|
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private router: Router) {
|
constructor(private route: ActivatedRoute, private router: Router, public listGQL: SubjectListGQL) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<app-edit [apiPath]="'/users'" [itemType]="itemType" [fields]="[
|
<app-edit [itemType]="itemType" [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 { User } from '../../model/user.model';
|
|
||||||
import { LoginService } from '../../auth/login.service';
|
import { LoginService } from '../../auth/login.service';
|
||||||
|
import { UserResult } from '../../services/graphql';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-user-edit',
|
selector: 'app-user-edit',
|
||||||
|
@ -8,7 +8,7 @@ import { LoginService } from '../../auth/login.service';
|
||||||
styleUrls: ['./user-edit.component.css']
|
styleUrls: ['./user-edit.component.css']
|
||||||
})
|
})
|
||||||
export class UserEditComponent implements OnInit {
|
export class UserEditComponent implements OnInit {
|
||||||
itemType = User;
|
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) { }
|
||||||
|
|
Loading…
Reference in a new issue