Skip to content

Commit

Permalink
feat(server): added user color feature
Browse files Browse the repository at this point in the history
  • Loading branch information
anuraghazra committed Jun 19, 2020
1 parent f5c2f5c commit c6a7b6e
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 3 deletions.
3 changes: 3 additions & 0 deletions server/entities/Me.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export class Me {

@Field(type => [Room])
public rooms!: Ref<Room>[];

@Field()
public color!: string;
}

export default Me;
3 changes: 3 additions & 0 deletions server/entities/Member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export class Member {

@Field(type => [ID])
public rooms!: Ref<Room>[];

@Field()
public color!: string;
}

export default Member;
4 changes: 4 additions & 0 deletions server/entities/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export class User {
@arrayProp({ ref: 'room' })
public rooms!: Ref<Room>[];

@Field()
@Property({ default: '#64FF8F' })
public color!: string;

@Property({ required: true, unique: true })
public email!: string;

Expand Down
24 changes: 22 additions & 2 deletions server/modules/user/user-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import "reflect-metadata";
import { ObjectID } from 'mongodb'
import { Context } from "../context.type";
import { ApolloError } from "apollo-server-express";
import { Resolver, Query, Ctx, Arg, Authorized, Mutation } from 'type-graphql';
import { Resolver, Query, Ctx, Arg, Authorized, Mutation, Args } from 'type-graphql';

import Member from "../../entities/Member";
import UserModel, { User } from "../../entities/User";
import Me from "../../entities/Me";
import { setColorArgs } from "./user.inputs";

@Resolver(of => User)
class UserResolver {
Expand All @@ -30,7 +31,7 @@ class UserResolver {
@Authorized()
@Query(() => User)
async getUser(
@Arg("id", { nullable: false }) id?: ObjectID
@Arg("id", { nullable: false }) id: ObjectID
): Promise<User> {
try {
let user = await UserModel.findOne({ _id: id }).populate("rooms");
Expand All @@ -41,6 +42,25 @@ class UserResolver {
}
}

@Authorized()
@Mutation(returns => Member)
async setColor(
@Args() { color }: setColorArgs,
@Ctx() context: Context
) {
try {
let user = await UserModel.findOneAndUpdate(
{ _id: context.currentUser.id },
{ color: color },
{ new: true }
)
if (!user) throw new ApolloError(`User not found with id`);
return user;
} catch (err) {
throw new ApolloError(err);
}
}

@Authorized()
@Mutation(returns => Boolean)
logout(@Ctx() context: Context) {
Expand Down
28 changes: 28 additions & 0 deletions server/modules/user/user.inputs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
IsHexColor,
ValidationArguments,
ValidatorConstraint,
ValidatorConstraintInterface,
} from "class-validator";
import { isColorTooDark } from '../../utils'
import { Validate } from "class-validator";
import { ArgsType, Field } from "type-graphql";

@ValidatorConstraint({ name: "validateColor", async: false })
class ValidateColor implements ValidatorConstraintInterface {
validate(text: string, args: ValidationArguments) {
return !isColorTooDark(text);
}

defaultMessage(args: ValidationArguments) {
return "Color value ($value) is too dark";
}
}

@ArgsType()
export class setColorArgs {
@Field({ nullable: false })
@IsHexColor()
@Validate(ValidateColor)
color: string;
}
33 changes: 33 additions & 0 deletions server/modules/user/user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ const queries = {
}
}
}
`,
setColor: `
mutation setColor($color: String!) {
setColor(color: $color) {
name
color
}
}
`
}

Expand Down Expand Up @@ -81,4 +89,29 @@ describe("UserResolver", () => {
}
})
})

it("should change user's color", async () => {
const OK_COLOR = '#ff5896';
const BAD_COLOR = '#000000';
let user = await gCall({
source: queries.setColor,
variableValues: { color: BAD_COLOR }
});

expect(user.errors[0].message).toBe('Argument Validation Error')

user = await gCall({
source: queries.setColor,
variableValues: { color: OK_COLOR }
});

expect(user).toMatchObject({
data: {
setColor: {
name: fakeUser.name,
color: OK_COLOR,
}
}
})
})
})
18 changes: 17 additions & 1 deletion server/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ const generateUsername = (name: string) => {
return `${sanitized}-${uniqueId}`;
};

const getColorLuminance = (color: string) => {
// https://stackoverflow.com/a/12043228/10629172
const c = color.substring(1); // strip #
const rgb = parseInt(c, 16); // convert rrggbb to decimal
const r = (rgb >> 16) & 0xff; // extract red
const g = (rgb >> 8) & 0xff; // extract green
const b = (rgb >> 0) & 0xff; // extract blue

const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b; // per ITU-R BT.709
return luma;
}

const isColorTooDark = (color: string) => getColorLuminance(color) < 40;

export {
generateUsername,
};
getColorLuminance,
isColorTooDark,
};

0 comments on commit c6a7b6e

Please sign in to comment.