Skip to content

Commit

Permalink
Add user error
Browse files Browse the repository at this point in the history
  • Loading branch information
johnrazeur committed Nov 24, 2019
1 parent 65f538e commit 0e370b6
Show file tree
Hide file tree
Showing 24 changed files with 1,803 additions and 1,532 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
# Changelog

## [2.0.0] - 2019-11-24

### Added
- Add user error in payload with union fragments

### Changed
- Standardize arguments

### Removed
- Lazy relation in entities

## [1.0.0] - 2019-07-14
First release
41 changes: 34 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,18 @@ yo graphql-typescript myapp
### Login

```graphql
query {
login(login: {email: "test@github.com", password: "s3cr3tp4ssw0rd"})
query login(
input: $input
) {
... on LoginType {
__typename
token
}
... on UserError {
__typename
message
}
}
}
```

Expand Down Expand Up @@ -60,8 +70,18 @@ query {

```graphql
mutation {
register(user: {username: "test", email:"test@gmail.com", password: "pass", confirmPassword: "pass"}) {
username
register(
input: $input
) {
... on User {
__typename
username
email
}
... on UserError {
__typename
message
}
}
}
```
Expand All @@ -78,9 +98,16 @@ You need to put the token you get from the login query to perform this query. Pr

```graphql
mutation {
addProject(project: { name: "test"}) {
name,
owner { email }
createProject(
input: $input
) {
__typename
... on Project {
name
}
... on UserError {
message
}
}
}
```
Expand Down
10 changes: 7 additions & 3 deletions __tests__/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,20 @@ describe("generator-rest-express-typescript:app", () => {
"src/context/authChecker.ts",
"src/server/createSchema.ts",
"src/server/index.ts",
"src/utils/helpers.ts",
"src/utils/error.ts",
"src/utils/genericTypes.ts",
"src/graphql/auth",
"src/graphql/auth/login.input.ts",
"src/graphql/auth/auth.input.ts",
"src/graphql/auth/auth.resolver.ts",
"src/graphql/auth/user.service.ts",
"src/graphql/auth/register.input.ts",
"src/graphql/auth/auth.payload.ts",
"src/graphql/auth/auth.error.ts",
"src/graphql/project",
"src/graphql/project/project.service.ts",
"src/graphql/project/project.resolver.ts",
"src/graphql/project/project.input.ts",
"src/graphql/project/project.error.ts",
"src/graphql/project/project.payload.ts",
"src/index.ts",
"src/validators/isUserAlreadyExist.ts",
"src/validators/sameValue.ts",
Expand Down
5 changes: 2 additions & 3 deletions generators/app/templates/src/entities/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
} from "typeorm";
import { Field, ObjectType } from "type-graphql";
import { User } from "./user";
import { Lazy } from "../utils/helpers";

@ObjectType()
@Entity()
Expand All @@ -31,6 +30,6 @@ export class Project {
public updatedAt: Date;

@Field((): typeof User => User)
@ManyToOne((): typeof User => User, { lazy: true })
public owner: Lazy<User>;
@ManyToOne((): typeof User => User)
public owner: User;
}
2 changes: 1 addition & 1 deletion generators/app/templates/src/entities/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class User implements UserInterface {
public email: string;

@Field()
@Column({ nullable: true })
@Column()
public username: string;

@Column()
Expand Down
22 changes: 22 additions & 0 deletions generators/app/templates/src/graphql/auth/auth.error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { MyError } from "../../utils/error";

export class InvalidEmailOrPasswordError extends MyError {
constructor() {
const message = "invalid email or password";
super(message);
}
}

export class EmailAlreadyUseError extends MyError {
constructor() {
const message = "email already use";
super(message);
}
};

export class UsernameAlreadyUseError extends MyError {
constructor() {
const message = "username already use";
super(message);
}
};
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { InputType, Field } from "type-graphql";
import { SameValue } from "../../validators/sameValue";
import { IsUserAlreadyExist } from "../../validators/isUserAlreadyExist";
import { IsEmail } from "class-validator";

@InputType()
export class LoginInput {
@Field()
@IsEmail()
public email: string;

@Field()
public password: string;
}

@InputType()
export class RegisterInput {
@Field()
public username: string;

@Field()
@SameValue('confirmPassword', {
message: "Passwords are not the same."
})
public password: string;

@Field()
@IsEmail()
@IsUserAlreadyExist({
message: "Email $value is already use. Choose another email."
})
public email: string;

@Field()
Expand Down
13 changes: 13 additions & 0 deletions generators/app/templates/src/graphql/auth/auth.payload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

import { ObjectType, Field } from "type-graphql";
import { User } from "../../entities/user";
import { userResponse } from "../../utils/genericTypes";

@ObjectType()
export class LoginType {
@Field()
public token: string;
}

export const LoginPayload = userResponse("LoginPayload", LoginType);
export const RegisterPayload = userResponse("RegisterPayload", User);
38 changes: 24 additions & 14 deletions generators/app/templates/src/graphql/auth/auth.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,39 @@
import { Resolver, Query, Arg, Mutation } from "type-graphql";

import { RegisterInput } from "./register.input";
import { LoginInput } from "./login.input";
import { Arg, Mutation, Query, Resolver } from "type-graphql";
import { Inject } from "typedi";
import { MyError } from "../../utils/error";
import { UserError } from "../../utils/genericTypes";
import { LoginInput, RegisterInput } from "./auth.input";
import { LoginPayload, LoginType, RegisterPayload } from "./auth.payload";
import { UserService } from "./user.service";
import { User } from "../../entities/user";
import { AuthenticationError } from "apollo-server";


@Resolver()
export class AuthResolver {
@Inject()
private readonly userService: UserService;

@Query((): StringConstructor => String)
public async login(@Arg("login") loginInput: LoginInput): Promise<string> {
@Query((): typeof LoginPayload => LoginPayload)
public async login(@Arg("input") input: LoginInput): Promise<typeof LoginPayload> {
try {
return await this.userService.login(loginInput);
const loginType = new LoginType();
const token = await this.userService.login(input.email, input.password);

loginType.token = token;
return loginType;
} catch(e) {
throw new AuthenticationError("invalid login or password");
if (e instanceof MyError) {
return new UserError(e.message);
}
}
}

@Mutation((): typeof User => User)
public async register(@Arg("user") registerInput: RegisterInput): Promise<User> {
return this.userService.register(registerInput);
@Mutation((): typeof RegisterPayload => RegisterPayload)
public async register(@Arg("input") input: RegisterInput): Promise<typeof RegisterPayload> {
try {
return await this.userService.register(input.username, input.password, input.email);
} catch(e) {
if (e instanceof MyError) {
return new UserError(e.message);
}
}
}
}
12 changes: 0 additions & 12 deletions generators/app/templates/src/graphql/auth/login.input.ts

This file was deleted.

27 changes: 17 additions & 10 deletions generators/app/templates/src/graphql/auth/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import * as jwt from "jsonwebtoken";
import { Repository } from "typeorm";

import { User } from "../../entities/user";
import { RegisterInput } from "./register.input";
import { LoginInput } from "./login.input";
import { InvalidEmailOrPasswordError, EmailAlreadyUseError, UsernameAlreadyUseError } from "./auth.error";

@Service()
export class UserService {
Expand All @@ -16,23 +15,31 @@ export class UserService {
return this.userRepository.findOne({ email: email });
}

public async register(registerInput: RegisterInput): Promise<User> {
public async register(username: string, password: string, email: string): Promise<User> {
if (await this.userRepository.findOne({ email })) {
throw new EmailAlreadyUseError();
}

if (await this.userRepository.findOne({ username })) {
throw new UsernameAlreadyUseError();
}

const user: User = this.userRepository.create({
username: registerInput.username,
password: registerInput.password,
email: registerInput.email
username,
password,
email
});

user.hashPassword();

return this.userRepository.save(user);
}

public async login(loginInput: LoginInput): Promise<string> {
const user = await this.getUserByEmail(loginInput.email);
public async login(email: string, password: string): Promise<string> {
const user = await this.getUserByEmail(email);

if (!user.checkIfUnencryptedPasswordIsValid(loginInput.password)) {
throw new Error("invalid credential");
if (!user || !user.checkIfUnencryptedPasswordIsValid(password)) {
throw new InvalidEmailOrPasswordError();
}

const token = jwt.sign(
Expand Down
8 changes: 8 additions & 0 deletions generators/app/templates/src/graphql/project/project.error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { MyError } from "../../utils/error";

export class ProjectSameNameError extends MyError {
constructor(projectName: string) {
const message = `project ${projectName} already exists`;
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { userResponse } from "../../utils/genericTypes";
import { Project } from "../../entities/project";

export const CreateProjectPayload = userResponse("CreateProjectPayload", Project);
28 changes: 21 additions & 7 deletions generators/app/templates/src/graphql/project/project.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Resolver, Query, Arg, Mutation, Ctx, Authorized } from "type-graphql";
import { Arg, Authorized, Ctx, Mutation, Query, Resolver, FieldResolver, Root } from "type-graphql";
import { Inject } from "typedi";
import { Context } from "../../context/context.interface";
import { Project } from "../../entities/project";
import { User } from "../../entities/user";
import { UserError } from "../../utils/genericTypes";
import { ProjectInput } from "./project.input";
import { Context } from "../../context/context.interface";
import { CreateProjectPayload } from "./project.payload";
import { ProjectService } from "./project.service";
import { Inject } from "typedi";

import { MyError } from "../../utils/error";

@Resolver(Project)
export class ProjectResolver {
Expand All @@ -18,8 +21,19 @@ export class ProjectResolver {
}

@Authorized()
@Mutation((): typeof Project => Project)
public addProject(@Arg("project") projectInput: ProjectInput, @Ctx() { user }: Context): Promise<Project> {
return this.projectService.createProject(projectInput, user);
@Mutation((): typeof CreateProjectPayload => CreateProjectPayload)
public async createProject(@Arg("input") input: ProjectInput, @Ctx() { user }: Context): Promise<typeof CreateProjectPayload> {
try {
return await this.projectService.createProject(input.name, user);
} catch (e) {
if (e instanceof MyError) {
return new UserError(e.message);
}
}
}

@FieldResolver()
public async owner(@Root() project: Project): Promise<User> {
return this.projectService.getOwnerByProjectId(project.id);
}
}
Loading

0 comments on commit 0e370b6

Please sign in to comment.