Implement login, find user, update user (partly)
This commit is contained in:
parent
099b40e77a
commit
65332d4947
6 changed files with 126 additions and 167 deletions
|
@ -1,4 +1,4 @@
|
|||
import { arg, GraphQLBindings, mutation, query, resolver, ResolverData } from '@loopback/graphql';
|
||||
import { arg, authorized, GraphQLBindings, Int, mutation, query, resolver, ResolverData } from '@loopback/graphql';
|
||||
import { User } from '../models';
|
||||
import { repository } from '@loopback/repository';
|
||||
import { UserRepository } from '../repositories';
|
||||
|
@ -10,6 +10,8 @@ import { SecurityBindings, UserProfile } from '@loopback/security';
|
|||
import { genSalt, hash } from 'bcryptjs';
|
||||
import { UserRegisterInput } from '../graphql-types/input/user-register.input';
|
||||
import { validated } from '../helpers';
|
||||
import { LoginResult } from '../graphql-types/user';
|
||||
import { UserUpdateInput } from '../graphql-types/input/user-update.input';
|
||||
|
||||
@resolver(of => User)
|
||||
export class UserResolver {
|
||||
|
@ -39,20 +41,10 @@ export class UserResolver {
|
|||
return (await this.userRepository.find())[0];
|
||||
}
|
||||
|
||||
/*@mutation(returns => LoginResult)
|
||||
async login(
|
||||
@requestBody({
|
||||
description: 'The input of login function',
|
||||
required: true,
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: getModelSchemaRef(User, {exclude: ['id', 'isAdmin', 'name']})
|
||||
},
|
||||
}
|
||||
}) credentials: Pick<User, 'email' | 'password'>,
|
||||
): Promise<{ token: string, user: Omit<User, 'id' | 'password'> }> {
|
||||
@mutation(returns => LoginResult)
|
||||
async login(@arg('email') email: string, @arg('password') password: string): Promise<LoginResult> {
|
||||
// ensure the user exists, and the password is correct
|
||||
const user = await this.userService.verifyCredentials(credentials);
|
||||
const user = await this.userService.verifyCredentials({email, password});
|
||||
// convert a User object into a UserProfile object (reduced set of properties)
|
||||
const userProfile = this.userService.convertToUserProfile(user);
|
||||
|
||||
|
@ -61,16 +53,11 @@ export class UserResolver {
|
|||
return {token, user};
|
||||
}
|
||||
|
||||
@post('/users/logout', {
|
||||
responses: {
|
||||
'204': {
|
||||
description: 'Logged out',
|
||||
},
|
||||
},
|
||||
})
|
||||
@authenticate('jwt')
|
||||
async logout(@inject(RestBindings.Http.REQUEST) request: Request): Promise<void> {
|
||||
const split = request.headers.authorization?.split(' ');
|
||||
@authorized()
|
||||
@mutation(returns => Boolean)
|
||||
async logout(@inject(GraphQLBindings.RESOLVER_DATA) request: any): Promise<boolean> {
|
||||
console.log('request:', request); //TODO
|
||||
const split = request.headers.get('Authorization')?.split(' ');
|
||||
if (split && split.length > 1) {
|
||||
if (this.jwtService.revokeToken) {
|
||||
await this.jwtService.revokeToken(split[1]);
|
||||
|
@ -78,107 +65,38 @@ export class UserResolver {
|
|||
console.error('Cannot revoke token');
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@get('/users/count')
|
||||
@response(200, {
|
||||
description: 'User model count',
|
||||
content: {'application/json': {schema: CountSchema}},
|
||||
})
|
||||
@authenticate('jwt')
|
||||
async count(
|
||||
@param.where(User) where?: Where<User>,
|
||||
): Promise<Count> {
|
||||
return this.userRepository.count(where);
|
||||
@authorized()
|
||||
@query(returns => [User])
|
||||
async find(user: Partial<User>): Promise<User[]> {
|
||||
return this.userRepository.find({}); //TODO
|
||||
}
|
||||
|
||||
@get('/users')
|
||||
@response(200, {
|
||||
description: 'Array of User model instances',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'array',
|
||||
items: getModelSchemaRef(User, {includeRelations: true}),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@authenticate('jwt')
|
||||
async find(
|
||||
@param.filter(User) filter?: Filter<User>,
|
||||
): Promise<User[]> {
|
||||
return this.userRepository.find(filter);
|
||||
@authorized()
|
||||
@query(returns => User)
|
||||
async findById(@arg('id', returns => Int) id: number): Promise<User> {
|
||||
return this.userRepository.findById(id);
|
||||
}
|
||||
|
||||
@patch('/users')
|
||||
@response(200, {
|
||||
description: 'User PATCH success count',
|
||||
content: {'application/json': {schema: CountSchema}},
|
||||
})
|
||||
@authenticate('jwt')
|
||||
async updateAll(
|
||||
@requestBody({
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: getModelSchemaRef(User, {partial: true}),
|
||||
},
|
||||
},
|
||||
})
|
||||
user: User,
|
||||
@param.where(User) where?: Where<User>,
|
||||
): Promise<Count> {
|
||||
return this.userRepository.updateAll(user, where);
|
||||
}
|
||||
|
||||
@get('/users/{id}')
|
||||
@response(200, {
|
||||
description: 'User model instance',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: getModelSchemaRef(User, {includeRelations: true}),
|
||||
},
|
||||
},
|
||||
})
|
||||
@authenticate('jwt')
|
||||
async findById(
|
||||
@param.path.number('id') id: number,
|
||||
@param.filter(User, {exclude: 'where'}) filter?: FilterExcludingWhere<User>
|
||||
): Promise<User> {
|
||||
return this.userRepository.findById(id, filter);
|
||||
}
|
||||
|
||||
@patch('/users/{id}')
|
||||
@response(204, {
|
||||
description: 'User PATCH success',
|
||||
})
|
||||
@authenticate('jwt')
|
||||
async updateById(
|
||||
@param.path.number('id') id: number,
|
||||
@requestBody({
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: getModelSchemaRef(User, {partial: true}),
|
||||
},
|
||||
},
|
||||
})
|
||||
user: User,
|
||||
): Promise<void> {
|
||||
if (id === +this.user.id) {
|
||||
@authorized()
|
||||
@mutation(returns => Boolean)
|
||||
async updateById(@arg('id', returns => Int) id: number, @arg('user') user: UserUpdateInput): Promise<boolean> {
|
||||
if (id === +this.user?.id) { //TODO: this.user
|
||||
const loggedInUser = await this.userService.findUserById(this.user.id);
|
||||
if (user.isAdmin !== undefined && loggedInUser.isAdmin !== user.isAdmin) {
|
||||
throw new HttpErrors.BadRequest('Cannot change admin status of self');
|
||||
throw new Error('Cannot change admin status of self');
|
||||
}
|
||||
}
|
||||
await this.userRepository.updateById(id, user);
|
||||
return true;
|
||||
}
|
||||
|
||||
@del('/users/{id}')
|
||||
@response(204, {
|
||||
description: 'User DELETE success',
|
||||
})
|
||||
@authenticate('jwt')
|
||||
async deleteById(@param.path.number('id') id: number): Promise<void> {
|
||||
@authorized()
|
||||
@mutation(returns => Boolean)
|
||||
async deleteById(id: number): Promise<Boolean> {
|
||||
await this.userRepository.deleteById(id);
|
||||
}*/
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,37 +1,18 @@
|
|||
import { field, inputType } from '@loopback/graphql';
|
||||
import { User } from '../../models';
|
||||
import { model, property } from '@loopback/repository';
|
||||
import { UserProperties } from '../user';
|
||||
|
||||
@model()
|
||||
@inputType()
|
||||
export class UserRegisterInput implements Pick<User, 'email' | 'name' | 'password'> {
|
||||
@property({
|
||||
type: 'string',
|
||||
required: true,
|
||||
jsonSchema: {
|
||||
pattern: /[A-Za-z0-9.+_-]+@[A-Za-z.-_]*(u-szeged.hu)|(szte.hu)/.source
|
||||
}
|
||||
})
|
||||
@property(UserProperties.email)
|
||||
@field()
|
||||
email: string;
|
||||
@property({
|
||||
type: 'string',
|
||||
required: true,
|
||||
jsonSchema: {
|
||||
pattern: /([A-Za-z-.]+ )+[A-Za-z-.]+/.source
|
||||
}
|
||||
})
|
||||
@property(UserProperties.name)
|
||||
@field()
|
||||
name: string;
|
||||
@property({
|
||||
type: 'string',
|
||||
required: true,
|
||||
jsonSchema: {
|
||||
minLength: 8,
|
||||
maxLength: 255
|
||||
},
|
||||
hidden: true
|
||||
})
|
||||
@property(UserProperties.password)
|
||||
@field()
|
||||
password: string;
|
||||
}
|
||||
|
|
19
backend/src/graphql-types/input/user-update.input.ts
Normal file
19
backend/src/graphql-types/input/user-update.input.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { User } from '../../models';
|
||||
import { field, inputType } from '@loopback/graphql';
|
||||
import { UserProperties } from '../user';
|
||||
import { property } from '@loopback/repository';
|
||||
|
||||
@inputType()
|
||||
export class UserUpdateInput implements Partial<Pick<User, 'name' | 'email' | 'password' | 'isAdmin'>> {
|
||||
@field({nullable: true})
|
||||
@property(UserProperties.email)
|
||||
email?: string;
|
||||
@field({nullable: true})
|
||||
@property(UserProperties.name)
|
||||
name?: string;
|
||||
@field({nullable: true})
|
||||
@property(UserProperties.password)
|
||||
password?: string;
|
||||
@field({nullable: true})
|
||||
isAdmin?: boolean;
|
||||
}
|
17
backend/src/graphql-types/list.ts
Normal file
17
backend/src/graphql-types/list.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { field, inputType, objectType } from '@loopback/graphql';
|
||||
|
||||
@inputType()
|
||||
export class ListInput {
|
||||
@field()
|
||||
offset: number;
|
||||
@field()
|
||||
limit: number;
|
||||
}
|
||||
|
||||
@objectType()
|
||||
export class ListResponse<T> {
|
||||
@field()
|
||||
count: number;
|
||||
@field()
|
||||
list: T[];
|
||||
}
|
|
@ -1,6 +1,53 @@
|
|||
import { User } from '../models';
|
||||
import { field, objectType } from '@loopback/graphql';
|
||||
|
||||
export class LoginResult {
|
||||
token: string;
|
||||
user: Omit<User, 'id' | 'password'>;
|
||||
@objectType()
|
||||
export class UserResult implements Pick<User, 'id' | 'email' | 'name' | 'isAdmin'> {
|
||||
@field()
|
||||
email: string;
|
||||
@field()
|
||||
id?: number;
|
||||
@field()
|
||||
isAdmin: boolean;
|
||||
@field()
|
||||
name: string;
|
||||
}
|
||||
|
||||
@objectType()
|
||||
export class LoginResult {
|
||||
@field()
|
||||
token: string;
|
||||
@field()
|
||||
user: UserResult;
|
||||
}
|
||||
|
||||
export const UserProperties = {
|
||||
id: {
|
||||
type: 'number',
|
||||
id: true,
|
||||
generated: true,
|
||||
},
|
||||
email: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
jsonSchema: {
|
||||
pattern: /[A-Za-z\d.+_-]+@[A-Za-z.-_]*(u-szeged.hu)|(szte.hu)/.source
|
||||
}
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
jsonSchema: {
|
||||
pattern: /([A-Za-z-.]+ )+[A-Za-z-.]+/.source
|
||||
}
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
jsonSchema: {
|
||||
minLength: 8,
|
||||
maxLength: 255
|
||||
},
|
||||
hidden: true
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,47 +2,24 @@ import { Entity, hasMany, model, property } from '@loopback/repository';
|
|||
import { Course } from './course.model';
|
||||
import { CourseUser } from './course-user.model';
|
||||
import { field, objectType } from '@loopback/graphql';
|
||||
import { UserProperties } from '../graphql-types/user';
|
||||
|
||||
@model()
|
||||
@objectType()
|
||||
export class User extends Entity {
|
||||
@property({
|
||||
type: 'number',
|
||||
id: true,
|
||||
generated: true,
|
||||
})
|
||||
@property(UserProperties.id)
|
||||
@field()
|
||||
id?: number;
|
||||
|
||||
@property({
|
||||
type: 'string',
|
||||
required: true,
|
||||
jsonSchema: {
|
||||
pattern: /[A-Za-z0-9.+_-]+@[A-Za-z.-_]*(u-szeged.hu)|(szte.hu)/.source
|
||||
}
|
||||
})
|
||||
@property(UserProperties.email)
|
||||
@field()
|
||||
email: string;
|
||||
|
||||
@property({
|
||||
type: 'string',
|
||||
required: true,
|
||||
jsonSchema: {
|
||||
pattern: /([A-Za-z-.]+ )+[A-Za-z-.]+/.source
|
||||
}
|
||||
})
|
||||
@property(UserProperties.name)
|
||||
@field()
|
||||
name: string;
|
||||
|
||||
@property({
|
||||
type: 'string',
|
||||
required: true,
|
||||
jsonSchema: {
|
||||
minLength: 8,
|
||||
maxLength: 255
|
||||
},
|
||||
hidden: true
|
||||
})
|
||||
@property(UserProperties.password)
|
||||
@field()
|
||||
password: string;
|
||||
|
||||
|
|
Loading…
Reference in a new issue