Skip to content

Commit 954e3b2

Browse files
authored
Merge pull request #4 from RisingStack/feat/add-location-and-initial-config-options
feat: add location and initial starting parameters config options
2 parents 727b523 + 3221a10 commit 954e3b2

File tree

10 files changed

+327
-125
lines changed

10 files changed

+327
-125
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,21 @@ Go to the `<host>/migration` page (e.g. (https://cybersim.demcloud.org/migrate)[
162162
- After filling in the relevant Airtable details, click the "Migrate the database" button.
163163

164164
By following these steps, you can successfully migrate data from Airtable to the PostgreSQL database, ensuring that your new game incorporates the customized data you have prepared.
165+
166+
# Airtable Handbook
167+
168+
### PURCHASED MITIGATIONS
169+
170+
In the game, mitigations are organized into groups according to their category. You can customize the order of these mitigations using the following steps:
171+
172+
1. Access the "purchase_mitigations" table.
173+
2. In the toolbar, click on the "Group" option, and then select the "category" field. Airtable will automatically reorganize the mitigations, grouping them just as they appear in the application.
174+
3. Within each category, you can rearrange the mitigations according to your preferences using the drag-and-drop feature. The order you set here will reflect how they appear in the actual game.
175+
176+
### LOCATIONS
177+
178+
Currently, the game exclusively accommodates exactly two locations. You have the flexibility to name these locations as you desire within the "locations" table.
179+
180+
:warning: **Please exercise caution and refrain from modifying the "location_code" fields. Altering the default values ('hq', 'local') here can disrupt the application's functionality!** :warning:
181+
182+
Changing the names of the locations in this section will solely impact how they are displayed in the header menu, tabs, and action titles. Role names (e.g., "HQ IT Team") remain distinct and should be configured separately within the **ROLES** table.

knexfile.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ module.exports = {
44
client: 'pg',
55
connection: {
66
connectionString: process.env.DB_URL,
7-
ssl: { rejectUnauthorized: false },
7+
ssl:
8+
process.env.NODE_ENV === 'production'
9+
? { rejectUnauthorized: false }
10+
: false,
811
},
912
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
exports.up = async (knex) => {
2+
await knex.schema.createTable('location', (tbl) => {
3+
tbl.string('id').primary().notNullable();
4+
tbl.string('name').notNullable();
5+
tbl.enu('type', ['hq', 'local']).notNullable();
6+
});
7+
};
8+
9+
exports.down = async (knex) => {
10+
await knex.schema.dropTableIfExists('location');
11+
};
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
exports.up = async (knex) => {
2+
// #region GAME_MITIGATION TABLE
3+
const locationExists = await knex.schema.hasColumn(
4+
'game_mitigation',
5+
'location',
6+
);
7+
8+
if (locationExists) {
9+
await knex.schema.alterTable('game_mitigation', (tbl) => {
10+
tbl.dropColumn('location');
11+
});
12+
}
13+
// #endregion
14+
15+
// #region MITIGATION TABLE
16+
/**
17+
* Since 'mitigations' are only connected to injections (events),
18+
* replace 'hq_cost', 'local_cost', 'is_hq' and 'is_local' columns
19+
* with a single 'cost' column.
20+
*/
21+
const hqCostExists = await knex.schema.hasColumn('mitigation', 'hq_cost');
22+
if (hqCostExists) {
23+
await knex.schema.alterTable('mitigation', (tbl) => {
24+
tbl.dropColumn('hq_cost');
25+
});
26+
}
27+
28+
const localCostExists = await knex.schema.hasColumn(
29+
'mitigation',
30+
'local_cost',
31+
);
32+
if (localCostExists) {
33+
await knex.schema.alterTable('mitigation', (tbl) => {
34+
tbl.dropColumn('local_cost');
35+
});
36+
}
37+
38+
const isHqExists = await knex.schema.hasColumn('mitigation', 'is_hq');
39+
if (isHqExists) {
40+
await knex.schema.alterTable('mitigation', (tbl) => {
41+
tbl.dropColumn('is_hq');
42+
});
43+
}
44+
45+
const isLocalExists = await knex.schema.hasColumn('mitigation', 'is_local');
46+
if (isLocalExists) {
47+
await knex.schema.alterTable('mitigation', (tbl) => {
48+
tbl.dropColumn('is_local');
49+
});
50+
}
51+
52+
// Add 'cost' if doesn't exist
53+
const costExists = await knex.schema.hasColumn('mitigation', 'cost');
54+
if (!costExists) {
55+
await knex.schema.alterTable('mitigation', (tbl) => {
56+
tbl.integer('cost');
57+
});
58+
}
59+
// #endregion
60+
61+
// #region RESPONSE TABLE
62+
const responseLocationExists = await knex.schema.hasColumn(
63+
'response',
64+
'location',
65+
);
66+
if (responseLocationExists) {
67+
await knex.schema.alterTable('response', (tbl) => {
68+
tbl.dropColumn('location');
69+
});
70+
}
71+
72+
const responseMitigationTypeExists = await knex.schema.hasColumn(
73+
'response',
74+
'mitigation_type',
75+
);
76+
if (responseMitigationTypeExists) {
77+
await knex.schema.alterTable('response', (tbl) => {
78+
tbl.dropColumn('mitigation_type');
79+
});
80+
}
81+
82+
const responseRequiredMitigationTypeExists = await knex.schema.hasColumn(
83+
'response',
84+
'required_mitigation_type',
85+
);
86+
if (responseRequiredMitigationTypeExists) {
87+
await knex.schema.alterTable('response', (tbl) => {
88+
tbl.dropColumn('required_mitigation_type');
89+
});
90+
}
91+
// #endregion
92+
93+
// #region GAME_LOG
94+
const gameLogMitigationTypeExists = await knex.schema.hasColumn(
95+
'game_log',
96+
'mitigation_type',
97+
);
98+
if (gameLogMitigationTypeExists) {
99+
await knex.schema.alterTable('game_log', (tbl) => {
100+
tbl.dropColumn('mitigation_type');
101+
});
102+
}
103+
// #endregion
104+
};
105+
106+
exports.down = async (knex) => {
107+
// #region GAME_MITIGATION TABLE
108+
const locationExists = await knex.schema.hasColumn(
109+
'game_mitigation',
110+
'location',
111+
);
112+
113+
if (!locationExists) {
114+
await knex.schema.alterTable('game_mitigation', (tbl) => {
115+
tbl.enu('location', ['hq', 'local']).defaultTo('hq').notNullable();
116+
});
117+
}
118+
// #endregion
119+
120+
// #region MITIGATION TABLE
121+
const hqCostExists = await knex.schema.hasColumn('mitigation', 'hq_cost');
122+
if (!hqCostExists) {
123+
await knex.schema.alterTable('mitigation', (tbl) => {
124+
tbl.integer('hq_cost');
125+
});
126+
}
127+
128+
const localCostExists = await knex.schema.hasColumn(
129+
'mitigation',
130+
'local_cost',
131+
);
132+
if (!localCostExists) {
133+
await knex.schema.alterTable('mitigation', (tbl) => {
134+
tbl.integer('local_cost');
135+
});
136+
}
137+
138+
const isHqExists = await knex.schema.hasColumn('mitigation', 'is_hq');
139+
if (!isHqExists) {
140+
await knex.schema.alterTable('mitigation', (tbl) => {
141+
tbl.boolean('is_hq').defaultTo(true).notNullable();
142+
});
143+
}
144+
145+
const isLocalExists = await knex.schema.hasColumn('mitigation', 'is_local');
146+
if (!isLocalExists) {
147+
await knex.schema.alterTable('mitigation', (tbl) => {
148+
tbl.boolean('is_local').defaultTo(false).notNullable();
149+
});
150+
}
151+
152+
// Add 'cost' if doesn't exist
153+
const costExists = await knex.schema.hasColumn('mitigation', 'cost');
154+
if (costExists) {
155+
await knex.schema.alterTable('mitigation', (tbl) => {
156+
tbl.dropColumn('cost');
157+
});
158+
}
159+
// #endregion
160+
161+
// #region RESPONSE TABLE
162+
const responseLocationExists = await knex.schema.hasColumn(
163+
'response',
164+
'location',
165+
);
166+
if (!responseLocationExists) {
167+
await knex.schema.alterTable('response', (tbl) => {
168+
tbl
169+
.enu('location', ['hq', 'local', 'party'])
170+
.defaultTo('hq')
171+
.notNullable();
172+
});
173+
}
174+
175+
const responseMitigationTypeExists = await knex.schema.hasColumn(
176+
'response',
177+
'mitigation_type',
178+
);
179+
if (!responseMitigationTypeExists) {
180+
await knex.schema.alterTable('response', (tbl) => {
181+
tbl.enu('mitigation_type', ['hq', 'local', 'party']);
182+
});
183+
}
184+
185+
const responseRequiredMitigationTypeExists = await knex.schema.hasColumn(
186+
'response',
187+
'required_mitigation_type',
188+
);
189+
if (!responseRequiredMitigationTypeExists) {
190+
await knex.schema.alterTable('response', (tbl) => {
191+
tbl.enu('required_mitigation_type', ['hq', 'local', 'party']);
192+
});
193+
}
194+
// #endregion
195+
196+
// #region GAME_LOG
197+
const gameLogMitigationTypeExists = await knex.schema.hasColumn(
198+
'game_log',
199+
'mitigation_type',
200+
);
201+
if (!gameLogMitigationTypeExists) {
202+
await knex.schema.alterTable('game_log', (tbl) => {
203+
tbl.string('mitigation_type');
204+
});
205+
}
206+
// #endregion
207+
};

src/app.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ app.get('/mitigations', async (req, res) => {
4545
res.json(records);
4646
});
4747

48+
app.get('/locations', async (req, res) => {
49+
const records = await db('location');
50+
res.json(records);
51+
});
52+
4853
app.get('/systems', async (req, res) => {
4954
const records = await db('system');
5055
res.json(records);

src/models/game.js

Lines changed: 18 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,20 @@ const getGame = (id) =>
3434
)
3535
.first();
3636

37-
const createGame = async (id) => {
37+
const createGame = async (
38+
id,
39+
initialBudget = 6000,
40+
initialPollPercentage = 55,
41+
) => {
3842
await db('game').insert(
3943
{
4044
id,
41-
budget: 6000,
42-
poll: 55.0,
45+
budget: initialBudget,
46+
poll: initialPollPercentage,
4347
},
4448
['id'],
4549
);
50+
4651
const systems = await db('system').select('id as systemId');
4752
await db('game_system').insert(
4853
systems.map(({ systemId }) => ({
@@ -51,32 +56,16 @@ const createGame = async (id) => {
5156
state: true,
5257
})),
5358
);
54-
const mitigations = await db('mitigation').select(
55-
'id as mitigationId',
56-
'is_hq as isHq',
57-
'is_local as isLocal',
58-
);
59+
60+
const mitigations = await db('mitigation').select('id as mitigationId');
5961
await db('game_mitigation').insert(
60-
mitigations.reduce((acc, { mitigationId, isHq, isLocal }) => {
61-
if (isHq) {
62-
acc.push({
63-
game_id: id,
64-
mitigation_id: mitigationId,
65-
location: 'hq',
66-
state: false,
67-
});
68-
}
69-
if (isLocal) {
70-
acc.push({
71-
game_id: id,
72-
mitigation_id: mitigationId,
73-
location: 'local',
74-
state: false,
75-
});
76-
}
77-
return acc;
78-
}, []),
62+
mitigations.map(({ mitigationId }) => ({
63+
game_id: id,
64+
mitigation_id: mitigationId,
65+
state: false,
66+
})),
7967
);
68+
8069
const injections = await db('injection').select('id as injecionId');
8170
await db('game_injection').insert(
8271
injections.map(({ injecionId }) => ({
@@ -87,12 +76,7 @@ const createGame = async (id) => {
8776
return getGame(id);
8877
};
8978

90-
const changeMitigation = async ({
91-
mitigationId,
92-
mitigationType,
93-
mitigationValue,
94-
gameId,
95-
}) => {
79+
const changeMitigation = async ({ mitigationId, mitigationValue, gameId }) => {
9680
try {
9781
const game = await db('game')
9882
.select(
@@ -115,13 +99,12 @@ const changeMitigation = async ({
11599
.where({
116100
game_id: gameId,
117101
mitigation_id: mitigationId,
118-
location: mitigationType,
119102
})
120103
.first();
121104

122105
if (gameMitigationValue !== mitigationValue) {
123106
const { cost } = await db('mitigation')
124-
.select(`${mitigationType}_cost as cost`)
107+
.select('cost')
125108
.where({ id: mitigationId })
126109
.first();
127110
if (cost) {
@@ -160,7 +143,6 @@ const changeMitigation = async ({
160143
game_timer: timeTaken,
161144
type: 'Budget Item Purchase',
162145
mitigation_id: mitigationId,
163-
mitigation_type: mitigationType,
164146
});
165147
}
166148
}

0 commit comments

Comments
 (0)