Skip to content

Commit eff6a70

Browse files
committed
feat(jsbattle): add league for registered users
1 parent 6ce4e21 commit eff6a70

32 files changed

+1866
-34
lines changed

packages/jsbattle-mockserver/app/MockServer.js

+2
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ class MockServer {
129129
server.use(jsonServer.rewriter({
130130
"/api/authMethods": "/authMethods",
131131
"/api/profile": "/profile",
132+
"/api/user/league": "/user,league",
133+
"/api/user/league/submission": "/user,league,submission",
132134
"/api/:a/:b": "/:a,:b",
133135
"/api/:a/:b/:c": "/:a,:b/:c",
134136
"/api/:a/:b/:c/:d": "/:a,:b/:c/:d",

packages/jsbattle-mockserver/app/init_db.json

+92-8
Original file line numberDiff line numberDiff line change
@@ -209,24 +209,24 @@
209209
},
210210
"user,scripts": [
211211
{
212-
"id": "xVDPu2zXl9M4lINa",
212+
"id": "FOjJ3IULPVU9KSWw",
213213
"ownerId": "cWzaLP12eSeDL68F",
214214
"ownerName": "mock",
215-
"scriptName": "nihnae",
215+
"scriptName": "peridot",
216216
"namespace": "user",
217217
"code": "importScripts('lib/tank.js');\n\n// Don't know where to start?\n// Read Getting Started in \"Docs\" section \n\ntank.init(function(settings, info) {\n\t// initialize tank here\n \n});\n\ntank.loop(function(state, control) {\n\t// write your tank logic here\n \n});\n\n\n",
218-
"createdAt": "2020-02-23T08:26:54.011Z",
219-
"modifiedAt": "2020-02-23T08:26:54.011Z"
218+
"createdAt": "2020-02-23T08:27:42.940Z",
219+
"modifiedAt": "2020-02-23T08:27:42.940Z"
220220
},
221221
{
222-
"id": "FOjJ3IULPVU9KSWw",
222+
"id": "xVDPu2zXl9M4lINa",
223223
"ownerId": "cWzaLP12eSeDL68F",
224224
"ownerName": "mock",
225-
"scriptName": "peridot",
225+
"scriptName": "nihnae",
226226
"namespace": "user",
227227
"code": "importScripts('lib/tank.js');\n\n// Don't know where to start?\n// Read Getting Started in \"Docs\" section \n\ntank.init(function(settings, info) {\n\t// initialize tank here\n \n});\n\ntank.loop(function(state, control) {\n\t// write your tank logic here\n \n});\n\n\n",
228-
"createdAt": "2020-02-23T08:27:42.940Z",
229-
"modifiedAt": "2020-02-23T08:27:42.940Z"
228+
"createdAt": "2020-02-23T08:26:54.011Z",
229+
"modifiedAt": "2020-02-23T08:26:54.011Z"
230230
},
231231
{
232232
"id": "BuLO667PXk0yEqwg",
@@ -259,6 +259,90 @@
259259
"completed": false
260260
}
261261
],
262+
"user,league,submission": {
263+
"id": "U7aO66layk0yEqwg",
264+
"scriptId": "xVDPu2zXl9M4lINa",
265+
"ownerId": "cWzaLP12eSeDL68F",
266+
"ownerName": "mock",
267+
"scriptName": "nihnae",
268+
"joinedAt": "2020-02-23T08:27:42.940Z",
269+
"rank": 1,
270+
"fights": {
271+
"total": 343,
272+
"won": 128,
273+
"lose": 223,
274+
"error": 43
275+
},
276+
"score": 1092
277+
},
278+
"user,league": {
279+
"submission": {
280+
"id": "U7aO66layk0yEqwg",
281+
"scriptId": "xVDPu2zXl9M4lINa",
282+
"ownerId": "cWzaLP12eSeDL68F",
283+
"ownerName": "mock",
284+
"scriptName": "nihnae",
285+
"joinedAt": "2020-02-23T08:27:42.940Z",
286+
"rank": 1,
287+
"fights": {
288+
"total": 343,
289+
"won": 128,
290+
"lose": 223,
291+
"error": 43
292+
},
293+
"score": 1092
294+
},
295+
"ranktable": [
296+
{
297+
"id": "hjkKvy79ahj34",
298+
"scriptId": "asdf772db8q9fasdo",
299+
"ownerId": "jnfao8y9ascdy",
300+
"ownerName": "Allegro",
301+
"scriptName": "killa-8",
302+
"joinedAt": "2020-01-28T10:27:42.940Z",
303+
"rank": 1,
304+
"fights": {
305+
"total": 642,
306+
"won": 561,
307+
"lose": 213,
308+
"error": 1
309+
},
310+
"score": 1872
311+
},
312+
{
313+
"id": "U7aO66layk0yEqwg",
314+
"scriptId": "xVDPu2zXl9M4lINa",
315+
"ownerId": "cWzaLP12eSeDL68F",
316+
"ownerName": "jerome",
317+
"scriptName": "nihnae",
318+
"joinedAt": "2020-02-23T08:27:42.940Z",
319+
"rank": 2,
320+
"fights": {
321+
"total": 343,
322+
"won": 128,
323+
"lose": 223,
324+
"error": 43
325+
},
326+
"score": 1092
327+
},
328+
{
329+
"id": "jhsadf90y79a3",
330+
"scriptId": "h84323adsfia98",
331+
"ownerId": "fash4u39awfh2349b",
332+
"ownerName": "monk",
333+
"scriptName": "3ll3a",
334+
"joinedAt": "2020-04-01T12:34:42.940Z",
335+
"rank": 3,
336+
"fights": {
337+
"total": 123,
338+
"won": 120,
339+
"lose": 2,
340+
"error": 1
341+
},
342+
"score": 993
343+
}
344+
]
345+
},
262346
"authMethods": {
263347
"mock": {
264348
"name": "Mock",

packages/jsbattle-server/app/Gateway.js

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class Gateway {
2020
start() {
2121
return new Promise((resolve) => {
2222
this.broker.start()
23+
.then(() => this.broker.broadcast('app.seed', {}))
2324
.then(resolve)
2425
.catch((err) => console.error(`Error occured! ${err.message}`));
2526
});

packages/jsbattle-server/app/services/ApiGateway.service.js

+4
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ class ApiGatewayService extends Service {
6868
"GET challenges": "challenges.listUserChallanges",
6969
"GET challenges/:challengeId": "challenges.getUserChallange",
7070
"PATCH challenges/:challengeId": "challenges.updateUserChallange",
71+
"GET league/": "league.getLeagueSummary",
72+
"GET league/submission": "league.getUserSubmission",
73+
"PATCH league/submission": "league.joinLeague",
74+
"DELETE league/submission": "league.leaveLeague",
7175
},
7276
bodyParsers: {
7377
json: true,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
const Service = require("moleculer").Service;
2+
const DbService = require("moleculer-db");
3+
const { ValidationError } = require("moleculer").Errors;
4+
const _ = require('lodash');
5+
const getDbAdapterConfig = require("../lib/getDbAdapterConfig.js");
6+
const fs = require('fs');
7+
const path = require('path');
8+
9+
class LeagueService extends Service {
10+
11+
constructor(broker) {
12+
super(broker);
13+
14+
let adapterConfig = getDbAdapterConfig(broker.serviceConfig.data, 'league')
15+
this.parseServiceSchema({
16+
...adapterConfig,
17+
name: "league",
18+
mixins: [DbService],
19+
settings: {
20+
idField: 'id',
21+
fields: [
22+
"id",
23+
"joinedAt",
24+
"ownerId",
25+
"ownerName",
26+
"scriptId",
27+
"scriptName",
28+
"rank",
29+
"fights_total",
30+
"fights_win",
31+
"fights_lose",
32+
"fights_error",
33+
"score",
34+
"code"
35+
]
36+
},
37+
entityValidator: {
38+
joinedAt: "date",
39+
ownerId: "string",
40+
ownerName: { type: "string", min: 1, max: 255 },
41+
scriptId: "string",
42+
scriptName: { type: "string", min: 1, max: 255 },
43+
rank: "number",
44+
fights_total: "number",
45+
fights_win: "number",
46+
fights_lose: "number",
47+
fights_error: "number",
48+
score: "number",
49+
code: { type: "string", min: 0, max: 65536 },
50+
},
51+
dependencies: ['scriptStore'],
52+
actions: {
53+
seedLeague: this.seedLeague,
54+
getUserSubmission: this.getUserSubmission,
55+
joinLeague: this.joinLeague,
56+
leaveLeague: this.leaveLeague,
57+
getLeagueSummary: this.getLeagueSummary
58+
},
59+
hooks: {
60+
before: {
61+
create: [
62+
function addDefaults(ctx) {
63+
ctx.params.joinedAt = new Date();
64+
ctx.params.rank = 0;
65+
ctx.params.fights_total = 0;
66+
ctx.params.fights_win = 0;
67+
ctx.params.fights_lose = 0;
68+
ctx.params.fights_error = 0;
69+
ctx.params.score = 0;
70+
ctx.params = _.omit(ctx.params, ['id']);
71+
return ctx;
72+
}
73+
]
74+
}
75+
},
76+
events: {
77+
"app.seed": async (ctx) => {
78+
await ctx.call('league.seedLeague', {})
79+
}
80+
}
81+
});
82+
}
83+
84+
async seedLeague(ctx) {
85+
const seedPath = path.resolve(__dirname, 'league', 'seed');
86+
const seedFiles = fs.readdirSync(seedPath)
87+
.map((filename) => ({
88+
ownerId: 0,
89+
ownerName: 'jsbattle',
90+
scriptId: 0,
91+
scriptName: filename.replace(/\.tank$/, ''),
92+
code: fs.readFileSync(path.resolve(seedPath, filename), 'utf8')
93+
}))
94+
.map((entry) => new Promise(async (resolve) => {
95+
let existingEntry = await ctx.call('league.find', {
96+
query: {
97+
ownerName: 'jsbattle',
98+
scriptName: entry.scriptName,
99+
}
100+
});
101+
if(existingEntry.length == 0) {
102+
await ctx.call('league.create', entry);
103+
}
104+
resolve();
105+
}));
106+
107+
await Promise.all(seedFiles);
108+
}
109+
110+
async getUserSubmission(ctx) {
111+
const userId = ctx.meta.user ? ctx.meta.user.id : null;
112+
if(!userId) {
113+
throw new ValidationError('Not Authorized!', 401);
114+
}
115+
116+
let response = await ctx.call('league.find', {
117+
query: {
118+
ownerId: userId
119+
},
120+
limit: 1
121+
});
122+
123+
if(response.length === 0) {
124+
return {}
125+
}
126+
return response[0]
127+
}
128+
129+
async joinLeague(ctx) {
130+
const userId = ctx.meta.user ? ctx.meta.user.id : null;
131+
if(!userId) {
132+
throw new ValidationError('Not Authorized!', 401);
133+
}
134+
135+
let script = await ctx.call('scriptStore.getUserScript', { id: ctx.params.scriptId });
136+
if(script.ownerId != userId) {
137+
throw new ValidationError('Not Authorized!', 401);
138+
}
139+
140+
await this.leaveLeague(ctx);
141+
142+
await ctx.call('league.create', {
143+
ownerId: script.ownerId,
144+
ownerName: script.ownerName,
145+
scriptId: script.id,
146+
scriptName: script.scriptName,
147+
code: script.code
148+
});
149+
150+
return this.getLeagueSummary(ctx);
151+
}
152+
153+
async leaveLeague(ctx) {
154+
const userId = ctx.meta.user ? ctx.meta.user.id : null;
155+
if(!userId) {
156+
throw new ValidationError('Not Authorized!', 401);
157+
}
158+
159+
let submissions = await ctx.call('league.find', {
160+
query: {
161+
ownerId: userId
162+
}
163+
});
164+
165+
let removals = submissions.map((submission) => ctx.call('league.remove', {
166+
id: submission.id
167+
}));
168+
169+
await Promise.all(removals);
170+
171+
return this.getLeagueSummary(ctx);
172+
}
173+
174+
async getLeagueSummary(ctx) {
175+
const userId = ctx.meta.user ? ctx.meta.user.id : null;
176+
if(!userId) {
177+
throw new ValidationError('Not Authorized!', 401);
178+
}
179+
180+
let ranktable = await ctx.call('league.find', {
181+
sort: 'rank'
182+
});
183+
ranktable = ranktable.map((item) => _.pick(item, [
184+
"rank",
185+
"ownerId",
186+
"ownerName",
187+
"scriptId",
188+
"scriptName",
189+
"joinedAt",
190+
"fights_total",
191+
"fights_win",
192+
"fights_lose",
193+
"fights_error",
194+
"score"
195+
]));
196+
197+
let submission = await this.getUserSubmission(ctx)
198+
submission = _.pick(submission, [
199+
"rank",
200+
"ownerId",
201+
"ownerName",
202+
"scriptId",
203+
"scriptName",
204+
"joinedAt",
205+
"fights_total",
206+
"fights_win",
207+
"fights_lose",
208+
"fights_error",
209+
"score"
210+
]);
211+
212+
return {
213+
submission,
214+
ranktable
215+
}
216+
}
217+
218+
}
219+
220+
module.exports = LeagueService;

packages/jsbattle-server/app/services/ScriptStore.service.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ class ScriptStoreService extends Service {
100100

101101
async createUserScript(ctx) {
102102
const userId = ctx.meta.user ? ctx.meta.user.id : null;
103-
const username = ctx.meta.user.username ? ctx.meta.user.username : null;
103+
const username = ctx.meta.user && ctx.meta.user.username ? ctx.meta.user.username : null;
104104
if(!userId || !username) {
105105
throw new ValidationError('Not Authorized!', 401);
106106
}

0 commit comments

Comments
 (0)