Fix and improve custom titles, add course support, fix things
Implemented hasManyThrough repository with count() method
This commit is contained in:
parent
38b06f54e6
commit
d076e0a013
20 changed files with 293 additions and 100 deletions
|
@ -1,5 +1,5 @@
|
|||
import { repository } from '@loopback/repository';
|
||||
import { CourseRepository } from '../repositories';
|
||||
import { CourseRepository, SubjectRepository, UserRepository } 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';
|
||||
|
@ -9,13 +9,20 @@ import { CourseList } from '../graphql-types/course';
|
|||
@resolver(of => Course)
|
||||
export class CourseResolver {
|
||||
constructor(
|
||||
@repository('CourseRepository') private courseRepo: CourseRepository
|
||||
@repository('CourseRepository') private courseRepo: CourseRepository,
|
||||
@repository('SubjectRepository') private subjectRepo: SubjectRepository,
|
||||
@repository('UserRepository') private userRepo: UserRepository
|
||||
) {
|
||||
}
|
||||
|
||||
@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);
|
||||
async coursesBySubject(@arg('subject', returns => ID) subject: number, @arg('offset', returns => Int) offset: number, @arg('limit', returns => Int) limit: number): Promise<ListResponse<Course>> {
|
||||
return listResponse(this.subjectRepo.courses(subject), offset, limit, CourseList);
|
||||
}
|
||||
|
||||
@query(returns => CourseList)
|
||||
async coursesByUser(@arg('user', returns => ID) user: number, @arg('offset', returns => Int) offset: number, @arg('limit', returns => Int) limit: number): Promise<ListResponse<Course>> {
|
||||
return listResponse(this.userRepo.courses(user), offset, limit, CourseList);
|
||||
}
|
||||
|
||||
@query(returns => Course)
|
||||
|
|
|
@ -13,7 +13,7 @@ export class SubjectResolver {
|
|||
|
||||
@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);
|
||||
return await listResponse(this.subjectRepo, offset, limit, SubjectList);
|
||||
}
|
||||
|
||||
@query(returns => Subject)
|
||||
|
|
|
@ -75,7 +75,7 @@ export class UserResolver {
|
|||
}
|
||||
|
||||
@authorized()
|
||||
@query(returns => [User])
|
||||
@query(returns => UserList)
|
||||
async users(@arg('limit', returns => Int) limit: number, @arg('offset', returns => Int) offset: number) {
|
||||
return await listResponse(this.userRepository, offset, limit, UserList);
|
||||
}
|
||||
|
|
|
@ -14,7 +14,9 @@ export interface ListResponse<T> {
|
|||
list: T[];
|
||||
}
|
||||
|
||||
export async function listResponse<T extends Entity, U extends ListResponse<Partial<T>>>(repo: DefaultCrudRepository<T, number>, offset: number, limit: number, listType: ClassType<U>) {
|
||||
export type ListRepository<T extends Entity> = Pick<DefaultCrudRepository<T, number>, 'find' | 'count'>;
|
||||
|
||||
export async function listResponse<T extends Entity, U extends ListResponse<Partial<T>>>(repo: ListRepository<T>, offset: number, limit: number, listType: ClassType<U>) {
|
||||
const list = new listType();
|
||||
list.list = await repo.find({offset, limit});
|
||||
list.count = (await repo.count()).count;
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
import { constrainWhere, Count, DefaultHasManyRepository, Entity, EntityCrudRepository, Where } from '@loopback/repository';
|
||||
import { Options } from '@loopback/repository/src/common-types';
|
||||
import { HasManyRepository } from '@loopback/repository/src/relations/has-many/has-many.repository';
|
||||
import { InclusionResolver } from '@loopback/repository/src/relations/relation.types';
|
||||
|
||||
export interface CustomHasManyRepositoryFactory<Target extends Entity,
|
||||
ForeignKeyType,
|
||||
> {
|
||||
/**
|
||||
* Invoke the function to obtain HasManyRepository.
|
||||
*/
|
||||
(fkValue: ForeignKeyType): CustomHasManyRepository<Target>;
|
||||
|
||||
/**
|
||||
* Use `resolver` property to obtain an InclusionResolver for this relation.
|
||||
*/
|
||||
inclusionResolver: InclusionResolver<Entity, Target>;
|
||||
}
|
||||
|
||||
export interface CustomHasManyRepository<Target extends Entity> extends HasManyRepository<Target> {
|
||||
count(where?: Where<Target>, options?: Options): Promise<Count>;
|
||||
}
|
||||
|
||||
export class DefaultCustomHasManyRepository<TEntity extends Entity, TID, TRepository extends EntityCrudRepository<TEntity, TID>> extends DefaultHasManyRepository<TEntity, TID, TRepository> implements CustomHasManyRepository<TEntity> {
|
||||
async count(where?: Where<TEntity>, options?: Options) {
|
||||
const targetRepository = await this.getTargetRepository();
|
||||
return targetRepository.count(constrainWhere(where, this.constraint), options);
|
||||
}
|
||||
}
|
149
backend/src/repositories/custom-has-many-repository.ts
Normal file
149
backend/src/repositories/custom-has-many-repository.ts
Normal file
|
@ -0,0 +1,149 @@
|
|||
import {
|
||||
constrainFilter,
|
||||
constrainWhere,
|
||||
Count,
|
||||
DataObject,
|
||||
DefaultHasManyRepository,
|
||||
DefaultHasManyThroughRepository,
|
||||
DefaultTransactionalRepository,
|
||||
Entity,
|
||||
EntityCrudRepository,
|
||||
HasManyRepository,
|
||||
HasManyThroughRepository,
|
||||
InclusionResolver,
|
||||
Options,
|
||||
Where
|
||||
} from '@loopback/repository';
|
||||
import { Getter } from '@loopback/core';
|
||||
|
||||
export interface CustomHasManyRepositoryFactory<Target extends Entity,
|
||||
ForeignKeyType,
|
||||
> {
|
||||
/**
|
||||
* Invoke the function to obtain HasManyRepository.
|
||||
*/
|
||||
(fkValue: ForeignKeyType): CustomHasManyRepository<Target>;
|
||||
|
||||
/**
|
||||
* Use `resolver` property to obtain an InclusionResolver for this relation.
|
||||
*/
|
||||
inclusionResolver: InclusionResolver<Entity, Target>;
|
||||
}
|
||||
|
||||
export interface CustomHasManyRepository<Target extends Entity> extends HasManyRepository<Target> {
|
||||
count(where?: Where<Target>, options?: Options): Promise<Count>;
|
||||
}
|
||||
|
||||
export class DefaultCustomHasManyRepository<TEntity extends Entity, TID, TRepository extends EntityCrudRepository<TEntity, TID>> extends DefaultHasManyRepository<TEntity, TID, TRepository> implements CustomHasManyRepository<TEntity> {
|
||||
async count(where?: Where<TEntity>, options?: Options) {
|
||||
const targetRepository = await this.getTargetRepository();
|
||||
return targetRepository.count(constrainWhere(where, this.constraint), options);
|
||||
}
|
||||
}
|
||||
|
||||
export interface CustomHasManyThroughRepositoryFactory<TargetEntity extends Entity, TargetID, ThroughEntity extends Entity, SourceID> {
|
||||
(fkValue: SourceID): CustomHasManyThroughRepository<TargetEntity, TargetID, ThroughEntity>;
|
||||
|
||||
/**
|
||||
* Use `resolver` property to obtain an InclusionResolver for this relation.
|
||||
*/
|
||||
inclusionResolver: InclusionResolver<Entity, TargetEntity>;
|
||||
}
|
||||
|
||||
export interface CustomHasManyThroughRepository<Target extends Entity, TID, Through extends Entity> extends HasManyThroughRepository<Target, TID, Through> {
|
||||
count(where?: Where<Target>, options?: Options): Promise<Count>;
|
||||
}
|
||||
|
||||
export class DefaultCustomHasManyThroughRepository<TargetEntity extends Entity,
|
||||
TargetID,
|
||||
TargetRepository extends EntityCrudRepository<TargetEntity, TargetID>,
|
||||
ThroughEntity extends Entity,
|
||||
ThroughID,
|
||||
ThroughRepository extends EntityCrudRepository<ThroughEntity, ThroughID>,
|
||||
> extends DefaultHasManyThroughRepository<TargetEntity, TargetID, TargetRepository, ThroughEntity, ThroughID, ThroughRepository>
|
||||
implements CustomHasManyThroughRepository<TargetEntity, TargetID, ThroughEntity> {
|
||||
async count(where?: Where<TargetEntity>, options?: Options): Promise<Count> {
|
||||
const targetRepository = await this.getTargetRepository();
|
||||
const throughRepository = await this.getThroughRepository();
|
||||
const sourceConstraint = this.getThroughConstraintFromSource();
|
||||
const throughInstances = await throughRepository.find(
|
||||
constrainFilter(undefined, sourceConstraint),
|
||||
options?.throughOptions,
|
||||
);
|
||||
const targetConstraint =
|
||||
this.getTargetConstraintFromThroughModels(throughInstances);
|
||||
return targetRepository.count(
|
||||
constrainWhere(where, targetConstraint),
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CustomCrudRepository<T extends Entity, ID, Relations extends object = {}> extends DefaultTransactionalRepository<T, ID, Relations> {
|
||||
createCustomHasManyRepositoryFactoryFor<Target extends Entity, TargetID, ForeignKeyType extends Target[ForeignKeyName], ForeignKeyName extends keyof Target>(
|
||||
relationName: string,
|
||||
targetRepositoryGetter: Getter<EntityCrudRepository<Target, TargetID>>,
|
||||
fkName: ForeignKeyName
|
||||
): CustomHasManyRepositoryFactory<Target, ForeignKeyType | undefined> {
|
||||
const origEntities = this.createHasManyRepositoryFactoryFor(relationName, targetRepositoryGetter);
|
||||
this.registerInclusionResolver(relationName, origEntities.inclusionResolver);
|
||||
const entities = function(fkValue: ForeignKeyType | undefined) {
|
||||
return new DefaultCustomHasManyRepository(targetRepositoryGetter, {[fkName]: fkValue} as unknown as DataObject<Target>);
|
||||
};
|
||||
entities.inclusionResolver = origEntities.inclusionResolver;
|
||||
return entities;
|
||||
}
|
||||
|
||||
createCustomHasManyThroughFactoryFor<Target extends Entity, Through extends Entity, ThroughID, SourceKeyName extends keyof Through, TargetKeyName extends keyof Through>(
|
||||
relationName: string,
|
||||
targetRepoGetter: Getter<EntityCrudRepository<Target, Through[TargetKeyName]>>,
|
||||
throughRepoGetter: Getter<EntityCrudRepository<Through, ThroughID>>,
|
||||
sourceKeyName: SourceKeyName,
|
||||
targetKeyName: TargetKeyName
|
||||
): CustomHasManyThroughRepositoryFactory<Target, Through[TargetKeyName] | undefined, Through, Through[SourceKeyName] | undefined> {
|
||||
const origEntities = this.createHasManyThroughRepositoryFactoryFor(relationName, targetRepoGetter, throughRepoGetter);
|
||||
this.registerInclusionResolver(relationName, origEntities.inclusionResolver);
|
||||
const entities = function(fkValue: Through[SourceKeyName] | undefined) {
|
||||
function getTargetConstraintFromThroughModels(
|
||||
throughInstances: Through[],
|
||||
): DataObject<Target> {
|
||||
const keys = throughInstances.map(instance => instance.getId());
|
||||
return {id: keys.length > 1 ? {inq: keys} : keys[0]} as unknown as DataObject<Target>;
|
||||
}
|
||||
|
||||
function getTargetKeys(throughInstances: Through[]): Through[TargetKeyName][] {
|
||||
return throughInstances.map(instance => instance[targetKeyName]);
|
||||
}
|
||||
|
||||
function getThroughConstraintFromSource(): DataObject<Through> {
|
||||
return {[sourceKeyName]: fkValue} as unknown as DataObject<Through>;
|
||||
}
|
||||
|
||||
function getTargetIds(targetInstances: Target[]): Through[TargetKeyName][] {
|
||||
return targetInstances.map(target => target.getId());
|
||||
}
|
||||
|
||||
function getThroughConstraintFromTarget(fkValues: Through[TargetKeyName][]): DataObject<Through> {
|
||||
return {[targetKeyName]: fkValues.length > 1 ? {inq: fkValues} : fkValues[0]} as unknown as DataObject<Through>;
|
||||
}
|
||||
|
||||
return new DefaultCustomHasManyThroughRepository<Target,
|
||||
Through[TargetKeyName],
|
||||
EntityCrudRepository<Target, Through[TargetKeyName]>,
|
||||
Through,
|
||||
ThroughID,
|
||||
EntityCrudRepository<Through, ThroughID>>(
|
||||
targetRepoGetter,
|
||||
throughRepoGetter,
|
||||
getTargetConstraintFromThroughModels,
|
||||
getTargetKeys,
|
||||
getThroughConstraintFromSource,
|
||||
getTargetIds,
|
||||
getThroughConstraintFromTarget,
|
||||
);
|
||||
};
|
||||
entities.inclusionResolver = origEntities.inclusionResolver;
|
||||
return entities;
|
||||
}
|
||||
}
|
|
@ -1,14 +1,12 @@
|
|||
import { Getter, inject } from '@loopback/core';
|
||||
import { DataObject, DefaultCrudRepository, repository } from '@loopback/repository';
|
||||
import { repository } from '@loopback/repository';
|
||||
import { DatabaseDataSource } from '../datasources';
|
||||
import { Course, Subject, SubjectRelations } from '../models';
|
||||
import { UserRepository } from './user.repository';
|
||||
import { CourseRepository } from './course.repository';
|
||||
import { CustomHasManyRepositoryFactory, DefaultCustomHasManyRepository } from './CustomHasManyRepository';
|
||||
import { CustomCrudRepository, CustomHasManyRepositoryFactory } from './custom-has-many-repository';
|
||||
|
||||
export class SubjectRepository extends DefaultCrudRepository<Subject,
|
||||
typeof Subject.prototype.id,
|
||||
SubjectRelations> {
|
||||
export class SubjectRepository extends CustomCrudRepository<Subject, typeof Subject.prototype.id, SubjectRelations> {
|
||||
|
||||
public readonly courses: CustomHasManyRepositoryFactory<Course, typeof Subject.prototype.id>;
|
||||
|
||||
|
@ -16,12 +14,6 @@ export class SubjectRepository extends DefaultCrudRepository<Subject,
|
|||
@inject('datasources.database') dataSource: DatabaseDataSource, @repository.getter('UserRepository') protected userRepositoryGetter: Getter<UserRepository>, @repository.getter('CourseRepository') protected courseRepositoryGetter: Getter<CourseRepository>,
|
||||
) {
|
||||
super(Subject, dataSource);
|
||||
const origCourses = this.createHasManyRepositoryFactoryFor('courses', courseRepositoryGetter,);
|
||||
this.registerInclusionResolver('courses', origCourses.inclusionResolver);
|
||||
const courses = function(fkValue: number | undefined) {
|
||||
return new DefaultCustomHasManyRepository(courseRepositoryGetter, {subject_id: fkValue} as DataObject<Course>);
|
||||
};
|
||||
courses.inclusionResolver = origCourses.inclusionResolver;
|
||||
this.courses = courses;
|
||||
this.courses = this.createCustomHasManyRepositoryFactoryFor('courses', courseRepositoryGetter, 'subjectId');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import { inject, Getter } from '@loopback/core';
|
||||
import { repository, HasManyThroughRepositoryFactory, DefaultTransactionalRepository } from '@loopback/repository';
|
||||
import { Getter, inject } from '@loopback/core';
|
||||
import { repository } from '@loopback/repository';
|
||||
import { DatabaseDataSource } from '../datasources';
|
||||
import { User, UserRelations, Course, CourseUser } from '../models';
|
||||
import { Course, CourseUser, User, UserRelations } from '../models';
|
||||
import { SubjectRepository } from './subject.repository';
|
||||
import { CourseUserRepository } from './course-user.repository';
|
||||
import { CourseRepository } from './course.repository';
|
||||
import { CustomCrudRepository, CustomHasManyThroughRepositoryFactory } from './custom-has-many-repository';
|
||||
|
||||
export class UserRepository extends DefaultTransactionalRepository<User,
|
||||
export class UserRepository extends CustomCrudRepository<User,
|
||||
typeof User.prototype.id,
|
||||
UserRelations> {
|
||||
|
||||
public readonly courses: HasManyThroughRepositoryFactory<Course, typeof Course.prototype.id,
|
||||
public readonly courses: CustomHasManyThroughRepositoryFactory<Course, typeof Course.prototype.id,
|
||||
CourseUser,
|
||||
typeof User.prototype.id>;
|
||||
|
||||
|
@ -18,7 +19,6 @@ export class UserRepository extends DefaultTransactionalRepository<User,
|
|||
@inject('datasources.database') dataSource: DatabaseDataSource, @repository.getter('SubjectRepository') protected subjectRepositoryGetter: Getter<SubjectRepository>, @repository.getter('CourseUserRepository') protected courseUserRepositoryGetter: Getter<CourseUserRepository>, @repository.getter('CourseRepository') protected courseRepositoryGetter: Getter<CourseRepository>,
|
||||
) {
|
||||
super(User, dataSource);
|
||||
this.courses = this.createHasManyThroughRepositoryFactoryFor('courses', courseRepositoryGetter, courseUserRepositoryGetter,);
|
||||
this.registerInclusionResolver('courses', this.courses.inclusionResolver);
|
||||
this.courses = this.createCustomHasManyThroughFactoryFor('courses', courseRepositoryGetter, courseUserRepositoryGetter, 'userId', 'courseId');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
|||
import { Observable } from 'rxjs';
|
||||
import { filter, map, shareReplay } from 'rxjs/operators';
|
||||
import { LoginService } from './auth/login.service';
|
||||
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Route, Router, Routes } from '@angular/router';
|
||||
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Params, Route, Router, Routes } from '@angular/router';
|
||||
import { RouteData } from './app-routing.module';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
|
@ -27,7 +27,7 @@ export class AppComponent implements OnInit {
|
|||
|
||||
pageTitle: string;
|
||||
|
||||
private activeRouteTitle: string;
|
||||
private activeComponentVars: object;
|
||||
|
||||
constructor(private breakpointObserver: BreakpointObserver, public loginService: LoginService,
|
||||
private router: Router, private activeRoute: ActivatedRoute, private title: Title) {
|
||||
|
@ -43,7 +43,9 @@ export class AppComponent implements OnInit {
|
|||
item.title = 'NOTFOUND';
|
||||
}
|
||||
}
|
||||
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
|
||||
this.setPageTitle();
|
||||
});
|
||||
}
|
||||
|
||||
getRoutes(data: { path: string, routes: Routes }[]): { routes: Routes, path: string, currentRoute: Route }[] {
|
||||
|
@ -82,32 +84,44 @@ export class AppComponent implements OnInit {
|
|||
return routeParts;
|
||||
}
|
||||
const data = snapshot.data as RouteData;
|
||||
if (data.title && snapshot.url.length) {
|
||||
if (data.title) {
|
||||
routeParts.push({
|
||||
title: data.title,
|
||||
url: snapshot.url[0].path
|
||||
url: snapshot.url.length ? snapshot.url[0].path : undefined,
|
||||
params: snapshot.params
|
||||
});
|
||||
}
|
||||
}
|
||||
return routeParts;
|
||||
}
|
||||
|
||||
setPageTitle(): void {
|
||||
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
|
||||
getTitleParts(): string[] {
|
||||
const routeParts = this.getRouteSegments(this.activeRoute.snapshot);
|
||||
if (!routeParts.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
function replaceVars(replacements: object, title: string): string {
|
||||
return Object.keys(replacements).reduce((pv, cv) => pv.replace(':' + cv, replacements[cv]), title);
|
||||
}
|
||||
|
||||
let titleParts = routeParts.reverse().map(part => replaceVars(part.params, part.title));
|
||||
if (this.activeComponentVars) {
|
||||
titleParts = titleParts.map(part => replaceVars(this.activeComponentVars, part));
|
||||
}
|
||||
return titleParts.map(part => part.startsWith(':') ? '~' : part);
|
||||
}
|
||||
|
||||
setPageTitle(): void {
|
||||
const titleParts = this.getTitleParts();
|
||||
if (!titleParts.length) {
|
||||
this.pageTitle = 'Szakdolgozat';
|
||||
return this.title.setTitle('Szakdolgozat');
|
||||
}
|
||||
const titleParts = routeParts.reverse().map(part => part.title);
|
||||
if (this.activeRouteTitle) { // TODO: Get title parts from parent components even if parent wasn't opened
|
||||
titleParts[titleParts.length - 1] = this.activeRouteTitle;
|
||||
}
|
||||
let pageTitle = titleParts.reduce((partA, partI) => `${partA} > ${partI}`);
|
||||
this.pageTitle = pageTitle;
|
||||
pageTitle += ` | Szakdolgozat`;
|
||||
this.title.setTitle(pageTitle);
|
||||
});
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
|
@ -119,23 +133,25 @@ export class AppComponent implements OnInit {
|
|||
return this.menu.filter(item => item.requiredRole === 'admin' ? this.loginService.user?.isAdmin : true); // TODO: Roles
|
||||
}
|
||||
|
||||
routeActivated($event: any): void {
|
||||
async routeActivated($event: any): Promise<void> {
|
||||
if (this.isCustomTitleComponent($event)) {
|
||||
this.activeRouteTitle = $event.getPageTitle();
|
||||
const title = $event.getPageTitleVars();
|
||||
this.activeComponentVars = title instanceof Promise ? await title : title;
|
||||
} else {
|
||||
this.activeRouteTitle = null;
|
||||
this.activeComponentVars = null;
|
||||
}
|
||||
this.setPageTitle();
|
||||
}
|
||||
|
||||
isCustomTitleComponent(obj: any): obj is CustomTitleComponent {
|
||||
return obj?.getPageTitle instanceof Function;
|
||||
return obj?.getPageTitleVars instanceof Function;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type MenuItem = { path: string, requiredRole: 'admin', title?: string }; // TODO: Role
|
||||
type RouteSegment = { title: string, url: string };
|
||||
type RouteSegment = { title: string, url: string, params: Params };
|
||||
|
||||
export interface CustomTitleComponent {
|
||||
getPageTitle(): string;
|
||||
getPageTitleVars(): object | Promise<object>;
|
||||
}
|
||||
|
|
33
frontend/src/app/graphql/course.graphql
Normal file
33
frontend/src/app/graphql/course.graphql
Normal file
|
@ -0,0 +1,33 @@
|
|||
query CourseListBySubject($subject: ID!, $limit: Int!, $offset: Int!) {
|
||||
coursesBySubject(subject: $subject, limit: $limit, offset: $offset) {
|
||||
count
|
||||
list {
|
||||
id
|
||||
alias
|
||||
semester
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query CourseListByUser($user: ID!, $limit: Int!, $offset: Int!) {
|
||||
coursesByUser(user: $user, limit: $limit, offset: $offset) {
|
||||
list {
|
||||
id
|
||||
alias
|
||||
semester
|
||||
}
|
||||
count
|
||||
}
|
||||
}
|
||||
|
||||
query Course($id: ID!) {
|
||||
course(id: $id) {
|
||||
id
|
||||
semester
|
||||
alias
|
||||
}
|
||||
}
|
||||
|
||||
mutation EditCourse($input: CourseUpdateInput!) {
|
||||
courseUpdate(course: $input)
|
||||
}
|
|
@ -16,11 +16,14 @@ mutation Logout {
|
|||
|
||||
query UserList($limit: Int!, $offset: Int!) {
|
||||
users(limit: $limit, offset: $offset) {
|
||||
list {
|
||||
id
|
||||
name
|
||||
email
|
||||
isAdmin
|
||||
}
|
||||
count
|
||||
}
|
||||
}
|
||||
|
||||
query User($id: ID!) {
|
||||
|
|
|
@ -55,6 +55,11 @@ export class EditComponent<T extends HasID, QT extends QueryResult<T>, UT extend
|
|||
} else {
|
||||
this.item = {} as T;
|
||||
this.creating = true;
|
||||
if (!this.createMutation) {
|
||||
alert('Nem hozható létre új elem ezzel a tipussal');
|
||||
await this.router.navigate(['..'], {relativeTo: this.route});
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.isLoading = false;
|
||||
}
|
||||
|
|
|
@ -3,15 +3,18 @@ import { PageEvent } from '@angular/material/paginator';
|
|||
import { PaginationData } from '../../utility/pagination-data';
|
||||
import { Router } from '@angular/router';
|
||||
import { Query } from 'apollo-angular';
|
||||
import { ListVariableType } from '../../utility/types';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list',
|
||||
templateUrl: './list.component.html',
|
||||
styleUrls: ['./list.component.css']
|
||||
})
|
||||
export class ListComponent<T extends { id: number }, U extends { [entityName: string]: { count: number; list: T[] } }> implements OnInit {
|
||||
export class ListComponent<T extends { id: number }, U extends { [entityName: string]: { count: number; list: T[] } },
|
||||
V extends object | undefined> implements OnInit {
|
||||
|
||||
@Input() gql: Query<U, { limit: number; offset: number }>;
|
||||
@Input() gql: Query<U, ListVariableType<V>>;
|
||||
@Input() queryVariables: V;
|
||||
@Input() itemType: T; // TODO: Remove
|
||||
@Input() columns: { title: string, prop: keyof T }[];
|
||||
@Input() allowNew = false;
|
||||
|
@ -36,7 +39,8 @@ export class ListComponent<T extends { id: number }, U extends { [entityName: st
|
|||
async getItems(limit: number, page: number): Promise<void> {
|
||||
try {
|
||||
this.loading = true;
|
||||
const {data} = await this.gql.fetch({limit, offset: (page - 1) * limit}).toPromise(); // TODO: Watch
|
||||
const vars = (this.queryVariables === undefined ? {} : this.queryVariables) as V extends object ? V : {};
|
||||
const {data} = await this.gql.fetch({...vars, limit, offset: (page - 1) * limit}).toPromise(); // TODO: Watch
|
||||
const key = Object.keys(data).filter(k => k !== '__typename')[0];
|
||||
this.paginationData.total = data[key].count;
|
||||
this.paginationData.page = page;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<!-- <app-edit [beforeSubmit]="beforeSubmit" [fields]="[
|
||||
<app-edit [beforeSubmit]="beforeSubmit" [gql]="itemGQL" [itemType]="itemType" [updateMutation]="editGQL" [fields]="[
|
||||
{title: 'Félév', name: 'semester'},
|
||||
{title: 'Név', name: 'alias'}
|
||||
]"></app-edit> -->
|
||||
]"></app-edit>
|
||||
|
|
|
@ -1,18 +1,27 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { CustomTitleComponent } from '../../../../app.component';
|
||||
import { Course, CourseGQL, EditCourseGQL, SubjectGQL } from '../../../../services/graphql';
|
||||
|
||||
@Component({
|
||||
selector: 'app-course-edit',
|
||||
templateUrl: './course-edit.component.html',
|
||||
styleUrls: ['./course-edit.component.css']
|
||||
})
|
||||
export class CourseEditComponent implements OnInit {
|
||||
export class CourseEditComponent implements OnInit, CustomTitleComponent {
|
||||
subjectId: string;
|
||||
itemType: Course;
|
||||
beforeSubmit = () => ({subjectId: +this.route.snapshot.params.subjectId});
|
||||
|
||||
constructor(private route: ActivatedRoute) {
|
||||
constructor(private route: ActivatedRoute, public subjectGQL: SubjectGQL, public itemGQL: CourseGQL, public editGQL: EditCourseGQL) {
|
||||
this.subjectId = route.snapshot.params.subjectId;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
getPageTitleVars(): object | Promise<object> {
|
||||
return this.subjectGQL.fetch({id: this.subjectId}).toPromise().then(subject => ({subjectName: subject.data.subject.name}));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<app-list [itemType]="itemType" allowNew="true" [columns]="[
|
||||
<app-list [itemType]="itemType" allowNew="true" [gql]="listGQL" [queryVariables]="{subject: subjectId}" [columns]="[
|
||||
{prop: 'semester', title: 'Félév'},
|
||||
{prop: 'alias', title: 'Név'}
|
||||
]"></app-list>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { CustomTitleComponent } from '../../../../app.component';
|
||||
import { Course, SubjectGQL } from '../../../../services/graphql';
|
||||
import { Course, CourseListBySubjectGQL, SubjectGQL } from '../../../../services/graphql';
|
||||
|
||||
@Component({
|
||||
selector: 'app-courses',
|
||||
|
@ -12,14 +12,14 @@ export class CourseListComponent implements OnInit, CustomTitleComponent {
|
|||
subjectId: string;
|
||||
itemType: Course;
|
||||
|
||||
constructor(route: ActivatedRoute, public listGQL: SubjectGQL) {
|
||||
constructor(route: ActivatedRoute, public subjectGQL: SubjectGQL, public listGQL: CourseListBySubjectGQL) {
|
||||
this.subjectId = route.snapshot.params.subjectId;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
getPageTitle(): string {
|
||||
return 'Custom title'; //TODO: SubjectGQL
|
||||
getPageTitleVars(): object | Promise<object> {
|
||||
return this.subjectGQL.fetch({id: this.subjectId}).toPromise().then(subject => ({subjectName: subject.data.subject.name}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@ import { CourseListComponent } from './subject-edit/courses/course-list/course-l
|
|||
import { CourseEditComponent } from './subject-edit/courses/course-edit/course-edit.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{path: '', component: SubjectListComponent, data: {title: 'Tárgyak'} as RouteData},
|
||||
{path: '', component: SubjectListComponent, data: {title: ''} as RouteData},
|
||||
{path: ':id', component: SubjectEditComponent, data: {title: 'Szerkesztés'}},
|
||||
{
|
||||
path: ':subjectId/courses', data: {title: 'Kurzusok'}, children: [
|
||||
path: ':subjectId/courses', data: {title: ':subjectName'}, children: [
|
||||
{path: ':id', component: CourseEditComponent, data: {title: 'Szerkesztés'} as RouteData},
|
||||
{path: '', component: CourseListComponent, data: {title: 'Kurzusok'}}
|
||||
]
|
||||
|
|
|
@ -7,7 +7,7 @@ import { SharedComponentsModule } from '../shared-components/shared-components.m
|
|||
import { UserEditComponent } from './user-edit/user-edit.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{path: '', component: UserListComponent, data: {title: 'Felhasználók'} as RouteData},
|
||||
{path: '', component: UserListComponent, data: {title: ''} as RouteData},
|
||||
{path: ':id', component: UserEditComponent, data: {title: 'Szerkesztés'}}
|
||||
];
|
||||
|
||||
|
|
|
@ -3,3 +3,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 };
|
||||
export type ListInput = { limit: number; offset: number };
|
||||
export type ListVariableType<V extends object | undefined> = (V extends object ? V : {}) & ListInput;
|
||||
|
|
Loading…
Reference in a new issue