chore(release): 0.3.6 v0.3.6
authorxangelo <me@xangelo.ca>
Wed, 6 Sep 2023 19:35:15 +0000 (15:35 -0400)
committerxangelo <me@xangelo.ca>
Wed, 6 Sep 2023 19:35:15 +0000 (15:35 -0400)
CHANGELOG.md
migrations/20230906180935_rbac.ts [new file with mode: 0644]
package-lock.json
package.json
src/server/api.ts
src/server/chat-commands.ts [new file with mode: 0644]
src/server/player.ts
src/shared/player.ts

index 81ae3c7b1a8860c12e47cce14ecc7b072da9771a..c9a78dc3841d3c32a4f04b4494533e39a43f697d 100644 (file)
@@ -2,6 +2,14 @@
 
 All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
 
 
 All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
 
+### [0.3.6](https://git.xangelo.ca/?p=risinglegends.git;a=commitdiff;h=v0.3.6;hp=v0.3.5;ds=sidebyside) (2023-09-06)
+
+
+### Features
+
+* chat command to set player level 9ced477
+* rbac support with admin permission 940079d
+
 ### [0.3.5](https://git.xangelo.ca/?p=risinglegends.git;a=commitdiff;h=v0.3.5;hp=v0.3.4;ds=sidebyside) (2023-09-06)
 
 
 ### [0.3.5](https://git.xangelo.ca/?p=risinglegends.git;a=commitdiff;h=v0.3.5;hp=v0.3.4;ds=sidebyside) (2023-09-06)
 
 
diff --git a/migrations/20230906180935_rbac.ts b/migrations/20230906180935_rbac.ts
new file mode 100644 (file)
index 0000000..e110d2d
--- /dev/null
@@ -0,0 +1,18 @@
+import { Knex } from "knex";
+
+
+export async function up(knex: Knex): Promise<void> {
+  return knex.schema.createTable('permissions', function(table){
+    table.string('name').primary();
+  }).createTable('player_permissions', function(table) {
+      table.string('permission');
+      table.uuid('player_id');
+      table.primary(['permission', 'player_id']);
+    });
+}
+
+
+export async function down(knex: Knex): Promise<void> {
+  return knex.schema.dropTable('permissions').dropTable('player_permissions');
+}
+
index ac9b5267cd11914dc8e0e39b8cf0d735aff521e3..312fb12066ea0cf68406492e38cd199cee3a3745 100644 (file)
@@ -1,12 +1,12 @@
 {
   "name": "rising-legends",
 {
   "name": "rising-legends",
-  "version": "0.3.5",
+  "version": "0.3.6",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "rising-legends",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "rising-legends",
-      "version": "0.3.5",
+      "version": "0.3.6",
       "dependencies": {
         "@honeycombio/opentelemetry-node": "^0.4.0",
         "@opentelemetry/auto-instrumentations-node": "^0.37.0",
       "dependencies": {
         "@honeycombio/opentelemetry-node": "^0.4.0",
         "@opentelemetry/auto-instrumentations-node": "^0.37.0",
index 891588dce1866fe829b28dc73f5c7ae7d248a958..3c242fc8858dfa49433c734d2ca1ec520ce13f02 100644 (file)
@@ -1,7 +1,7 @@
 {
   "name": "rising-legends",
   "private": true,
 {
   "name": "rising-legends",
   "private": true,
-  "version": "0.3.5",
+  "version": "0.3.6",
   "scripts": {
     "up": "npx prisma migrate dev --name \"init\"",
     "start": "pm2 start dist/server/api.js",
   "scripts": {
     "up": "npx prisma migrate dev --name \"init\"",
     "start": "pm2 start dist/server/api.js",
index 80aa82af1b4649f7dacfc2d639a57d5274e0f45f..0aa2689c95a6e594999ccffc22309771648ee358 100644 (file)
@@ -25,6 +25,7 @@ import { clearTravelPlan, completeTravel, getAllPaths, getAllServices, getCityDe
 import { signup, login, authEndpoint } from './auth';
 import {db} from './lib/db';
 import { getPlayerSkills} from './skills';
 import { signup, login, authEndpoint } from './auth';
 import {db} from './lib/db';
 import { getPlayerSkills} from './skills';
+import { handleChatCommands } from './chat-commands';
 
 import { fightRound, blockPlayerInFight } from './fight';
 
 
 import { fightRound, blockPlayerInFight } from './fight';
 
@@ -45,9 +46,6 @@ import { renderTravel, travelButton } from './views/travel';
 import { renderChatMessage } from './views/chat';
 
 // TEMP!
 import { renderChatMessage } from './views/chat';
 
 // TEMP!
-import { createMonsters } from '../../seeds/monsters';
-import { createAllCitiesAndLocations } from '../../seeds/cities';
-import { createShopItems, createShopEquipment } from '../../seeds/shop_items';
 import { Item, PlayerItem, ShopItem } from 'shared/items';
 import { equip, unequip } from './equipment';
 import { HealthPotionSmall } from '../shared/items/health_potion';
 import { Item, PlayerItem, ShopItem } from 'shared/items';
 import { equip, unequip } from './equipment';
 import { HealthPotionSmall } from '../shared/items/health_potion';
@@ -164,27 +162,11 @@ app.post('/chat', authEndpoint, async (req: Request, res: Response) => {
     return;
   }
 
     return;
   }
 
-  let message: Message;
-  if(msg.startsWith('/server lmnop')) {
+  if(msg.startsWith('/server') && req.player.permissions.includes('admin')) {
     try {
     try {
-      if(msg === '/server lmnop refresh-monsters') {
-        await createMonsters();
-        message = broadcastMessage('server', 'Monster refresh!');
-      }
-      else if(msg === '/server lmnop refresh-cities') {
-        await createAllCitiesAndLocations();
-        message = broadcastMessage('server', 'Cities, Locations, and Paths refreshed!');
-      }
-      else if(msg === '/server lmnop refresh-shops') {
-        await createShopItems();
-        await createShopEquipment();
-        message = broadcastMessage('server', 'Refresh shop items');
-      }
-      else {
-        const str = msg.split('/server lmnop ')[1];
-        if(str) {
-          message = broadcastMessage('server', str);
-        }
+      const output = await handleChatCommands(msg, req.player, io);
+      if(output) {
+        io.to(cache.get(`socket:${req.player.id}`)).emit('chat', renderChatMessage(output));
       }
     }
     catch(e) {
       }
     }
     catch(e) {
@@ -199,17 +181,15 @@ app.post('/chat', authEndpoint, async (req: Request, res: Response) => {
     res.sendStatus(204);
   }
   else {
     res.sendStatus(204);
   }
   else {
-    message = broadcastMessage(req.player.username, xss(msg, {
+    const message = broadcastMessage(req.player.username, xss(msg, {
       whiteList: {}
     }));
     chatHistory.push(message);
     chatHistory.slice(-10);
       whiteList: {}
     }));
     chatHistory.push(message);
     chatHistory.slice(-10);
-  }
-
-  if(message) {
     io.emit('chat', renderChatMessage(message));
     io.emit('chat', renderChatMessage(message));
-    res.sendStatus(204);
   }
   }
+
+  res.sendStatus(204);
 });
 
 app.get('/player', authEndpoint, async (req: Request, res: Response) => {
 });
 
 app.get('/player', authEndpoint, async (req: Request, res: Response) => {
diff --git a/src/server/chat-commands.ts b/src/server/chat-commands.ts
new file mode 100644 (file)
index 0000000..5e43627
--- /dev/null
@@ -0,0 +1,52 @@
+import { Server } from 'socket.io';
+import { maxHp, maxVigor, Player } from '../shared/player';
+import { createMonsters } from '../../seeds/monsters';
+import { createAllCitiesAndLocations } from '../../seeds/cities';
+import { createShopItems, createShopEquipment } from '../../seeds/shop_items';
+import { broadcastMessage, Message } from '../shared/message';
+import { updatePlayer } from './player';
+
+export async function handleChatCommands(msg: string, player: Player, io: Server): Promise<Message> {
+  let message: Message;
+  if(msg === '/server refresh-monsters') {
+    await createMonsters();
+    message = broadcastMessage('server', 'Monster refresh!');
+  }
+  else if(msg === '/server refresh-cities') {
+    await createAllCitiesAndLocations();
+    message = broadcastMessage('server', 'Cities, Locations, and Paths refreshed!');
+  }
+  else if(msg === '/server refresh-shops') {
+    await createShopItems();
+    await createShopEquipment();
+    message = broadcastMessage('server', 'Refresh shop items');
+  }
+  else if(msg.startsWith('/server set-level')) {
+    const level = parseInt(msg.split(' ').pop());
+    if(level < 1) {
+      message = broadcastMessage('server', 'Needs to be at least level 1');
+    }
+    else {
+      message = broadcastMessage('server', `Set player level: ${level}`);
+
+      player.level = level;
+      player.strength = 4;
+      player.constitution = 4;
+      player.dexterity = 4;
+      player.intelligence = 4;
+      player.hp = maxHp(player.constitution, player.level);
+      player.vigor = maxVigor(player.constitution, player.level);
+      player.stat_points = level-1;
+
+      await updatePlayer(player);
+    }
+  }
+  else {
+    const str = msg.split('/server ')[1];
+    if(str) {
+      message = broadcastMessage('server', str);
+    }
+  }
+
+  return message;
+}
index 527069b6693acfd1a2e92142bc59787b6d65abba..a9d077c23c68b474b0355cb0ff23ec76f9a55fbf 100644 (file)
@@ -7,7 +7,12 @@ import {logger} from './lib/logger';
 
 export async function loadPlayer(authToken: string): Promise<Player> {
   const res = await db.first()
 
 export async function loadPlayer(authToken: string): Promise<Player> {
   const res = await db.first()
-            .select('players.*', 'profession_levels.level', 'profession_levels.exp')
+            .select(
+              'players.*',
+              'profession_levels.level',
+              'profession_levels.exp',
+              db.raw(`coalesce(pp.permissions, '[]'::json) as permissions`)
+            )
             .from<Player>('players')
             .join('profession_levels', function() {
               this.on(function() {
             .from<Player>('players')
             .join('profession_levels', function() {
               this.on(function() {
@@ -15,11 +20,15 @@ export async function loadPlayer(authToken: string): Promise<Player> {
                 this.andOn('profession_levels.profession', '=', 'players.profession')
               })
             })
                 this.andOn('profession_levels.profession', '=', 'players.profession')
               })
             })
+            .leftJoin(
+              db.raw(`(select json_agg(pp.permission) as permissions, pp.player_id from player_permissions pp group by pp.player_id) pp`),
+              'pp.player_id','=', 'players.id'
+            )
             .where({
               'players.id': authToken
             });
 
             .where({
               'players.id': authToken
             });
 
-    return res;
+  return res;
 }
 
 export async function createPlayer(): Promise<Player> {
 }
 
 export async function createPlayer(): Promise<Player> {
index b7c172ab978d421e8fa3ee6cb2f8a70a09c8501e..93d75f3bdb8ed3816ae3803f1e095ac07879f50c 100644 (file)
@@ -3,6 +3,8 @@ import { Stat } from './stats';
 import { SkillDefinition, Skill } from './skills';
 import { EquippedItemDetails } from './equipped';
 
 import { SkillDefinition, Skill } from './skills';
 import { EquippedItemDetails } from './equipped';
 
+export type Permission = 'admin' | 'moderator';
+
 export type Player = {
   id: string,
   account_type: 'session' | 'auth',
 export type Player = {
   id: string,
   account_type: 'session' | 'auth',
@@ -21,6 +23,7 @@ export type Player = {
   city_id: number;
   stat_points: number;
   vigor: number;
   city_id: number;
   stat_points: number;
   vigor: number;
+  permissions: Permission[]
 }
 
 export type PlayerWithSkills = Player & {
 }
 
 export type PlayerWithSkills = Player & {