TeamsModule moved to old repos
This commit is contained in:
1 parent
2814d4307b
commit
8bf22b6bde
210 files changed
No files matched your search
@@ -0,0 +1,124 @@
|
||||
import http from 'http'
|
||||
import { ApolloServer, makeExecutableSchema } from 'apollo-server-express'
|
||||
import { applyMiddleware as applyGraphQLMiddleware } from 'graphql-middleware'
|
||||
import { moduleAuth } from './middleware'
|
||||
import { logger } from './logger'
|
||||
|
||||
export default function createApolloServer(
|
||||
app,
|
||||
{
|
||||
// Main options
|
||||
graphqlEndpoint = '',
|
||||
typeDefs = {},
|
||||
resolvers = {},
|
||||
schemaDirectives = {},
|
||||
directiveResolvers = {},
|
||||
graphqlMiddlewares = [],
|
||||
dataSources = () => ({}),
|
||||
context = () => ({}),
|
||||
// Subscriptions
|
||||
subscriptionsEndpoint = '',
|
||||
wsMiddlewares = [],
|
||||
// Mocks
|
||||
mocks,
|
||||
// Apollo Engine
|
||||
engineKey = '',
|
||||
// HTTP options
|
||||
cors = true,
|
||||
timeout = 120000,
|
||||
// Extra options for Apollo Server
|
||||
apolloServerOptions = {},
|
||||
}
|
||||
) {
|
||||
const schema = makeExecutableSchema({
|
||||
typeDefs,
|
||||
resolvers,
|
||||
schemaDirectives,
|
||||
directiveResolvers,
|
||||
resolverValidationOptions: {
|
||||
requireResolversForResolveType: false,
|
||||
},
|
||||
})
|
||||
|
||||
// Apollo server options
|
||||
const options = {
|
||||
...apolloServerOptions,
|
||||
schema: applyGraphQLMiddleware(schema, ...graphqlMiddlewares),
|
||||
tracing: true,
|
||||
cacheControl: true,
|
||||
introspection: true,
|
||||
playground: true,
|
||||
engine: engineKey ? { apiKey: engineKey } : false,
|
||||
dataSources,
|
||||
// Resolvers context in POST requests
|
||||
context: async ({ req, connection }) => {
|
||||
let contextData
|
||||
try {
|
||||
if (connection) {
|
||||
contextData = { ...connection.context }
|
||||
} else {
|
||||
contextData = await context({ req, request: req })
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
throw err
|
||||
}
|
||||
|
||||
return contextData
|
||||
},
|
||||
// Resolvers context in WebSocket requests
|
||||
subscriptions: {
|
||||
path: subscriptionsEndpoint,
|
||||
onConnect: async (connection, websocket) => {
|
||||
const { authorization, userid } = connection
|
||||
|
||||
let contextData = {}
|
||||
try {
|
||||
// Simulate `req` object for auth
|
||||
const req = { headers: { authorization, userid } }
|
||||
|
||||
// Call all middlewares in order and modify `req`
|
||||
await new Promise((resolve, reject) =>
|
||||
wsMiddlewares.reduceRight(
|
||||
(acc, m) => err => (err ? reject(err) : m(req, null, acc)),
|
||||
err => (err ? reject(err) : resolve())
|
||||
)()
|
||||
)
|
||||
|
||||
contextData = await context({
|
||||
connection,
|
||||
websocket,
|
||||
request: req,
|
||||
req,
|
||||
})
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
}
|
||||
|
||||
return contextData
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Apollo Server
|
||||
const server = new ApolloServer(options)
|
||||
|
||||
// Express middleware
|
||||
server.applyMiddleware({
|
||||
moduleAuth,
|
||||
app,
|
||||
cors,
|
||||
path: graphqlEndpoint,
|
||||
// gui: {
|
||||
// endpoint: graphqlEndpoint,
|
||||
// subscriptionEndpoint: graphqlSubscriptionsPath,
|
||||
// },
|
||||
})
|
||||
|
||||
// Create HTTP server and add subscriptions
|
||||
const httpServer = http.createServer(app)
|
||||
httpServer.setTimeout(timeout)
|
||||
server.installSubscriptionHandlers(httpServer)
|
||||
|
||||
return httpServer
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import { SchemaDirectiveVisitor } from 'apollo-server-express'
|
||||
import _get from 'lodash/get'
|
||||
import { logger, AuthenticationError, DatabaseError } from '../logger'
|
||||
|
||||
const memberLocationOnContext = 'request.user'
|
||||
const userIdOnContext = 'request.headers.userid'
|
||||
const bearerAccessToken = 'request.headers.authorization'
|
||||
|
||||
const ctxUserId = ctx => _get(ctx, userIdOnContext)
|
||||
const ctxMember = ctx => _get(ctx, memberLocationOnContext)
|
||||
const ctxToken = ctx => _get(ctx, bearerAccessToken)
|
||||
|
||||
const createMember = async (ctx, idToken, info) => {
|
||||
logger.info(`createMember: ${idToken}`)
|
||||
const member = await ctx.db.mutation.upsertMember({
|
||||
where: {
|
||||
authId: idToken.sub,
|
||||
},
|
||||
create: {
|
||||
authId: idToken.sub,
|
||||
alias: idToken.nickname
|
||||
},
|
||||
update: {
|
||||
authId: idToken.sub,
|
||||
alias: idToken.nickname
|
||||
}
|
||||
}, info)
|
||||
return member
|
||||
}
|
||||
|
||||
const isUserId = async ctx => {
|
||||
logger.info(`isUserId 0: ${ctx.request.headers.userid}`)
|
||||
logger.info(`isUserId 1: ${ctx.request}`)
|
||||
let member = await ctxUserId(ctx)
|
||||
logger.info(member)
|
||||
if (!member) {
|
||||
throw new AuthenticationError()
|
||||
return
|
||||
} else {
|
||||
return member
|
||||
}
|
||||
}
|
||||
|
||||
const isRequestingMemberAlsoOwner = ({ ctx, memberId, type, teamSlug }) =>
|
||||
ctx.db.exists[type]({ slug: teamSlug, owner: memberId })
|
||||
const isRequestingMember = ({ ctx, memberId }) => ctx.db.exists.Member({ authId: memberId })
|
||||
|
||||
|
||||
const getUser = ({ req = {}, request = {} }) => req.user || request.user || null
|
||||
const hasRole = (roles, ctx) => {
|
||||
const { role = '' } = getUser(ctx) || {}
|
||||
return role && roles.includes(role)
|
||||
}
|
||||
|
||||
const assertAuth = ctx => {
|
||||
if (!getUser(ctx)) {
|
||||
throw new AuthenticationError('Access token is missing or expired')
|
||||
}
|
||||
}
|
||||
|
||||
// Directive resolvers (apollo v1 syntax)
|
||||
export const directiveResolvers = {
|
||||
isAuthenticated: async (next, source, args, ctx) => {
|
||||
let result = await isUserId(ctx)
|
||||
logger.info('directive isAuthenticated: ')
|
||||
logger.info(await result)
|
||||
return next(result)
|
||||
},
|
||||
hasRole: (next, source, { roles }, ctx) => {
|
||||
const { role } = isUserId(ctx)
|
||||
logger.info('directive hasRole: ', role)
|
||||
if (roles.includes(role)) {
|
||||
return next()
|
||||
}
|
||||
throw new AuthenticationError('Insufficient permissions')
|
||||
},
|
||||
isOwner: async (next, source, { type }, ctx) => {
|
||||
logger.info('directive isOwner: ', source, type, ctx)
|
||||
logger.info(ctx.request.body.variables)
|
||||
logger.info(type)
|
||||
const { _v0_slug: typeId } = ctx.request.body.variables ? ctx.request.body.variables : { _v0_slug: null }
|
||||
logger.info('typeId')
|
||||
logger.info(typeId)
|
||||
const userId = await isUserId(ctx)
|
||||
const isOwner =
|
||||
type === `Member`
|
||||
? memberId === typeId
|
||||
: await isRequestingMemberAlsoOwner({ ctx, userId, type, typeId })
|
||||
logger.info('isOwner')
|
||||
logger.info(isOwner)
|
||||
if (isOwner) {
|
||||
return next()
|
||||
}
|
||||
throw new AuthenticationError('Insufficient permissions')
|
||||
},
|
||||
isOwnerOrHasRole: async (next, source, { roles, type }, ctx, ...p) => {
|
||||
const userId = await isUserId(ctx)
|
||||
logger.info('directive isOwnerOrHasRole 1: ', userId, roles, type)
|
||||
if(userId === undefined && role=== undefined) throw new Error(`Not logged in`)
|
||||
if (roles.includes(role)) {
|
||||
return next()
|
||||
}
|
||||
|
||||
const { teamSlug } = ctx.request.body.variables
|
||||
const isOwner = await isRequestingMemberAlsoOwner({
|
||||
ctx,
|
||||
userId,
|
||||
type,
|
||||
teamSlug
|
||||
})
|
||||
logger.info('directive isOwnerOrHasRole 2: ', teamSlug, userId, role, isOwner)
|
||||
if (isOwner) {
|
||||
return next()
|
||||
}
|
||||
|
||||
const hasRole = await isRequestingMember({
|
||||
ctx,
|
||||
userId,
|
||||
type,
|
||||
teamSlug
|
||||
})
|
||||
if(hasRole){
|
||||
return next()
|
||||
}
|
||||
throw new AuthenticationError('Insufficient permissions')
|
||||
}
|
||||
}
|
||||
|
||||
export const defaultFieldResolver = (source, args, contextValue, info) => {
|
||||
// ensure source is a value for which property access is acceptable.
|
||||
if (typeof source === 'object' || typeof source === 'function') {
|
||||
const property = source[info.fieldName]
|
||||
if (typeof property === 'function') {
|
||||
return source[info.fieldName](args, contextValue, info)
|
||||
}
|
||||
return property
|
||||
}
|
||||
}
|
||||
|
||||
// Schema directives (apollo v2 syntax)
|
||||
export const schemaDirectives = Object.keys(directiveResolvers).reduce(
|
||||
(acc, directiveName) => ({
|
||||
...acc,
|
||||
[directiveName]: class extends SchemaDirectiveVisitor {
|
||||
visitFieldDefinition(field) {
|
||||
const resolver = directiveResolvers[directiveName]
|
||||
const originalResolver = field.resolve || defaultFieldResolver
|
||||
const directiveArgs = this.args
|
||||
field.resolve = (...args) => {
|
||||
const [source, , context, info] = args
|
||||
return resolver(
|
||||
async () => originalResolver.apply(field, args),
|
||||
source,
|
||||
directiveArgs,
|
||||
context,
|
||||
info
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
{}
|
||||
)
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ApolloError } from 'apollo-server-express'
|
||||
import { inspect } from 'util'
|
||||
import { logger } from '../logger'
|
||||
|
||||
export {
|
||||
ApolloError,
|
||||
AuthenticationError,
|
||||
ForbiddenError,
|
||||
SyntaxError,
|
||||
UserInputError,
|
||||
ValidationError,
|
||||
} from 'apollo-server-express'
|
||||
|
||||
export const formatError = (error) => {
|
||||
// This can be used to send errors to third
|
||||
// party services like Sentry or Stackdriver
|
||||
logger.info('formatError')
|
||||
logger.error(JSON.parse(JSON.stringify(inspect(error))))
|
||||
const originalError = searchOriginalError(error)
|
||||
logger.error('originalError')
|
||||
logger.error(originalError)
|
||||
const { message, code } = originalError
|
||||
return { message, code}
|
||||
}
|
||||
|
||||
const searchOriginalError = (error) => {
|
||||
if (error.originalError) {
|
||||
logger.error('Original Error')
|
||||
logger.error(error.originalError)
|
||||
return error.originalError
|
||||
}
|
||||
if (error.errors) {
|
||||
logger.error('Errors')
|
||||
logger.error(JSON.parse(JSON.stringify(inspect(error.errors))))
|
||||
return error.errors.map(searchOriginalError)[0]
|
||||
}
|
||||
logger.error('searchOriginalErrors')
|
||||
logger.error(JSON.parse(JSON.stringify(inspect(error))))
|
||||
return error;
|
||||
}
|
||||
|
||||
// Define custom Apollo errors here
|
||||
export class ExternalServiceError extends ApolloError {
|
||||
constructor(message) {
|
||||
super(message, 'EXTERNAL_SERVICE_UNAVAILABLE')
|
||||
|
||||
Object.defineProperty(this, 'name', { value: this.constructor.name })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
"code": 401,
|
||||
"message": "Wait, you're not supposed to be here, go through the gate as everyone else"
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,16 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
const FB_FRAGMENT = gql`{
|
||||
id
|
||||
name
|
||||
slug
|
||||
avatar
|
||||
kind
|
||||
status {
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
}`
|
||||
|
||||
module.exports = FB_FRAGMENT
|
||||
@@ -0,0 +1,40 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
const FB_TEAM_FRAGMENT = gql`{
|
||||
edges{
|
||||
node{
|
||||
id
|
||||
name
|
||||
slug
|
||||
avatar
|
||||
kind
|
||||
status {
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
team {
|
||||
id
|
||||
name
|
||||
slug
|
||||
owner
|
||||
profile {
|
||||
avatar
|
||||
banner
|
||||
description
|
||||
motto
|
||||
updatedAt
|
||||
}
|
||||
members {
|
||||
role
|
||||
email
|
||||
member {
|
||||
alias
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
module.exports = FB_TEAM_FRAGMENT
|
||||
@@ -0,0 +1,34 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
const TEAM_FB_FRAGMENT = gql`{
|
||||
id
|
||||
name
|
||||
slug
|
||||
members {
|
||||
role
|
||||
member {
|
||||
alias
|
||||
authId
|
||||
}
|
||||
status {
|
||||
id
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
fb {
|
||||
id
|
||||
name
|
||||
slug
|
||||
avatar
|
||||
kind
|
||||
status {
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
module.exports = TEAM_FB_FRAGMENT
|
||||
@@ -0,0 +1,53 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
const TEAMS_CONNECTIONS_FRAGMENT = gql`{
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
slug
|
||||
owner
|
||||
status {
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
createdAt
|
||||
profile {
|
||||
avatar
|
||||
banner
|
||||
description
|
||||
motto
|
||||
updatedAt
|
||||
}
|
||||
members {
|
||||
role
|
||||
email
|
||||
member {
|
||||
alias
|
||||
authId
|
||||
}
|
||||
status {
|
||||
id
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
fb {
|
||||
id
|
||||
name
|
||||
slug
|
||||
avatar
|
||||
kind
|
||||
status {
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
module.exports = TEAMS_CONNECTIONS_FRAGMENT
|
||||
@@ -0,0 +1,49 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
const TEAMS_FRAGMENT = gql`{
|
||||
id
|
||||
name
|
||||
slug
|
||||
owner
|
||||
status {
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
createdAt
|
||||
profile {
|
||||
avatar
|
||||
banner
|
||||
description
|
||||
motto
|
||||
updatedAt
|
||||
}
|
||||
members {
|
||||
role
|
||||
email
|
||||
member {
|
||||
alias
|
||||
authId
|
||||
}
|
||||
status {
|
||||
id
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
fb {
|
||||
id
|
||||
name
|
||||
slug
|
||||
avatar
|
||||
kind
|
||||
status {
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
module.exports = TEAMS_FRAGMENT
|
||||
@@ -0,0 +1,17 @@
|
||||
export const createMember = async function (ctx, info, authId) {
|
||||
logger.info('createMember', authId)
|
||||
const member = await ctx.db.mutation.upsertMember({
|
||||
where: {
|
||||
authId: authId,
|
||||
},
|
||||
create: {
|
||||
authId: authId,
|
||||
alias: idToken.nickname
|
||||
},
|
||||
update: {
|
||||
authId: authId,
|
||||
alias: idToken.nickname
|
||||
}
|
||||
}, info)
|
||||
return member
|
||||
}
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,40 @@
|
||||
import axios from 'axios'
|
||||
|
||||
import { logger, DatabaseError } from '../../../logger'
|
||||
|
||||
export const createStrategy = fbSlug => {
|
||||
logger.info('createStrategy')
|
||||
logger.info(fbSlug)
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
return axios.post(process.env.PLATFORM_API_URL, {
|
||||
query: `mutation strategizer_CreateStrategy($fbSlug: String!) {
|
||||
strategizer_CreateStrategy(fbSlug: $fbSlug){
|
||||
id
|
||||
}
|
||||
}`,
|
||||
variables: {
|
||||
fbSlug: fbSlug
|
||||
}
|
||||
}, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
logger.info('createStrategy result')
|
||||
logger.info(res.data)
|
||||
resolve(res.data)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.error('createStrategy err')
|
||||
logger.error(err)
|
||||
reject(err)
|
||||
})
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default createStrategy
|
||||
@@ -0,0 +1,219 @@
|
||||
import axios from 'axios'
|
||||
import { ApolloError } from 'apollo-server-express'
|
||||
|
||||
import { getUser } from '../../../middleware/getMember'
|
||||
import { createStrategy } from './createStrategy'
|
||||
|
||||
import { notifications_sendTeamCreateConfirmation, notifications_sendTeamMemberInvite, notifications_VerifyTeamMemberInviteToken } from '../notifications/sendgrid'
|
||||
|
||||
import TEAMS_CONNECTIONS_FRAGMENT from '../../fragments/TeamsConnectionsFragment'
|
||||
import TEAMS_FRAGMENT from '../../fragments/TeamsFragment'
|
||||
import FB_FRAGMENT from '../../fragments/FBFragment'
|
||||
|
||||
import { logger, AuthenticationError, ServiceUnavailableError, DatabaseError, ConflictError } from '../../../logger'
|
||||
|
||||
export const resolvers = {
|
||||
Mutation: {
|
||||
async sendMemberInviteSG(parent, { email, teamId }, ctx, info) {
|
||||
const team = await ctx.db.query.team({ where: { id: teamId } }, TEAMS_FRAGMENT)
|
||||
const addMember = await ctx.db.mutation.upsertTeamMembers({
|
||||
where:{email: email},
|
||||
create:{role:'MEMBER', email:email, team: {connect: {id: teamId}}, status:{create:{status: 'INVITED', reason: `Invited by ${team.members[0].member.alias}`}}},
|
||||
update:{role: 'MEMBER', email: email, team: {connect: {id: teamId}}, status:{create:{status: 'INVITED', reason: `Invite resent by ${team.members[0].member.alias}`}}}},
|
||||
`{ id }`)
|
||||
const sendInvite = await notifications_sendTeamMemberInvite(email, team)
|
||||
return sendInvite
|
||||
},
|
||||
async verifyTeamInvite(parent, { token }, ctx, info) {
|
||||
let verifiedToken = null
|
||||
let team = null
|
||||
try {
|
||||
verifiedToken = await notifications_VerifyTeamMemberInviteToken(token)
|
||||
team = {
|
||||
email: verifiedToken.email,
|
||||
team: {
|
||||
slug: verifiedToken.team
|
||||
}
|
||||
}
|
||||
logger.info('verifyTeamInvite.token: ', await verifiedToken)
|
||||
} catch (err) {
|
||||
logger.debug('verifyTeamInvite. err: ', err)
|
||||
throw new DatabaseError(err.message)
|
||||
}
|
||||
return team
|
||||
},
|
||||
async createTeam(parent, { name, slug, botName, botSlug }, ctx, info) {
|
||||
logger.info('createTeam ctx.request.user:')
|
||||
logger.info(ctx.request.headers.userid)
|
||||
const authId = ctx.request.headers.userid
|
||||
if (!authId) {
|
||||
throw new AuthenticationError()
|
||||
}
|
||||
const userData = await getUser(authId)
|
||||
logger.info('userData:')
|
||||
logger.info(JSON.stringify(await userData))
|
||||
const alias = await userData.data.users_User.alias
|
||||
const email = await userData.data.users_User.email
|
||||
const avatar = process.env.STORAGE_URL + '/module-teams/module-default/aa-avatar-default.png'
|
||||
const banner = process.env.STORAGE_URL + '/module-teams/module-default/aa-banner-default.png'
|
||||
|
||||
const createTeamUrl = encodeURI(`${slug}/${name}/${alias}/${botSlug}/${botName}`)
|
||||
logger.info('createTeamUrl:')
|
||||
logger.info(JSON.stringify(await createTeamUrl))
|
||||
|
||||
const chartsUrl = process.env.CHARTS_URL + '/AABrowserAPI/newTeam/'
|
||||
|
||||
logger.info(`${chartsUrl}${createTeamUrl}/${authId}`)
|
||||
const createPlatformTeam = await axios.get(`${chartsUrl}${createTeamUrl}/${authId}/${process.env.AAWEB_TEAM_SHARED_SECRET}`)
|
||||
.then((result) => {
|
||||
console.log('createPlatformTeam result:', result.data)
|
||||
if(result.data.message === 'Team Name already taken'){
|
||||
return { error: 'Team already exist on AA Cloud.', code: 409 }
|
||||
}
|
||||
if(result.data.result === 'Fail'){
|
||||
return { error: `${result.data.message} - Creating team on AA Cloud has failed`, code: 404 }
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
logger.info('createPlatformTeam')
|
||||
logger.info(await createPlatformTeam)
|
||||
logger.info('createPlatformTeam Error')
|
||||
logger.error(await createPlatformTeam.error)
|
||||
logger.info('createPlatformTeam Code')
|
||||
logger.error(await createPlatformTeam.code)
|
||||
|
||||
// if (await createPlatformTeam.error && await createPlatformTeam.code === 404) throw new ApolloError(await createPlatformTeam.error, 404)
|
||||
// if (await createPlatformTeam.error && await createPlatformTeam.code === 409) throw new ApolloError(await createPlatformTeam.error, 409)
|
||||
|
||||
const existingMember = await ctx.db.query.member({ where: { authId } }, `{id}`)
|
||||
.catch((err) => {
|
||||
logger.debug(err, 'existingMember error: ')
|
||||
throw new DatabaseError(err)
|
||||
})
|
||||
|
||||
logger.debug('existingMember:')
|
||||
logger.debug(await existingMember)
|
||||
|
||||
let createTeam
|
||||
if(existingMember !== null && existingMember.id !== null){
|
||||
logger.info('createTeam with existingMember:')
|
||||
createTeam = await ctx.db.mutation.createTeam({ data: {name: name, slug: slug, owner: authId, members: {create: {member: {connect: {authId: authId}}, role: 'OWNER', status: { create: { status: 'ACTIVE', reason: `Connected to Team ${name}`}}}}, profile: {create: {avatar: avatar, banner: banner}}, fb: {create: {name: botName, slug: botSlug, kind:'Trading', avatar: avatar, status: {create: {status: 'ACTIVE', reason: "Cloned on team creation"}}}}, status: {create: {status: 'ACTIVE', reason:"Team created"}}} }, TEAMS_FRAGMENT)
|
||||
.catch((err) => {
|
||||
logger.debug(err, 'createTeamMutation error: ')
|
||||
return new DatabaseError(err)
|
||||
})
|
||||
} else {
|
||||
logger.info('createTeam without existingMember:')
|
||||
createTeam = await ctx.db.mutation.createTeam({ data: {name: name, slug: slug, owner: authId, members: {create: {member: {create: {authId: authId, alias: alias, visible: true, status: { create: { status: 'ACTIVE', reason: `Created team ${name}`}}}}, role: 'OWNER', status: { create: { status: 'ACTIVE', reason: `Created with Team ${name}`}}}}, profile: {create: {avatar: avatar, banner: banner}}, fb: {create: {name: botName, slug: botSlug, kind:'Trading', avatar: avatar, status: {create: {status: 'ACTIVE', reason: "Cloned on team creation"}}}}, status: {create: {status: 'ACTIVE', reason:"Team created"}}} }, TEAMS_FRAGMENT)
|
||||
.catch((err) => {
|
||||
logger.debug(err, 'createTeamMutation error: ')
|
||||
return new DatabaseError(err)
|
||||
})
|
||||
|
||||
}
|
||||
let createFB;
|
||||
if(createTeam){
|
||||
createFB = await ctx.db.mutation.createFinancialBeings({ data:{
|
||||
type:'BOT',
|
||||
kind:'Indicator',
|
||||
name:'Simulator ' + botName,
|
||||
slug:'simulator-' + botSlug,
|
||||
avatar:avatar,
|
||||
team: {
|
||||
connect:{
|
||||
id:createTeam.id
|
||||
}
|
||||
}
|
||||
}
|
||||
}, FB_FRAGMENT)
|
||||
.catch((err) => {
|
||||
logger.debug(err, 'Error creating the simulator: ')
|
||||
return new DatabaseError(err)
|
||||
})
|
||||
logger.info('createTeam creating simulator success')
|
||||
logger.info(JSON.stringify(await createFB))
|
||||
let createdStrategy
|
||||
if(await createFB){
|
||||
logger.info('createStrategy:' + 'simulator-' + botSlug)
|
||||
createdStrategy = await createStrategy('simulator-' + botSlug)
|
||||
logger.info('createStrategy:')
|
||||
logger.info(JSON.stringify(await createStrategy))
|
||||
}
|
||||
}
|
||||
/* **temporarily disable team creation confirmation
|
||||
if(await createTeam && await createFB && await createStrategy){
|
||||
notifications_sendTeamCreateConfirmation(email, name, botName)
|
||||
}
|
||||
*/
|
||||
return createTeam
|
||||
},
|
||||
async updateTeamProfile(parent, { slug, owner, description, motto, avatar, banner }, ctx, info) {
|
||||
return ctx.db.mutation.updateTeam({data:{profile: {update: {description: description, motto: motto, avatar: avatar, banner: banner}}}, where:{slug: slug}}, TEAMS_FRAGMENT)
|
||||
.catch((err) => {
|
||||
logger.debug('createTeam error: ', err)
|
||||
throw new DatabaseError(`Team Profile Update Error: ${err}`)
|
||||
})
|
||||
},
|
||||
async updateFB(parent, { fbId, avatar}, ctx, info) {
|
||||
return ctx.db.mutation.updateFinancialBeings({data:{avatar:avatar}, where:{id:fbId}}, info)
|
||||
.catch((err) => {
|
||||
logger.debug('createTeam error: ', err)
|
||||
throw new DatabaseError(`Update Financial Being Error: ${err}`)
|
||||
})
|
||||
},
|
||||
async deleteTeam(parent, { slug, botSlug }, ctx, info) {
|
||||
logger.info('deleteTeam ctx.request.user:')
|
||||
logger.info(ctx.request.headers.userid)
|
||||
const authId = ctx.request.headers.userid
|
||||
if (!authId) {
|
||||
throw new AuthenticationError()
|
||||
}
|
||||
logger.info(authId)
|
||||
logger.info('getUser')
|
||||
logger.info(getUser)
|
||||
|
||||
return getUser(authId)
|
||||
.then(async result => {
|
||||
logger.info('deleteTeam axios:')
|
||||
logger.info(await result)
|
||||
|
||||
const alias = await result.data.users_User.alias
|
||||
// const email = result.data.users_User.email
|
||||
const deleteTeamUrl = encodeURI(`${slug}/${alias}/${botSlug}`)
|
||||
logger.info('deleteTeamUrl:')
|
||||
logger.info(JSON.stringify(await deleteTeamUrl))
|
||||
|
||||
const chartsUrl = process.env.CHARTS_URL + '/AABrowserAPI/deleteTeam/'
|
||||
|
||||
logger.info(`${chartsUrl}${deleteTeamUrl}/${authId}`)
|
||||
const deletePlatformTeam = await axios.get(`${chartsUrl}${deleteTeamUrl}/${authId}/${process.env.AAWEB_DELETE_TEAM_SHARED_SECRET}`)
|
||||
.then((result) => {
|
||||
console.log('deletePlatformTeam result:', result.data)
|
||||
if(result.data.result === 'Fail'){
|
||||
throw new DatabaseError(result.data.message)
|
||||
}
|
||||
return result
|
||||
})
|
||||
.catch(err =>{
|
||||
logger.debug('deletePlatformTeam err:')
|
||||
logger.debug(err)
|
||||
throw new DatabaseError(err)
|
||||
})
|
||||
console.log('deletePlatformTeam returned: ', await deletePlatformTeam)
|
||||
|
||||
return ctx.db.mutation.deleteTeam({ where: { slug } }, info)
|
||||
.catch((res) => {
|
||||
logger.info('deleteTeam error: ', res)
|
||||
const errors = res.graphQLErrors.map((error) => {
|
||||
throw new DatabaseError(error.message)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.debug('deleteTeam error: ')
|
||||
throw new DatabaseError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { logger } from '../../../logger'
|
||||
|
||||
import FB_FRAGMENT from '../../fragments/FBFragment'
|
||||
import FB_TEAM_FRAGMENT from '../../fragments/FBTeamFragment'
|
||||
|
||||
import { isDefined, isNull } from '../../../utils'
|
||||
|
||||
export const fbByFbId = async (parent, { fbId }, ctx, info) => {
|
||||
logger.info(`fbByFbId: ${fbId}`)
|
||||
let FB = await ctx.db.query.financialBeings({where: {id: fbId}}, FB_FRAGMENT)
|
||||
logger.info(`fbByFbId response: ${JSON.stringify(await FB)}`)
|
||||
return FB
|
||||
}
|
||||
|
||||
export const fbByFbSlug = async (parent, { fbSlug }, ctx, info) => {
|
||||
logger.info(`fbByFbSlug: ${fbSlug}`)
|
||||
let FB = await ctx.db.query.financialBeingses({where: {slug: fbSlug}, first:1}, FB_FRAGMENT)
|
||||
logger.info(`fbByFbSlug response: ${JSON.stringify(await FB)}`)
|
||||
return FB[0]
|
||||
}
|
||||
|
||||
export const fbByOwner = async (parent, { fbId, fbSlug }, ctx, info) => {
|
||||
logger.info('teamsByOwner')
|
||||
const authId = ctx.request.headers.userid
|
||||
if (!authId) {
|
||||
throw new AuthenticationError()
|
||||
return
|
||||
}
|
||||
if ((isDefined(fbId) && !isNull(fbId)) || (isDefined(fbSlug) && !isNull(fbSlug))){
|
||||
return ctx.db.query.financialBeingsesConnection({where: {AND:[{team:{members_every:{member:{authId: authId}}}},{OR:[{slug:fbSlug},{id:fbId}]}]}}, FB_TEAM_FRAGMENT)
|
||||
} else {
|
||||
return ctx.db.query.financialBeingsesConnection({where: {team:{members_every:{member:{authId: authId}}}}}, FB_TEAM_FRAGMENT)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { logger, AuthenticationError, DatabaseError } from '../../../logger'
|
||||
|
||||
export const member = async (parent, arg, ctx, info) => {
|
||||
logger.info(' --> resolver.member')
|
||||
logger.info(ctx.request.headers.userid)
|
||||
const authId = ctx.request.headers.userid
|
||||
if (!authId) {
|
||||
throw new AuthenticationError()
|
||||
}
|
||||
return ctx.db.query.member({ where: { authId } }, info)
|
||||
.catch(err => {
|
||||
logger.debug('resolver.member error: ')
|
||||
logger.debug(err)
|
||||
throw new DatabaseError(`Member Data Error: ${err.message}`)
|
||||
})
|
||||
}
|
||||
|
||||
export const owner = async (parent, args, ctx, info) => {
|
||||
logger.info('resolver.query.owner ctx: ')
|
||||
logger.info(ctx.request.headers.userid)
|
||||
const authId = cctx.request.headers.userid
|
||||
if (!authId) {
|
||||
throw new AuthenticationError()
|
||||
}
|
||||
return ctx.db.query.member({ where: { authId: authId } }, info)
|
||||
.catch(err => {
|
||||
logger.debug('resolver.owner error: ')
|
||||
logger.debug(err)
|
||||
throw new DatabaseError(`Member Data Error: ${err.message}`)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { logger, AuthenticationError, DatabaseError } from '../../../logger'
|
||||
|
||||
import TEAMS_CONNECTIONS_FRAGMENT from '../../fragments/TeamsConnectionsFragment'
|
||||
import TEAMS_FRAGMENT from '../../fragments/TeamsFragment'
|
||||
|
||||
export const teams = async (parent, args, ctx, info) => {
|
||||
logger.info('teams')
|
||||
return ctx.db.query.teamsConnection({}, TEAMS_CONNECTIONS_FRAGMENT)
|
||||
}
|
||||
|
||||
export const teamsByIds = async (parent, { teamIds }, ctx, info) => {
|
||||
logger.info('teamsByIds')
|
||||
logger.info(teamIds)
|
||||
return ctx.db.query.teams({where: { id_in: teamIds } }, TEAMS_FRAGMENT)
|
||||
}
|
||||
|
||||
export const teamById = async (parent, { teamId }, ctx, info) => {
|
||||
logger.info('teamById')
|
||||
logger.info(teamId)
|
||||
return ctx.db.query.team({ where: { id: teamId } }, TEAMS_FRAGMENT)
|
||||
}
|
||||
|
||||
export const teamByName = async (parent, { name }, ctx, info) => {
|
||||
return ctx.db.query.team({ where: { name: name } }, TEAMS_FRAGMENT)
|
||||
}
|
||||
|
||||
export const teamBySlug= async (parent, { slug }, ctx, info) => {
|
||||
logger.info('teamsBySlug')
|
||||
logger.info(`slug: ${slug}`)
|
||||
return ctx.db.query.team({ where: { slug: slug } }, TEAMS_FRAGMENT)
|
||||
}
|
||||
|
||||
export const teamWithRole = async (parent, { teamId, role }, ctx, info) => {
|
||||
return ctx.db.query.teamsConnection({where: {AND: [{id: teamId},{members_some:{role: role}}]}, first:1}, info)
|
||||
}
|
||||
|
||||
export const teamAuthorization = async (parent, { teamId, role }, ctx, info) => {
|
||||
const authId = ctx.request.headers.userid
|
||||
if (!authId) {
|
||||
throw new AuthenticationError()
|
||||
return
|
||||
}
|
||||
if(role == undefined || role == null){
|
||||
return ctx.db.query.teamsConnection({where:{ AND:{id:teamId,members_some:{member:{authId:authId}, role_in:['OWNER','ADMIN']}}}, first:1}, info)
|
||||
} else {
|
||||
return ctx.db.query.teamsConnection({where:{ AND:{id:teamId,members_some:{member:{authId:authId}, role:role}}}, first:1}, info)
|
||||
}
|
||||
}
|
||||
|
||||
export const teamsByOwner = async (parent, args, ctx, info) => {
|
||||
logger.info('teamsByOwner')
|
||||
const authId = ctx.request.headers.userid
|
||||
if (!authId) {
|
||||
throw new AuthenticationError()
|
||||
return
|
||||
}
|
||||
return ctx.db.query.teams({where: { owner: authId }, orderBy:'createdAt_DESC'}, TEAMS_FRAGMENT)
|
||||
}
|
||||
|
||||
export const teamsByRole = async (parent, args, ctx, info) => {
|
||||
const authId = ctx.request.headers.userid
|
||||
if (!authId) {
|
||||
throw new AuthenticationError()
|
||||
}
|
||||
let teamAdmin
|
||||
try {
|
||||
teamAdmin= await ctx.db.query.teams({where: {members_some: {AND: [{member: {authId: authId}, role_not:'MEMBER'}]}}}, TEAMS_FRAGMENT)
|
||||
} catch (err) {
|
||||
logger.debug('teamsByRole error: ')
|
||||
logger.debug(err)
|
||||
const errors = err.graphQLErrors.map((error) => {
|
||||
throw new DatabaseError(error.message)
|
||||
})
|
||||
}
|
||||
logger.info('teamsByRole teamAdmin: ', teamAdmin)
|
||||
return teamAdmin
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { logger } from '../../../logger'
|
||||
|
||||
import TEAM_FB_FRAGMENT from '../../fragments/TeamFBFragment'
|
||||
|
||||
export const fbByTeamMember = async (parent, args, ctx, info) => {
|
||||
logger.info('fbByTeamMember: ', args, ctx.request, ctx.request.res.req.user)
|
||||
const authId = ctx.request.headers.userid
|
||||
if (!authId) {
|
||||
throw new AuthenticationError()
|
||||
}
|
||||
let teamMemberFB = await ctx.db.query.teams({where: {members_some: {AND: [{member: {authId: authId}}]}}, orderBy: 'createdAt_DESC'}, TEAM_FB_FRAGMENT)
|
||||
logger.info('fbByTeamMember response: ', await teamMemberFB[0])
|
||||
return teamMemberFB[0]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { logger } from '../../../logger'
|
||||
|
||||
import { member, owner } from './Member'
|
||||
import { teams, teamById, teamsByIds, teamByName, teamBySlug, teamWithRole, teamAuthorization, teamsByOwner, teamsByRole } from './Team'
|
||||
import{ fbByTeamMember } from './TeamMember'
|
||||
import{ fbByFbId, fbByFbSlug, fbByOwner } from './FinancialBeing'
|
||||
|
||||
export const resolvers = {
|
||||
Query: {
|
||||
member: (parent, arg, ctx, info) => member(parent, arg, ctx, info),
|
||||
teams: (parent, arg, ctx, info) => teams(parent, arg, ctx, info),
|
||||
teamById: (parent, arg, ctx, info) => teamById(parent, arg, ctx, info),
|
||||
teamsByIds: (parent, arg, ctx, info) => teamsByIds(parent, arg, ctx, info),
|
||||
teamByName: (parent, arg, ctx, info) => teamByName(parent, arg, ctx, info),
|
||||
teamBySlug: (parent, arg, ctx, info) => teamBySlug(parent, arg, ctx, info),
|
||||
teamWithRole: (parent, arg, ctx, info) => teamWithRole(parent, arg, ctx, info),
|
||||
teamAuthorization: (parent, arg, ctx, info) => teamAuthorization(parent, arg, ctx, info),
|
||||
teamsByOwner: (parent, arg, ctx, info) => teamsByOwner(parent, arg, ctx, info),
|
||||
teamsByRole: (parent, arg, ctx, info) => teamsByRole(parent, arg, ctx, info),
|
||||
owner: (parent, arg, ctx, info) => owner(parent, arg, ctx, info),
|
||||
fbByTeamMember: (parent, arg, ctx, info) => fbByTeamMember(parent, arg, ctx, info),
|
||||
fbByOwner: (parent, arg, ctx, info) => fbByOwner(parent, arg, ctx, info),
|
||||
fbByFbId: (parent, arg, ctx, info) => fbByFbId(parent, arg, ctx, info),
|
||||
fbByFbSlug: (parent, arg, ctx, info) => fbByFbSlug(parent, arg, ctx, info)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import merge from 'lodash/merge'
|
||||
|
||||
import { resolvers as queryResolvers } from './Query'
|
||||
import { resolvers as mutationResolvers } from './Mutation'
|
||||
|
||||
export const resolvers = merge(queryResolvers, mutationResolvers)
|
||||
@@ -0,0 +1,125 @@
|
||||
var jwt = require('jsonwebtoken')
|
||||
var axios = require('axios')
|
||||
|
||||
import { logger } from '../../../logger'
|
||||
|
||||
export const notifications_sendTeamMemberInvite = (email, team) => {
|
||||
logger.info('notifications_sendTeamMemberInvite')
|
||||
logger.info(email)
|
||||
logger.info(teamName)
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
return axios.post(process.env.PLATFORM_API_URL, {
|
||||
query: `mutation notifications_Teams_SendTeamMemberInvite($email:String!, $teamName:String!){
|
||||
notifications_Teams_SendTeamMemberInvite(email:$email, teamName:$teamName)
|
||||
}`,
|
||||
variables: {
|
||||
email: email,
|
||||
teamName: team.name
|
||||
}
|
||||
}, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
logger.info('sendTeamMemberInvite result')
|
||||
logger.info(res.data)
|
||||
resolve(res.data)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.info('sendTeamMemberInvite err')
|
||||
logger.info(err)
|
||||
reject(err)
|
||||
})
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const notifications_sendTeamCreateConfirmation = (email, teamName, botName) => {
|
||||
logger.info('notifications_sendTeamCreateConfirmation')
|
||||
logger.info(email)
|
||||
logger.info(teamName)
|
||||
logger.info(botName)
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
return axios.post(process.env.PLATFORM_API_URL, {
|
||||
query: `mutation notifications_sendTeamCreateConfirmation($email: String!, $teamName: String!, $botName: String!){
|
||||
notifications_Teams_SendTeamCreateConfirmation(email: $email, teamName: $teamName, botName: $botName)
|
||||
}`,
|
||||
variables: {
|
||||
email: email,
|
||||
teamName: teamName,
|
||||
botName: botName
|
||||
}
|
||||
}, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
logger.info('sendTeamCreateConfirmation result')
|
||||
logger.info(res.data)
|
||||
resolve(res.data)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.info('sendTeamCreateConfirmation err')
|
||||
logger.info(err)
|
||||
reject(err)
|
||||
})
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const notifications_VerifyTeamMemberInviteToken = (token) => {
|
||||
logger.info('notifications_VerifyTeamMemberInviteToken')
|
||||
logger.info(token)
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
return axios.post(process.env.PLATFORM_API_URL, {
|
||||
query: `mutation notifications_VerifyTeamMemberInviteToken($token: String!){
|
||||
notifications_Teams_VerifyTeamMemberInviteToken(token: $token)
|
||||
}`,
|
||||
variables: {
|
||||
token: token
|
||||
}
|
||||
}, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
logger.info('VerifyTeamMemberInviteToken result')
|
||||
logger.info(res.data)
|
||||
resolve(res.data)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.info('VerifyTeamMemberInviteToken err')
|
||||
logger.info(err)
|
||||
reject(err)
|
||||
})
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const verifyInviteToken = async function(token) {
|
||||
const API_KEY = process.env.SG_APIKEY
|
||||
let verifiedToken
|
||||
try {
|
||||
verifiedToken = await jwt.verify(token, API_KEY, {maxAge: '7d'})
|
||||
} catch(err) {
|
||||
if (err.name === "TokenExpiredError"){
|
||||
throw new Error ("Error: Token Expired. Please request new invitation or apply to the team again")
|
||||
} else {
|
||||
throw new Error (`Error: Token error - ${err.message}`)
|
||||
}
|
||||
}
|
||||
logger.info(`verifyInviteToken: ${verifiedToken}`)
|
||||
return verifiedToken
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
# import Query.TeamConnection, Query.FinancialBeingsConnection, Team, FinancialBeings from "../generated/prisma.graphql"
|
||||
|
||||
directive @isAuthenticated on QUERY | FIELD | MUTATION | FIELD_DEFINITION
|
||||
directive @hasRole(roles: [String]) on QUERY | FIELD | MUTATION | FIELD_DEFINITION
|
||||
directive @isOwner(type: String) on QUERY | MUTATION | FIELD_DEFINITION
|
||||
directive @isOwnerOrHasRole(type: String, roles: [String]) on QUERY | MUTATION | FIELD_DEFINITION
|
||||
|
||||
type Query {
|
||||
teams: TeamConnection
|
||||
teamDetails: [Team!]! @isAuthenticated
|
||||
teamByName(name: String!): Team
|
||||
teamBySlug(slug: String!): Team
|
||||
teamById(teamId: String!): Team
|
||||
teamsByIds(teamIds: [ID!]): [Team]
|
||||
teamWithRole(teamId: String!, role: String!): Team
|
||||
teamAuthorization(teamId: String!, role: String): Team
|
||||
teamsByOwner: [Team!]! @isAuthenticated
|
||||
teamsByRole: [Team!]! @isAuthenticated
|
||||
fbByTeamMember: Team @isAuthenticated
|
||||
fbByOwner(fbId: String, fbSlug: String): FinancialBeingsConnection @isAuthenticated
|
||||
fbByFbId(fbId: String!): FinancialBeings
|
||||
fbByFbSlug(fbSlug: String!): FinancialBeings
|
||||
owner: Member @isAuthenticated
|
||||
member: Member @isAuthenticated
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
authenticate(idToken: String!): Member
|
||||
verifyTeamInvite(token: String!): TeamMembers
|
||||
createTeam(name: String!, slug: String!, botName: String!, botSlug: String!): Team @isAuthenticated
|
||||
updateTeamProfile(slug: String!, description: String, motto: String, avatar: String, banner: String): Team @isAuthenticated
|
||||
updateFB(fbId: String!, avatar: String): FinancialBeings @isAuthenticated
|
||||
deleteTeam(slug: String!, botSlug: String!): Team @isAuthenticated @isOwner(type: "Team")
|
||||
sendMemberInviteSG(email: String!, teamId: String!): String
|
||||
confirmMember(token: String!): String
|
||||
removeTeamMember(teamId: String!, memberId: String!): String
|
||||
}
|
||||
|
||||
fragment MemberInfo on Member {
|
||||
id
|
||||
authId
|
||||
alias
|
||||
visible
|
||||
status {
|
||||
...MemberStatusInfo
|
||||
}
|
||||
}
|
||||
|
||||
fragment MemberStatusInfo on MemberStatus {
|
||||
status
|
||||
reason
|
||||
createdAt
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
require("@babel/register");
|
||||
require("@babel/polyfill");
|
||||
import express from 'express'
|
||||
import createApolloServer from './ApolloServer'
|
||||
import { formatError } from './errors'
|
||||
import {
|
||||
Prisma,
|
||||
extractFragmentReplacements,
|
||||
forwardTo
|
||||
} from 'prisma-binding'
|
||||
import { makeExecutableSchema } from 'graphql-tools'
|
||||
import { importSchema } from 'graphql-import'
|
||||
import parser from 'fast-xml-parser'
|
||||
import axios from 'axios'
|
||||
|
||||
import { resolvers } from './graphql/resolvers'
|
||||
|
||||
import { schemaDirectives } from './directives'
|
||||
|
||||
import pubsub from './pubsub'
|
||||
import { logger } from './logger'
|
||||
|
||||
import wrongPreshared from './errors/notAllowed.json'
|
||||
|
||||
const GRAPHQL_ENDPOINT = '/graphql'
|
||||
const GRAPHQL_SUBSCRIPTIONS = '/graphql'
|
||||
const PORT = process.env.PORT
|
||||
const NODE_ENV = process.env.NODE_ENV
|
||||
const SCHEMA_PATH = process.env.SCHEMA_PATH
|
||||
|
||||
const db = new Prisma({
|
||||
fragmentReplacements: extractFragmentReplacements(resolvers),
|
||||
typeDefs: importSchema(SCHEMA_PATH),
|
||||
endpoint: process.env.PRISMA_ENDPOINT,
|
||||
secret: process.env.PRISMA_SECRET,
|
||||
debug: false
|
||||
})
|
||||
|
||||
const app = express()
|
||||
|
||||
app.post(GRAPHQL_ENDPOINT, (req, res, next) => {
|
||||
if (req.headers.preshared === process.env.PRESHARED_GATEWAY_KEY) {
|
||||
next();
|
||||
} else {
|
||||
res.send(wrongPreshared);
|
||||
}
|
||||
});
|
||||
|
||||
app.post(GRAPHQL_ENDPOINT)
|
||||
|
||||
const corsOptions = {
|
||||
"origin": "*",
|
||||
"methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
|
||||
"preflightContinue": false,
|
||||
"optionsSuccessStatus": 204
|
||||
}
|
||||
|
||||
const options = {
|
||||
port: 4001,
|
||||
cors: corsOptions,
|
||||
endpoint: '/graphql',
|
||||
subscriptions: '/graphql',
|
||||
playground: '/graphiql'
|
||||
}
|
||||
|
||||
const server = createApolloServer(app, {
|
||||
graphqlEndpoint: GRAPHQL_ENDPOINT,
|
||||
subscriptionsEndpoint: GRAPHQL_SUBSCRIPTIONS,
|
||||
apolloServerOptions: { formatError },
|
||||
typeDefs: importSchema('src/graphql/schema.graphql'),
|
||||
resolvers,
|
||||
resolverValidationOptions: { requireResolversForResolveType: false },
|
||||
schemaDirectives,
|
||||
context: req => ({
|
||||
...req ,
|
||||
token: req.headers ? req.headers : undefined,
|
||||
user: req.user ? req.user : undefined,
|
||||
userId: (req.headers && req.headers.userid) ? req.headers.userid : undefined,
|
||||
db,
|
||||
pubsub
|
||||
})
|
||||
})
|
||||
|
||||
server.listen({ port: PORT }, () => {
|
||||
logger.info(
|
||||
`🚀 GraphQL Server is running on http://localhost:${PORT}${GRAPHQL_ENDPOINT} in "${NODE_ENV}" mode\n`
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,24 @@
|
||||
export class AuthenticationError extends Error {
|
||||
code = 401
|
||||
message = 'Not Authenticated - you have to be authenticated to perform this action'
|
||||
}
|
||||
|
||||
export class DatabaseError extends Error {
|
||||
code = 404
|
||||
message = `Resource Error: ${this.message}`
|
||||
}
|
||||
|
||||
export class ApolloError extends Error {
|
||||
code = 409
|
||||
message = `Resource already exists: ${this.message}`
|
||||
}
|
||||
|
||||
export class WrongArgumentsError extends Error {
|
||||
code = 400
|
||||
message = 'Wrong arguments : ' + this.message
|
||||
}
|
||||
|
||||
export class ServiceUnavailableError extends Error {
|
||||
code = 503
|
||||
message = 'At least one service is unresponding ' + this.message
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { createLogger, format, transports } from 'winston'
|
||||
import { inspect } from 'util'
|
||||
|
||||
import { AuthenticationError, ApolloError, DatabaseError, WrongArgumentsError, ServiceUnavailableError, ConflictError } from './errors'
|
||||
|
||||
class LoggerService {
|
||||
constructor() {
|
||||
const winstonTransports = [
|
||||
new transports.Console({
|
||||
format: format.combine(
|
||||
format.colorize(),
|
||||
format.timestamp({ format: 'YY/MM/DD HH:mm' }),
|
||||
format.printf(
|
||||
info => `${info.timestamp} - ${info.level}: ${JSON.parse(JSON.stringify(inspect(info.message)))}`
|
||||
)
|
||||
),
|
||||
}),
|
||||
]
|
||||
|
||||
const logger = createLogger({
|
||||
level: 'info',
|
||||
transports: winstonTransports,
|
||||
exceptionHandlers: winstonTransports,
|
||||
})
|
||||
|
||||
return new Proxy(this, {
|
||||
get: (target, propKey) => logger[propKey],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const logger = new LoggerService() // Default logger
|
||||
export { LoggerService, AuthenticationError, ApolloError, ConflictError, DatabaseError, WrongArgumentsError, ServiceUnavailableError }
|
||||
@@ -0,0 +1,56 @@
|
||||
import axios from 'axios'
|
||||
|
||||
import { logger, DatabaseError } from '../logger'
|
||||
|
||||
export const getMember = async (req, res, next, db) => {
|
||||
if (!req.headers.userId) return next()
|
||||
logger.info('getMember ')
|
||||
logger.info(req.headers.userId)
|
||||
try {
|
||||
const member = await getUser(req.headers.userId)
|
||||
req.user = await member
|
||||
next()
|
||||
} catch (err) {
|
||||
next()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const getUser = authId => {
|
||||
logger.info('getUser MiddleWare')
|
||||
logger.info(authId)
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
return axios.post(process.env.PLATFORM_API_URL, {
|
||||
query: `query users_User($userId: ID!) {
|
||||
users_User(id: $userId){
|
||||
id
|
||||
alias
|
||||
email
|
||||
}
|
||||
}`,
|
||||
variables: {
|
||||
userId: authId
|
||||
}
|
||||
}, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
logger.info('getUser result')
|
||||
logger.info(res.data)
|
||||
resolve(res.data)
|
||||
})
|
||||
.catch(err => {
|
||||
logger.info('getUser err')
|
||||
logger.info(err)
|
||||
reject(err)
|
||||
})
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default getUser
|
||||
@@ -0,0 +1,3 @@
|
||||
import moduleAuth from './moduleAuth'
|
||||
|
||||
export { moduleAuth }
|
||||
@@ -0,0 +1,24 @@
|
||||
const jwt = require('express-jwt')
|
||||
const jwksRsa = require('jwks-rsa')
|
||||
// Authentication middleware. When used, the
|
||||
// if the access token exists, it be verified against
|
||||
// the Auth0 JSON Web Key Set
|
||||
const checkJwt = jwt({
|
||||
// Dynamically provide a signing key
|
||||
// based on the kid in the header and
|
||||
// the signing keys provided by the JWKS endpoint.
|
||||
secret: jwksRsa.expressJwtSecret({
|
||||
cache: true,
|
||||
rateLimit: true,
|
||||
jwksRequestsPerMinute: 1,
|
||||
jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`
|
||||
}),
|
||||
|
||||
// Validate the audience and the issuer.
|
||||
credentialsRequired: false,
|
||||
audience: process.env.AUTH0_AUDIENCE,
|
||||
issuer: process.env.AUTH0_ISSUER,
|
||||
algorithms: [`RS256`]
|
||||
})
|
||||
|
||||
module.exports = checkJwt
|
||||
@@ -0,0 +1,13 @@
|
||||
import { logger } from '../logger'
|
||||
|
||||
export const moduleAuth = async (req, res, next, db) => {
|
||||
if (req.headers.preshared === process.env.PRESHARED_GATEWAY_KEY) {
|
||||
logger.info('Module authenticated')
|
||||
next();
|
||||
} else {
|
||||
logger.error('Module authentication error')
|
||||
res.send(wrongPreshared);
|
||||
}
|
||||
}
|
||||
|
||||
export default moduleAuth
|
||||
@@ -0,0 +1,3 @@
|
||||
import { PubSub } from 'graphql-subscriptions'
|
||||
|
||||
export default new PubSub()
|
||||
@@ -0,0 +1,191 @@
|
||||
const Azure = require("@azure/storage-blob")
|
||||
const azureAccount = process.env.AZURE_STORAGE_ACCOUNT
|
||||
const azureKey = process.env.AZURE_STORAGE_ACCESS_KEY
|
||||
const azureStorageUrl = process.env.AZURE_STORAGE_URL
|
||||
|
||||
/* Create Azure Storage pipeline
|
||||
* @accountName: Azure Blob Storage account name (required) - Environment variable
|
||||
* @accountKey: Azure Blob Storage storage key (required) - Environment variable
|
||||
*/
|
||||
const createStoragePipline = async (accountName, accountKey) => {
|
||||
return Azure.StorageURL.newPipeline(
|
||||
new Azure.SharedKeyCredential(accountName, accountKey)
|
||||
)
|
||||
}
|
||||
|
||||
/* Create Azure Storage Service URL
|
||||
* @storageURL: URL for Azure Storage endpoint (required) - Environment variable
|
||||
* @pipeline: Azure Storage Pipline (required) - created using createStoragePipeline()
|
||||
*/
|
||||
const createServiceURL = async (storageURL, pipeline) => {
|
||||
return new Azure.ServiceURL(
|
||||
storageURL,
|
||||
pipeline
|
||||
)
|
||||
}
|
||||
|
||||
/* List containers
|
||||
* @pipeline: Azure Storage Pipline (required) - created using createServiceURL
|
||||
* @service_url: URL for Azure Storage endpoint (required) - Environment variable
|
||||
*/
|
||||
const listContainers = async (serviceURL) => {
|
||||
let marker;
|
||||
do {
|
||||
const listContainersResponse = await serviceURL.listContainersSegment(
|
||||
Azure.Aborter.None,
|
||||
marker,
|
||||
);
|
||||
|
||||
marker = listContainersResponse.marker;
|
||||
for (const container of listContainersResponse.containerItems) {
|
||||
console.log(`Container: ${container.name}`);
|
||||
}
|
||||
} while (marker);
|
||||
}
|
||||
|
||||
|
||||
/* Create containers
|
||||
* @serviceURL: URL for Azure Storage endpoint (required) - created using createServiceURL
|
||||
* @containerName: Container Name
|
||||
* Returns: containerURL
|
||||
*/
|
||||
const createContainer = async (serviceURL, containerName) => {
|
||||
const containerURL = ContainerURL.fromServiceURL(serviceURL, containerName)
|
||||
|
||||
const createContainerResponse = await containerURL.create(Aborter.None)
|
||||
|
||||
console.log(
|
||||
`Created container ${containerName} successfully`,
|
||||
await createContainerResponse.requestId
|
||||
)
|
||||
return containerURL
|
||||
}
|
||||
|
||||
/* Create containers
|
||||
* @containerURL: continer URL (required) - created using createContainer
|
||||
* @blobName: Blob Name
|
||||
* Returns: blobURL
|
||||
*/
|
||||
const createBlob = async (containerURL, blobName) => {
|
||||
const blobURL = Azure.BlobURL.fromContainerURL(containerURL, blobName)
|
||||
console.log(`created blob successfully`, await blobURL)
|
||||
return blobURL
|
||||
}
|
||||
|
||||
/* Upload Blob
|
||||
* @blobURL: blobURL (required) - created using createBlob
|
||||
* @blobContent: Blob Content
|
||||
* Returns: uploadBlobResponse
|
||||
*/
|
||||
const uploadBlob = async (blobURL, blobContent) => {
|
||||
const blockBlobURL = Azure.BlockBlobURL.fromBlobURL(blobURL)
|
||||
const uploadBlobResponse = await blockBlobURL.upload(
|
||||
Azure.Aborter.None,
|
||||
blobContent,
|
||||
blobContent.length
|
||||
);
|
||||
console.log(`Upload block blob ${blobName} successfully`, await uploadBlobResponse)
|
||||
return uploadBlobResponse
|
||||
}
|
||||
|
||||
/* Download Blob
|
||||
* @blobURL: blobURL (required) - created using createBlob
|
||||
* @blobContent: Blob Content
|
||||
* Returns: downloadBlockBlobResponse
|
||||
*/
|
||||
const downloadBlob = async (blobURL, blobContent) => {
|
||||
const downloadBlockBlobResponse = await blobURL.download(Azure.Aborter.None, 0)
|
||||
console.log(
|
||||
"Downloaded blob content",
|
||||
downloadBlockBlobResponse.readableStreamBody.read(content.length).toString()
|
||||
)
|
||||
console.log(`BlockBlobResponse: ${downloadBlockBlobResponse}`)
|
||||
return downloadBlockBlobResponse
|
||||
}
|
||||
|
||||
/* Delete Blob
|
||||
* @blobURL: blobURL (required) - created using createBlob
|
||||
* Returns: bool
|
||||
*/
|
||||
const deleteBlob = async (blobrURL) => {
|
||||
await blobrURL.delete(Azure.Aborter.None);
|
||||
console.log("deleted blob", blobrURL);
|
||||
return true
|
||||
}
|
||||
|
||||
/* Delete Container
|
||||
* @containerURL: continer URL (required) - created using createContainer
|
||||
* Returns: bool
|
||||
*/
|
||||
const deleteContainer = async (containerURL) => {
|
||||
await containerURL.delete(Azure.Aborter.None);
|
||||
console.log("deleted container", containerURL);
|
||||
return true
|
||||
}
|
||||
|
||||
const createSASQueryURL = async (containerName) => {
|
||||
let today = new Date()
|
||||
let week = new Date()
|
||||
week.setDate(today.getDate() + 7)
|
||||
// Create SharedKeyCredential and attach to pipline
|
||||
const SKC = new Azure.SharedKeyCredential(azureAccount, azureKey)
|
||||
const pipeline = Azure.StorageURL.newPipeline(SKC)
|
||||
// Create container URL
|
||||
const serviceURL = new Azure.ServiceURL(azureStorageUrl, pipeline)
|
||||
const containerURL = Azure.ContainerURL.fromServiceURL(serviceURL, containerName)
|
||||
|
||||
//list container and check if already exists.
|
||||
let marker
|
||||
let containerCheck = null
|
||||
do {
|
||||
const listContainersResponse = await serviceURL.listContainersSegment(
|
||||
Azure.Aborter.None,
|
||||
marker,
|
||||
)
|
||||
// console.log(`ContainerCheck marker: `, listContainersResponse)
|
||||
marker = listContainersResponse.marker;
|
||||
for (const container of listContainersResponse.containerItems) {
|
||||
console.log(`ContainerCheck: ${container.name} | ${containerName} | ${marker}`)
|
||||
if(container.name === containerName){
|
||||
containerCheck = container.name
|
||||
}
|
||||
}
|
||||
} while (marker)
|
||||
|
||||
// if container doesn't exist, create one
|
||||
let newContainer
|
||||
if(containerCheck === null){
|
||||
newContainer = await containerURL.create(Azure.Aborter.None, { access: 'blob' })
|
||||
}
|
||||
console.log('createSASQueryURL container: ', containerURL, containerCheck, newContainer)
|
||||
|
||||
// Set permissions for service, resource types and containers
|
||||
const SASContainerPerms = await Azure.AccountSASPermissions.parse('rwlacu')
|
||||
const SASServicePerms = await Azure.AccountSASServices.parse('b')
|
||||
const SASResourceTypes = await Azure.AccountSASResourceTypes.parse('co')
|
||||
console.log('createSASQueryURL permissions: ', SASServicePerms, SASResourceTypes, SASContainerPerms)
|
||||
// Generate SAS url
|
||||
const SASQueryParameters = await Azure.generateBlobSASQueryParameters(
|
||||
{
|
||||
version: '2017-11-09',
|
||||
protocol: 'https,http',
|
||||
expiryTime: week,
|
||||
permissions: 'rwlac',
|
||||
containerName: containerName
|
||||
}, SKC )
|
||||
console.log('createSASQueryURL SASQueryParameters: ', containerURL.url, SASQueryParameters)
|
||||
return SASQueryParameters
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createStoragePipline,
|
||||
createServiceURL,
|
||||
listContainers,
|
||||
createContainer,
|
||||
createBlob,
|
||||
uploadBlob,
|
||||
downloadBlob,
|
||||
deleteBlob,
|
||||
deleteContainer,
|
||||
createSASQueryURL
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
export const toLocalTime = (epoch) => {
|
||||
const localDate = new Date(0);
|
||||
localDate.setUTCSeconds(epoch);
|
||||
const options = { year: 'numeric', month: 'long', day: '2-digit' };
|
||||
return (
|
||||
`${localDate.toLocaleDateString(options)
|
||||
} at ${
|
||||
localDate.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })}`
|
||||
);
|
||||
};
|
||||
|
||||
export const temporaryFunction = input => input;
|
||||
|
||||
export function isDefined (d) {
|
||||
return d !== null && typeof d !== 'undefined'
|
||||
}
|
||||
|
||||
export function isNull (d) {
|
||||
return d === null
|
||||
}
|
||||
|
||||
export const isArray = Array.isArray
|
||||
|
||||
export function isObject (d) {
|
||||
return isDefined(d) && typeof d === 'object' && !Array.isArray(d)
|
||||
}
|
||||
|
||||
export function isEmpty (obj) {
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export function validObject (object, ...keys) {
|
||||
return keys.reduce((a, b) => (a || { })[b], object) !== undefined
|
||||
}
|
||||
|
||||
export function mergeDeep (target, ...sources) {
|
||||
if (!sources.length) return target
|
||||
const source = sources.shift()
|
||||
|
||||
if (isObject(target) && isObject(source)) {
|
||||
for (const key in source) {
|
||||
if (isObject(source[key])) {
|
||||
if (!target[key]) Object.assign(target, { [key]: {} })
|
||||
mergeDeep(target[key], source[key])
|
||||
} else {
|
||||
Object.assign(target, { [key]: source[key] })
|
||||
}
|
||||
}
|
||||
}
|
||||
return mergeDeep(target, ...sources)
|
||||
}
|
||||
|
||||
export function slugify (string) {
|
||||
const a = 'àáäâãåèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;'
|
||||
const b = 'aaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------'
|
||||
const p = new RegExp(a.split('').join('|'), 'g')
|
||||
|
||||
return string.toString().toLowerCase()
|
||||
.replace(/\s+/g, '-') // Replace spaces with -
|
||||
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
|
||||
.replace(/&/g, '-and-') // Replace & with 'and'
|
||||
.replace(/[^\w-]+/g, '') // Remove all non-word characters
|
||||
.replace(/--+/g, '-') // Replace multiple - with single -
|
||||
.replace(/^-+/, '') // Trim - from start of text
|
||||
.replace(/-+$/, '') // Trim - from end of text
|
||||
}
|
||||
Reference in new issue
Block a user