Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
{
branch: "master",
policy: {},
protocol: {
REST: {
basePath: "/storage",
routes: [
{
path: "/",
method: "GET",
call: {
action: "storage.get",
params: {
offset: "@query.offset:number",
limit: "@query.limit:number",
},
},
},
{
path: "/upload",
method: "POST",
call: {
action: "storage.create",
params: {
file: "@body.file",
meta: {
tags: {
identityId: "@context.auth.identity.sub",
},
allowedContentTypes: ["text/*", "image/*", "application/pdf"],
private: false,
},
},
},
},
{
path: "/upload-stream",
method: "POST",
description: "not a production purpose, need a wrapper action to make this safe",
call: {
action: "storage.createWithStream",
params: "@body.file",
implicitParams: false,
},
},
{
path: "/download/:id",
method: "GET",
call: {
action: "storage.getURL",
params: {
id: "@path.id",
expiryHours: "@query.expiryHours:number",
prompt: "@query.prompt:boolean",
promptAs: "@query.promptAs:string",
},
map: `({ response }) => ({
$status: response ? 303 : 404,
$headers: response ? { "Location": response } : undefined,
})`,
},
},
{
path: "/:id",
method: "GET",
call: {
action: "storage.find",
params: {
id: "@path.id",
},
map: `({ response }) => ({
$status: response ? 200 : 404,
$body: response,
})`,
},
},
{
path: "/:id",
method: "PUT",
call: {
action: "storage.update",
params: {
id: "@path.id",
// id, name, tags, private, contentType
},
},
},
{
path: "/:id",
method: "DELETE",
call: {
action: "storage.delete",
params: {
id: "@path.id",
},
},
},
],
},
},
}import { APIGateway, APIGatewayOptions } from "moleculer-api";
const options: APIGatewayOptions = {
// ...
};
const gateway = new APIGateway(options);
gateway.start()
.then(() => {
// ...
});
const { APIGateway } = require("moleculer-api");
const options = {
// ...
};
const gateway = new APIGateway(options);
gateway.start()
.then(() => {
// ...
});
{
branch: "master",
policy: {},
protocol: {
GraphQL: {
typeDefs: `
extend type Query {
storage: StorageQuery!
}
type StorageQuery {
files(offset: Int = 0, limit: Int = 10): FileList!
file(id: ID!): File
}
type File {
id: ID!
name: String!
contentType: String!
tags: JSON!
private: Boolean!
byteSize: Int!
size: String!
url(expiryHours: Int = 2, prompt: Boolean = false, promptAs: String): String!
updatedAt: DateTime!
createdAt: DateTime!
}
type FileList {
offset: Int!
limit: Int!
# it is not exact value
total: Int!
entries: [File!]!
hasNext: Boolean!
hasPrev: Boolean!
}
extend type Mutation {
storage: StorageMutation!
}
type StorageMutation {
upload(file: Upload!): File!
update(id: ID!, name: String, tags: JSON, private: Boolean): File!
delete(id: ID!): Boolean!
}
`,
resolvers: {
Query: {
storage: `() => ({})`,
},
Mutation: {
storage: `() => ({})`,
},
StorageMutation: {
upload: {
call: {
action: "storage.create",
params: {
file: "@args.file",
meta: {
allowedContentTypes: [],
private: false,
},
},
},
},
update: {
call: {
action: "storage.update",
params: {},
},
},
delete: {
call: {
action: "storage.delete",
params: {},
}
},
},
StorageQuery: {
file: {
call: {
action: "storage.find",
params: {
id: "@args.id[]",
},
},
},
files: {
call: {
action: "storage.get",
params: {
offset: "@args.offset",
limit: "@args.limit",
},
},
},
},
File: {
url: {
call: {
action: "storage.getURL",
params: {
id: "@source.id[]",
expiryHours: "@args.expiryHours",
prompt: "@args.prompt",
promptAs: "@args.promptAs",
},
},
},
size: (({ source }: any) => {
const { byteSize = 0 } = source;
const boundary = 1000;
if (byteSize < boundary) {
return `${byteSize} B`;
}
let div = boundary;
let exp = 0;
for (let n = byteSize / boundary; n >= boundary; n /= boundary) {
div *= boundary;
exp++;
}
const size = byteSize / div;
return `${isNaN(size) ? "-" : size.toLocaleString()} ${"KMGTPE"[exp]}B`;
}).toString(),
},
FileList: {
hasPrev: `({ source }) => source.offset > 0`,
}
},
},
},
}{
branch: "master",
policy: {
call: [
{
actions: ["user.update"],
description: "A user can update the user profile which is belongs to own account.",
scope: ["user.write"],
filter: (({ context, params }) => {
console.log(context);
if (params.id) {
return context.auth.identity.sub === params.id;
}
return false;
}).toString(),
},
],
},
protocol: {
GraphQL: {
typeDefs: `
extend type Query {
viewer: User
user(id: Int, identityId: String, username: String, where: JSON): User
}
extend type Mutation {
createUser(input: UserInput!): User!
updateUser(input: UserInput!): User!
}
input SportsUserInput {
id: Int
username: String
birthdate: Date
gender: Gender
name: String
pictureFileId: String
}
type User {
id: Int!
username: String!
name: String!
birthdate: Date
gender: Gender
pictureFile: File
}
`,
resolvers: {
User: {
pictureFile: {
call: {
if: "({ source }) => !!source.pictureFileId",
action: "storage.find",
params: {
id: "@source.pictureFileId[]",
},
},
},
},
Mutation: {
createUser: {
call: {
action: "user.create",
params: "@args.input",
implicitParams: false,
},
},
updateUser: {
call: {
action: "user.update",
params: "@args.input",
implicitParams: false,
},
},
},
},
},
},
}{
branch: "master",
policy: {},
protocol: {
REST: {
description: "update user's FCM registration token",
basePath: "/notification",
routes: [
{
method: "PUT",
path: "/update-token",
call: {
action: "notification.updateToken",
params: {
identityId: "@context.auth.identity.sub",
token: "@body.token",
},
},
},
],
},
},
}{
branch: "master",
policy: {},
protocol: {
WebSocket: {
basePath: "/chat",
description: "...",
routes: [
/* bidirectional streaming chat */
{
path: "/message-stream/:roomId",
call: {
action: "chat.message.stream",
params: {
roomId: "@path.roomId",
},
},
},
/* pub/sub chat */
{
path: "/message-pubsub/:roomId",
subscribe: {
events: `({ path }) => ["chat.message." + path.roomId]`,
},
publish: {
event: `({ path }) => "chat.message." + path.roomId`,
params: "@message",
},
},
/* pub/sub video */
{
path: "/video-pubsub",
subscribe: {
events: ["chat.video"],
},
publish: {
event: "chat.video",
params: {
id: "@context.id",
username: "@query.username",
data: "@message",
},
filter: `({ params }) => params.id && params.username && params.data`,
},
},
/* streaming video */
{
path: "/video-stream/:type",
call: {
action: "chat.video.stream",
params: {
id: "@context.id",
type: "@path.type",
},
},
},
],
},
},
}2020-11-17T07:25:17.944Z info dwkimqmit.local/broker[0]/moleculer: Moleculer v0.14.11 is starting...
2020-11-17T07:25:17.946Z info dwkimqmit.local/broker[0]/moleculer: Namespace: <not defined>
2020-11-17T07:25:17.947Z info dwkimqmit.local/broker[0]/moleculer: Node ID: dwkimqmit.local-58669
2020-11-17T07:25:17.947Z info dwkimqmit.local/broker[0]/moleculer: Strategy: RoundRobinStrategy
2020-11-17T07:25:17.947Z info dwkimqmit.local/broker[0]/moleculer: Discoverer: LocalDiscoverer
2020-11-17T07:25:17.948Z info dwkimqmit.local/broker[0]/moleculer: Serializer: JSONSerializer
2020-11-17T07:25:17.953Z info dwkimqmit.local/broker[0]/moleculer: Validator: FastestValidator
2020-11-17T07:25:17.954Z info dwkimqmit.local/broker[0]/moleculer: Registered 13 internal middleware(s).
2020-11-17T07:25:17.964Z info dwkimqmit.local/server: gateway context factories have been applied id, ip, locale, cookie, userAgent, request, auth
2020-11-17T07:25:17.968Z info dwkimqmit.local/server: gateway server middleware have been applied: cors, bodyParser, logging, error
2020-11-17T07:25:17.969Z info dwkimqmit.local/server/application: gateway server application has been started: http<HTTPRoute>, ws<WebSocketRoute>
2020-11-17T07:25:17.971Z info dwkimqmit.local/server: gateway server protocol has been started: http
2020-11-17T07:25:17.971Z info dwkimqmit.local/server: gateway server has been started and listening on: http://localhost:8080, ws://localhost:8080
2020-11-17T07:25:17.972Z info dwkimqmit.local/schema: schema policy plugin has been started: filter, scope
2020-11-17T07:25:17.972Z info dwkimqmit.local/schema: schema protocol plugin has been started: GraphQL, REST, WebSocket
2020-11-17T07:25:17.973Z info dwkimqmit.local/schema: master (0 services) branch has been created
2020-11-17T07:25:17.991Z info dwkimqmit.local/schema/master: master (0 services) branch succeeded 1239eb4a (0 schemata, 0 routes) -> 7a2db312 (0 schemata, 4 routes) version compile:
(+) /graphql (http:POST): GraphQL HTTP operation endpoint
(+) /graphql (ws): GraphQL WebSocket operation endpoint
(+) /graphql (http:GET): GraphQL Playground endpoint
(+) /~status (http:GET): master branch introspection endpoint
2020-11-17T07:25:17.991Z info dwkimqmit.local/schema: master (0 services) branch has been updated
2020-11-17T07:25:17.992Z info dwkimqmit.local/schema: schema registry has been started
2020-11-17T07:25:17.993Z info dwkimqmit.local/broker[0]/moleculer: '$api' service is registered.
2020-11-17T07:25:17.995Z info dwkimqmit.local/broker[0]/moleculer: Service '$api' started.
2020-11-17T07:25:17.997Z info dwkimqmit.local/broker[0]/moleculer: '$node' service is registered.
2020-11-17T07:25:17.997Z info dwkimqmit.local/broker[0]/moleculer: Service '$node' started.
2020-11-17T07:25:17.998Z info dwkimqmit.local/broker[0]/moleculer: Service '$api' started.
2020-11-17T07:25:17.998Z info dwkimqmit.local/broker[0]/moleculer: ✔ ServiceBroker with 2 service(s) is started successfully in 3ms.
2020-11-17T07:25:17.999Z info dwkimqmit.local/broker[0]: service broker has been started: moleculer
2020-11-17T07:25:19.997Z info dwkimqmit.local/server/application: master (0 services) handler mounted for http<HTTPRoute> component
2020-11-17T07:25:19.998Z info dwkimqmit.local/server/application: master (0 services) handler mounted for ws<WebSocketRoute> component{
"branch": "master",
"latestUsedAt": "2020-11-17T07:45:03.322Z",
"parentVersion": null,
"latestVersion": "7a2db312",
"versions": [
{
"version": "7a2db312",
"fullVersion": "7a2db312e34f6105efb5ec107137763b",
"routes": [
"/graphql (http:POST): GraphQL HTTP operation endpoint",
"/graphql (ws): GraphQL WebSocket operation endpoint",
"/graphql (http:GET): GraphQL Playground endpoint",
"/~status (http:GET): master branch introspection endpoint"
],
"integrations": []
},
{
"version": "1239eb4a",
"fullVersion": "1239eb4a8416af46c0448426b51771f5",
"routes": [],
"integrations": []
}
],
"services": []
}
REST: {
basePath: "/players",
description: "player service REST API",
routes: [ protocol: { {
method: "GET",
path: "/:id",
deprecated: false,
description: "Get player information by id",
call: {
action: "player.get",
params: {
id: "@path.id",
},
},
}, {
method: "GET",
path: "/me",
deprecated: false,
description: "Get player information of mine",
call: {
action: "player.get",
params: {
id: "@context.user.player.id",
},
},
}, {
method: "GET",
path: "/me",
deprecated: false,
description: "Get player information of mine",
map: `({ path, query, body, context }) => context.user.player`,
}, {
method: "POST",
path: "/message",
deprecated: false,
description: "Push notifications to all players",
publish: {
event: "player.message",
broadcast: false,
params: {
userId: "@context.user.player.id",
message: "@body.message",
},
},
}, ],
},// @body 객체 전체를 페이로드로 전달하거나 스트림을 전달 할 때 이용됩니다.
params: "@body",
// @ 문자열로 시작되지 않는 값들은 해석되지 않고 그대로 전달됩니다.
params: {
foo: "@path.foo", // will bar parsed
bar: "query.bar", // will be "query.bar"
zzz: ["any", { obj: "ject", can: "be", "use": 2 }],
},
// 항상 string 타입을 갖는 @query, @path 객체의 속성들에 한해서 타입을 boolean이나 number로 변환 할 수 있습니다.
params: {
foo: "@path.foo:number",
bar: "@query.bar:boolean",
}, GraphQL: {
typeDefs: `
"""Soccer Player"""
type Player implements Node {
id: ID!
email: String!
name: String!
photoURL: String
position: String
"""A team player belongs to"""
team: Team
}
extend type Query {
"""Current Player"""
viewer: Player
player(id: ID!): Player
}
extend type Subscription {
playerMessage: String!
playerUpdated: Player
}
`, resolvers: { Player: { team: {
call: {
action: "team.get",
params: {
id: "@source.teamId",
},
},
}, position: `({ source, args, context, info }) => source.position.toUpperCase()`,
` // be noted that special field __isTypeOf got only three arguments
__isTypeOf: `({ source, context, info }) => return source.someSpecialFieldForThisType != null`,
// be noted that special field __resolveType got only three arguments
__resolveType: `
({ source, context, info }) => {
if (source.someSpecialFieldForThisType != null) {
return "TypeA";
} else {
return "TypeB";
}
}
`,
}, Query: {
viewer: {
call: {
action: "player.get",
params: {
id: "@context.user.player.id[]",
},
},
},
player: {
call: {
action: "player.get",
params: {
id: "@args.id[]",
},
},
},
},query {
viewer {
id
email
}
one: player(id: 1) {
id
email
}
two: player(id: 2) {
id
email
}
three: player(id: 3) {
id
email
}
} Subscription: {
playerMessage: {
subscribe: {
events: ["player.message"],
},
}, playerMessage: {
subscribe: {
events: ["player.message"],
map: `({ source, args, context, info }) => source.payload.message`,
},
},
},
},
}, filter: `({ action, params, context, util }) => {
if (action === "player.remove") {
return context.user.player.isAdmin && context.user.player.id != params.id;
} else if (action === "player.create") {
return context.user && (!context.user.player || context.user.player.isAdmin);
}
return true;
}`,
}, {
description: "player can get associated team, admin can get all the teams",
actions: ["team.get"],
scopes: ["player", "player.admin"],
filter: (({ action, params, context, util }) => {
if (context.user.player.isAdmin || params.id === context.user.player.teamId) {
return true;
}
return false;
}).toString(),
},
], publish: [
{
description: "Only admins can publish player events",
events: ["player.**"],
scopes: ["player"],
filter: (({ event, params, context, util }) => {
return context.user.player.isAdmin;
}).toString(),
},
], subscribe: [
{
events: ["player.**"],
description: "Any user can receive player events",
scopes: ["openid"],
},
],
},
}{
actions: ["**"],
scopes: ["**"],
filter: `() => true`,
}{
actions: ["**"],
scopes: ["**"],
filter: `(action, params, context) => {
console.log("policy filter", action, params, context);
}`,
} call: [
{
description: "admin can remove player, newbie and admin can create player",
actions: ["player.**"],
scopes: ["player", "player.admin"],

API Gateway constructor options.