Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write mutations to: create a community and join a community #41

Merged
merged 24 commits into from
Dec 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
fab0fa7
build(deps): update version of koa-graphql dependency
noghartt Dec 15, 2021
796eddd
feat: add headerEditorEnabled on graphiql option on koa-graphql
noghartt Dec 15, 2021
7c52e92
feat: write a auth function to get user if exists
noghartt Dec 15, 2021
d49a8d6
feat: add user to context on graphql request
noghartt Dec 15, 2021
b4c70bd
feat: add communities array as field on the user document
noghartt Dec 15, 2021
9aae6dd
feat: write CommunityModel
noghartt Dec 15, 2021
cbe405b
feat: write CommunityType
noghartt Dec 15, 2021
1ab48e2
feat: write createCommunityMutation
noghartt Dec 15, 2021
26e0ffd
feat: write joinCommunityMutation
noghartt Dec 15, 2021
fce6b07
test: write a method to upsert models on tests
noghartt Dec 15, 2021
b2ecbaf
style: improve types on createUser fixture function
noghartt Dec 15, 2021
38fb0fb
test: write a fixture to create communities
noghartt Dec 15, 2021
7dd3290
test: write tests to createCommunityMutation
noghartt Dec 15, 2021
8a2fe45
test: write tests to joinCommunity.test.ts
noghartt Dec 15, 2021
ccb831f
build(tsconfig): exclude test files and fixtures when build server
noghartt Dec 15, 2021
9c530da
chore(gitignore): fix /graphql folder on gitignore
noghartt Dec 15, 2021
033f29a
style: add setting to detect react version on eslint config
noghartt Dec 15, 2021
7fe1e33
feat: write graphql modules with initial GraphQLContext type
noghartt Dec 15, 2021
97f6002
style: rename community mutations to avoid redundancy
noghartt Dec 15, 2021
671a6bf
fix(community): instantiates a new model with correctly properties
noghartt Dec 15, 2021
0468961
style: move outputFields on community mutations down from resolvers
noghartt Dec 15, 2021
49c02f5
style: change findedCommunity to communityFound
noghartt Dec 15, 2021
6370492
test: rename community mutation tests to according with mutations
noghartt Dec 15, 2021
c746bcf
feat: add ref prop to fields on Community schema
noghartt Dec 16, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,12 @@ module.exports = {
'relay/unused-fields': 'off',
},
settings: {
react: {
version: 'detect',
},
'import/resolver': {
typescript: {
directory: ['packages/*/tsconfig.json'],
project: ['packages/*/tsconfig.json'],
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion packages/server/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
graphql/
/graphql
6 changes: 3 additions & 3 deletions packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@
},
"dependencies": {
"@koa/cors": "^3.1.0",
"@koa/router": "^10.1.1",
"bcryptjs": "^2.4.3",
"dotenv": "^10.0.0",
"graphql": "^15.6.1",
"graphql-relay": "^0.9.0",
"jsonwebtoken": "^8.5.1",
"koa": "^2.13.3",
"koa-bodyparser": "^4.3.0",
"koa-graphql": "^0.9.0",
"koa-router": "^10.1.1",
"koa-graphql": "^0.12.0",
"mongoose": "^6.0.10"
},
"devDependencies": {
Expand All @@ -32,8 +32,8 @@
"@types/koa": "^2.13.4",
"@types/koa-bodyparser": "^4.3.3",
"@types/koa-graphql": "^0.8.5",
"@types/koa-router": "^7.4.4",
"@types/koa__cors": "^3.0.3",
"@types/koa__router": "^8.0.11",
"babel-loader": "^8.2.3",
"mongodb-memory-server-global": "^8.0.0",
"webpack": "^5.64.0",
Expand Down
61 changes: 37 additions & 24 deletions packages/server/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,49 @@
import Koa from 'koa';
import Router from 'koa-router';
import Koa, { Request } from 'koa';
import Router from '@koa/router';
import bodyparser from 'koa-bodyparser';
import GraphQLHTTP from 'koa-graphql';
import { graphqlHTTP, OptionsData } from 'koa-graphql';
import cors from '@koa/cors';

import { schema } from './schema';
import { config } from './environment';
import { getUser } from './auth';

const app = new Koa();
const router = new Router();

const graphQlSettingsPerReq = async (): Promise<GraphQLHTTP.OptionsData> => ({
graphiql: config.NODE_ENV !== 'production',
schema,
pretty: true,
formatError: ({ message, locations, stack }) => {
/* eslint-disable no-console */
console.log(message);
console.log(locations);
console.log(stack);
/* eslint-enable no-console */

return {
message,
locations,
stack,
};
},
});

const graphQlServer = GraphQLHTTP(graphQlSettingsPerReq);

const graphQlSettingsPerReq = async (req: Request): Promise<OptionsData> => {
const user = await getUser(req.header.authorization);

return {
graphiql:
config.NODE_ENV !== 'production'
? {
headerEditorEnabled: true,
shouldPersistHeaders: true,
}
: false,
schema,
pretty: true,
context: {
user,
},
customFormatErrorFn: ({ message, locations, stack }) => {
/* eslint-disable no-console */
console.log(message);
console.log(locations);
console.log(stack);
/* eslint-enable no-console */

return {
message,
locations,
stack,
};
},
};
};

const graphQlServer = graphqlHTTP(graphQlSettingsPerReq);
router.all('/graphql', graphQlServer);

app.use(cors());
Expand Down
20 changes: 20 additions & 0 deletions packages/server/src/auth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
import jwt from 'jsonwebtoken';

import { Maybe } from '@fakeddit/types';

import { UserModel, UserDocument } from './modules/user/UserModel';
import { config } from './environment';

export const getUser = async (
token: Maybe<string>,
): Promise<Maybe<UserDocument>> => {
if (!token) return null;

// TODO: Maybe it should be a crime

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drastic measures are sometimes necessary lmao

[, token] = token.split('JWT ');

const decodedToken = jwt.verify(token, config.JWT_SECRET) as { id: string };

const user = await UserModel.findOne({ _id: decodedToken.id });

if (!user) return null;

return user;
};

export const generateJwtToken = (userId: string) =>
`JWT ${jwt.sign({ id: userId }, config.JWT_SECRET)}`;
47 changes: 47 additions & 0 deletions packages/server/src/modules/community/CommunityModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import mongoose, { Schema, Types } from 'mongoose';

export interface Community {
name: string;
displayName: string;
admin: Types.ObjectId;
members: Types.ObjectId[];
mods: Types.ObjectId[];
}

const CommunitySchema = new Schema(
{
name: {
type: String,
required: true,
unique: true,
maxlength: 21,
},
admin: {
type: Schema.Types.ObjectId,
noghartt marked this conversation as resolved.
Show resolved Hide resolved
ref: 'User',
required: true,
},
displayName: {
type: String,
required: true,
},
mods: {
type: [Schema.Types.ObjectId],
ref: 'User',
default: [],
},
members: {
type: [Schema.Types.ObjectId],
ref: 'User',
required: true,
},
},
{
timestamps: {
createdAt: true,
updatedAt: true,
},
},
);

export const CommunityModel = mongoose.model('Community', CommunitySchema);
42 changes: 42 additions & 0 deletions packages/server/src/modules/community/CommunityType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
GraphQLObjectType,
GraphQLString,
GraphQLList,
GraphQLID,
GraphQLNonNull,
} from 'graphql';
import { globalIdField } from 'graphql-relay';

import { Community } from './CommunityModel';

export const CommunityType = new GraphQLObjectType<Community>({
name: 'Community',
fields: {
id: globalIdField('Community'),
name: {
type: new GraphQLNonNull(GraphQLString),
resolve: community => community.name,
description: 'The slugged name of the community - this is unique',
},
displayName: {
type: new GraphQLNonNull(GraphQLString),
resolve: community => community.displayName,
description:
"Some custom name that doens't necessary be the name of the community",
},
admin: {
type: new GraphQLNonNull(GraphQLID),
},
mods: {
type: new GraphQLNonNull(new GraphQLList(GraphQLID)),
resolve: community => community.mods,
},
members: {
// TODO: Turn it a list of ObjectIDs
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLID))),
resolve: community => community.members,
description:
'A list containing the IDs of all users that is member of this community',
},
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { graphql } from 'graphql';

import {
clearDatabaseAndRestartCounters,
connectWithMongoose,
disconnectWithMongoose,
} from '../../../../test';
import { schema } from '../../../schema';

import { createUser } from '../../user/fixtures/createUser';

beforeAll(connectWithMongoose);
beforeEach(clearDatabaseAndRestartCounters);
afterAll(disconnectWithMongoose);

it('should create a new community', async () => {
const user = await createUser();

const mutation = `
mutation M($displayName: String!, $communityId: String!) {
communityCreate(
input: { displayName: $displayName, communityId: $communityId }
) {
community {
id
name
displayName
members
}
}
}
`;

const rootValue = {};

const variables = {
displayName: 'A community to lovers of tests',
communityId: 'WeLoveTests',
};

const result = await graphql(
schema,
mutation,
rootValue,
{ user },
variables,
);

expect(result.errors).toBeUndefined();

const { community } = result?.data?.communityCreate;

expect(community.id).toBeDefined();
expect(community.name).toBe(variables.communityId);
expect(community.displayName).toBe(variables.displayName);
expect(community.members).toHaveLength(1);
expect(community.members).toContain(user._id.toString());
});

it("should not allow create a community if doesn't have authorization header", async () => {
const mutation = `
mutation M($displayName: String!, $communityId: String!) {
communityCreate(
input: { displayName: $displayName, communityId: $communityId }
) {
community {
id
name
displayName
members
}
}
}
`;

const rootValue = {};

const variables = {
displayName: 'A community to lovers of tests',
communityId: 'WeLoveTests',
};

const result = await graphql(schema, mutation, rootValue, {}, variables);

expect(result?.data?.communityCreate).toBeNull();

expect(result?.errors).toBeDefined();
expect(result.errors && result.errors[0]?.message).toBe(
'You are not logged in. Please, try again!',
);
});

it('should not create a duplicate community', async () => {
const user = await createUser();

const mutation = `
mutation M($displayName: String!, $communityId: String!) {
communityCreate(
input: { displayName: $displayName, communityId: $communityId }
) {
community {
id
name
displayName
members
}
}
}
`;

const rootValue = {};

const variables = {
displayName: 'A community to lovers of tests',
communityId: 'WeLoveTests',
};

await graphql(schema, mutation, rootValue, { user }, variables);
const result = await graphql(
schema,
mutation,
rootValue,
{ user },
variables,
);

expect(result?.data?.communityCreate).toBeNull();

expect(result?.errors).toBeDefined();
expect(result.errors && result.errors[0].message).toBe(
'A community with this name has already been created. Please, try again!',
);
});
Loading