Skip to content

Commit 0f61c0b

Browse files
committed
init. setup server with apollo, accounts, typegoose and type-graphql
0 parents  commit 0f61c0b

21 files changed

+9314
-0
lines changed

.prettierrc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"printWidth": 100,
3+
"semi": false,
4+
"singleQuote": true,
5+
"trailingComma": "es5"
6+
}

server/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/node_modules/

server/babel.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'],
3+
}

server/config/default.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"PORT": 4000,
3+
"DB_NAME": "oneFraction",
4+
"MONGO_HOST": "",
5+
"ACCOUNTS_SECRET": ""
6+
}

server/config/local.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"PORT": 4000,
3+
"DB_NAME": "oneFraction",
4+
"MONGO_HOST": "",
5+
"ACCOUNTS_SECRET": "979083ce-a9b6-483d-a899-ea22d5b0a1dd"
6+
}

server/package.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"name": "server",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"license": "MIT",
6+
"scripts": {
7+
"start": "ts-node src/index.ts",
8+
"watch": "nodemon -e ts -w ./src -x npm run watch:serve",
9+
"watch:serve": "ts-node --inspect src/index.ts"
10+
},
11+
"devDependencies": {
12+
"@babel/preset-env": "^7.4.5",
13+
"@babel/preset-typescript": "^7.3.3",
14+
"@types/config": "^0.0.34",
15+
"@types/graphql": "^14.2.0",
16+
"@types/mongoose": "^5.5.3",
17+
"@types/node": "^12.0.4",
18+
"husky": "^2.3.0",
19+
"nodemon": "^1.19.1",
20+
"prettier": "^1.17.1",
21+
"pretty-quick": "^1.11.0",
22+
"ts-node": "^8.2.0",
23+
"typescript": "^3.5.1"
24+
},
25+
"dependencies": {
26+
"@accounts/database-manager": "^0.15.0",
27+
"@accounts/graphql-api": "^0.15.0",
28+
"@accounts/mongo": "^0.15.0",
29+
"@accounts/password": "^0.15.0",
30+
"@accounts/server": "^0.15.0",
31+
"@graphql-modules/core": "^0.7.5",
32+
"apollo-server": "^2.6.1",
33+
"apollo-server-express": "^2.6.1",
34+
"config": "^3.1.0",
35+
"graphql": "^14.3.1",
36+
"graphql-toolkit": "^0.2.14",
37+
"mongoose": "^5.5.12",
38+
"reflect-metadata": "^0.1.13",
39+
"type-graphql": "^0.17.4",
40+
"typegoose": "^5.6.0"
41+
},
42+
"husky": {
43+
"hooks": {
44+
"pre-commit": "pretty-quick --staged"
45+
}
46+
}
47+
}

server/schema.gql

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# -----------------------------------------------
2+
# !!! THIS FILE WAS GENERATED BY TYPE-GRAPHQL !!!
3+
# !!! DO NOT MODIFY THIS FILE BY YOURSELF !!!
4+
# -----------------------------------------------
5+
6+
"""
7+
The javascript `Date` as string. Type represents date and time as the ISO Date string.
8+
"""
9+
scalar DateTime
10+
11+
type Query {
12+
me: User!
13+
getUser(userId: ID!): User!
14+
}
15+
16+
enum Role {
17+
User
18+
Admin
19+
}
20+
21+
type User {
22+
_id: ID!
23+
sessionId: String!
24+
roles: [Role!]!
25+
phoneNumber: String!
26+
username: String
27+
ethAddress: String
28+
smsToken: String
29+
isBlocked: Boolean
30+
isPhoneVerified: Boolean
31+
isOnboarded: Boolean
32+
createdAt: DateTime!
33+
updatedAt: DateTime!
34+
}

server/src/index.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import 'reflect-metadata'
2+
import * as mongoose from 'mongoose'
3+
import * as express from 'express'
4+
import { ApolloServer, makeExecutableSchema } from 'apollo-server-express'
5+
import { buildSchema } from 'type-graphql'
6+
import { mergeResolvers, mergeTypeDefs, mergeSchemas } from 'graphql-toolkit'
7+
import { PORT, MONGO_HOST, DB_NAME } from './modules/common/consts'
8+
import UserResolver from './modules/user/UserResolver'
9+
import { authChecker } from './modules/user/authChecker'
10+
import { setUpAccounts, userTypeDefs } from './modules/user/accounts'
11+
import { TypegooseMiddleware } from './middleware/typegoose'
12+
13+
;(async () => {
14+
const mongooseConnection = await mongoose.connect(
15+
`mongodb://${MONGO_HOST || 'localhost'}:27017/${DB_NAME}`
16+
)
17+
const { accountsGraphQL, accountsServer } = setUpAccounts(mongooseConnection.connection)
18+
19+
const typeGraphqlSchema = await buildSchema({
20+
resolvers: [UserResolver],
21+
globalMiddlewares: [TypegooseMiddleware],
22+
// scalarsMap: [{ type: ObjectId, scalar: ObjectIdScalar }],
23+
validate: false,
24+
emitSchemaFile: true,
25+
authChecker,
26+
})
27+
28+
const schema = makeExecutableSchema({
29+
typeDefs: mergeTypeDefs([userTypeDefs, accountsGraphQL.typeDefs]),
30+
resolvers: mergeResolvers([accountsGraphQL.resolvers]),
31+
schemaDirectives: {
32+
...accountsGraphQL.schemaDirectives,
33+
},
34+
})
35+
36+
const server = new ApolloServer({
37+
schema: mergeSchemas({
38+
schemas: [schema, typeGraphqlSchema],
39+
}),
40+
context: accountsGraphQL.context,
41+
formatError: error => {
42+
console.error(error)
43+
return error
44+
},
45+
playground: true,
46+
})
47+
48+
const app = express()
49+
server.applyMiddleware({ app })
50+
51+
await app.listen({ port: PORT })
52+
console.log(`🚀 Server ready at localhost:${PORT}`)
53+
})()

server/src/middleware/typegoose.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { MiddlewareFn } from 'type-graphql'
2+
import { Model, Document } from 'mongoose'
3+
import { getClassForDocument } from 'typegoose'
4+
5+
export const TypegooseMiddleware: MiddlewareFn = async (_, next) => {
6+
const result = await next()
7+
8+
if (Array.isArray(result)) {
9+
return result.map(item => (item instanceof Model ? convertDocument(item) : item))
10+
}
11+
12+
if (result instanceof Model) {
13+
return convertDocument(result)
14+
}
15+
16+
return result
17+
}
18+
19+
function convertDocument(doc: Document) {
20+
const convertedDocument = doc.toObject()
21+
const DocumentClass: Function = getClassForDocument(doc)
22+
Object.setPrototypeOf(convertedDocument, DocumentClass.prototype)
23+
return convertedDocument
24+
}

server/src/modules/common/consts.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import * as config from 'config'
2+
3+
export const PORT: number = config.get('PORT')
4+
export const MONGO_HOST: string = config.get('MONGO_HOST')
5+
export const DB_NAME: string = config.get('DB_NAME')
6+
export const ACCOUNTS_SECRET: string = config.get('ACCOUNTS_SECRET')

server/src/modules/common/context.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import User from '../user/UserEntity'
2+
3+
export interface Context {
4+
userId?: string
5+
user?: User
6+
}

server/src/modules/user/UserEntity.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { prop, Typegoose } from 'typegoose'
2+
import { ObjectType, Field, ID } from 'type-graphql'
3+
import { ObjectId } from 'mongodb'
4+
import { Role } from './consts'
5+
6+
@ObjectType()
7+
export class User extends Typegoose {
8+
@Field(type => ID)
9+
readonly _id: ObjectId
10+
11+
@prop()
12+
@Field()
13+
sessionId?: string
14+
15+
@prop({ required: true, enum: Role })
16+
@Field(type => Role)
17+
roles: Role[]
18+
19+
@prop({ required: true })
20+
@Field()
21+
phoneNumber: string
22+
23+
@prop({ lowercase: true })
24+
@Field({ nullable: true })
25+
username?: string
26+
27+
@prop({ lowercase: true })
28+
@Field({ nullable: true })
29+
ethAddress?: string
30+
31+
@prop()
32+
@Field({ nullable: true })
33+
smsToken?: string
34+
35+
@prop()
36+
@Field({ nullable: true })
37+
isBlocked?: boolean
38+
39+
@prop()
40+
@Field({ nullable: true })
41+
isPhoneVerified?: boolean
42+
43+
@prop()
44+
@Field({ nullable: true })
45+
isOnboarded?: boolean
46+
47+
@prop()
48+
@Field(() => Date)
49+
createdAt: Date
50+
51+
@prop()
52+
@Field(() => Date)
53+
updatedAt: Date
54+
}
55+
56+
export default new User().getModelForClass(User, {
57+
schemaOptions: { timestamps: true },
58+
})
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import {
2+
Arg,
3+
Resolver,
4+
Query,
5+
Authorized,
6+
Mutation,
7+
Field,
8+
InputType,
9+
ObjectType,
10+
FieldResolver,
11+
Root,
12+
ID,
13+
Ctx,
14+
} from 'type-graphql'
15+
import { Context } from '../common/context'
16+
import { UserService } from './UserService'
17+
import { User } from './UserEntity'
18+
import './enums'
19+
20+
@Resolver(User)
21+
export default class UserResolver {
22+
private readonly service: UserService
23+
24+
constructor() {
25+
this.service = new UserService()
26+
}
27+
28+
@Query(returns => User)
29+
@Authorized()
30+
async me(@Ctx() ctx: Context) {
31+
if (ctx.userId) {
32+
return await this.service.findOneById(ctx.userId)
33+
}
34+
}
35+
36+
@Query(returns => User)
37+
@Authorized()
38+
async getUser(@Arg('userId', returns => ID) userId: string) {
39+
return await this.service.findOneById(userId)
40+
}
41+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { ModelType } from 'typegoose'
2+
import UserModal, { User } from './UserEntity'
3+
4+
export class UserService {
5+
private readonly modal: ModelType<User>
6+
7+
constructor() {
8+
this.modal = UserModal
9+
}
10+
11+
async find(selector?: Partial<User>) {
12+
return this.modal.find(selector)
13+
}
14+
15+
async findOneById(_id: string) {
16+
return this.modal.findOne({ _id })
17+
}
18+
19+
async remove(_id: string) {
20+
let entityToRemove = await this.modal.findOne(_id)
21+
await this.modal.remove(entityToRemove)
22+
}
23+
24+
async count(entity: any) {
25+
return this.modal.count(entity)
26+
}
27+
}

server/src/modules/user/accounts.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import MongoDBInterface from '@accounts/mongo'
2+
import { AccountsModule } from '@accounts/graphql-api'
3+
import { DatabaseManager } from '@accounts/database-manager'
4+
import { AccountsServer } from '@accounts/server'
5+
import { AccountsPassword } from '@accounts/password'
6+
import { ACCOUNTS_SECRET } from '../common/consts'
7+
8+
export const accountsPassword = new AccountsPassword({
9+
validateNewUser: user => {
10+
console.log('validateNewUser', user)
11+
12+
return user
13+
},
14+
})
15+
16+
export const setUpAccounts = (connection: any) => {
17+
const userStorage = new MongoDBInterface(connection)
18+
19+
const accountsDb = new DatabaseManager({
20+
sessionStorage: userStorage,
21+
userStorage,
22+
})
23+
24+
const accountsServer = new AccountsServer(
25+
{ db: accountsDb, tokenSecret: ACCOUNTS_SECRET },
26+
{
27+
password: accountsPassword,
28+
}
29+
)
30+
31+
const accountsGraphQL = AccountsModule.forRoot({
32+
accountsServer,
33+
})
34+
35+
return { accountsGraphQL, accountsServer }
36+
}
37+
38+
export const userTypeDefs = `
39+
# Our custom fields to add to the user
40+
extend input CreateUserInput {
41+
profile: CreateUserProfileInput!
42+
roles: [String!]
43+
}
44+
input CreateUserProfileInput {
45+
firstName: String!
46+
lastName: String!
47+
}
48+
`

0 commit comments

Comments
 (0)