ソースを参照

Implementierung ChatController nun auf File-Basis (statt Array In-Memory)

Christian Kahlau 3 年 前
コミット
664d02bd48

+ 2 - 1
.env

@@ -1,4 +1,5 @@
 WEB_PORT=8999
 SESSION_TIMEOUT_SEC=1440
 ENABLE_BASIC_AUTH=1
-PASSWORD_SALT=Geh3im3s5@lt
+PASSWORD_SALT=Geh3im3s5@lt
+CHATDATA_DIR=./chatdata

+ 1 - 0
.gitignore

@@ -1,3 +1,4 @@
 node_modules/
 package-lock.json
+chatdata/
 dist/

+ 6 - 6
private/index.html

@@ -78,10 +78,10 @@
 
       function renderMessages() {
         chat.container.empty();
-        chat.messages.forEach((msg, i) => renderMessage(msg, i));
+        chat.messages.forEach(msg => renderMessage(msg));
       }
 
-      function renderMessage(msg, idx) {
+      function renderMessage(msg) {
         chat.container.append(
           '<div class="card chat-message">' +
             '<div class="card-body">' +
@@ -89,7 +89,7 @@
             msg.author +
             '</h5>' +
             '<button class="btn btn-outline-danger btn-sm" onclick="deleteMessage(' +
-            idx +
+            msg.id +
             ')">Delete</button>' +
             '<h6 class="card-subtitle mb-2 text-muted">' +
             msg.time +
@@ -124,7 +124,7 @@
             'X-CSRF-Token': token
           },
           success: function (data, textStatus, xhr) {
-            renderMessage(data, chat.messages.length);
+            renderMessage(data);
             chat.input.val('');
           },
           error: function (xhr, textStatus, errorMsg) {
@@ -134,7 +134,7 @@
         });
       }
 
-      function deleteMessage(idx) {
+      function deleteMessage(id) {
         var token = getXsrfTokenFromCookie();
         if (!token) {
           alert('Konnte XSRF-TOKEN Cookie nicht auslesen');
@@ -142,7 +142,7 @@
         }
 
         $.ajax({
-          url: 'chat/' + idx,
+          url: 'chat/' + id,
           method: 'DELETE',
           dataType: 'json',
           headers: {

+ 2 - 0
scripts/init.js

@@ -148,6 +148,8 @@ function readJsonFile(path, encoding = 'utf8') {
         pkgJson = yield readJsonFile(path_1.default.resolve(projectRootDir, 'package.json'));
         pkgJson.scripts = Object.assign(Object.assign({}, pkgJson.scripts), { build: 'tsc -b', start: 'npm run build && node dist/index.js' });
         yield util_1.default.promisify(fs_1.default.writeFile)(path_1.default.resolve(projectRootDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
+        console.log(cli.blue('[INFO]'), 'Creating chat data directory');
+        yield util_1.default.promisify(fs_1.default.mkdir)(path_1.default.resolve(projectRootDir, 'chatdata'));
         console.log(cli.blue('[INFO]'), 'Cleanup: Removing .clone directory...');
         yield util_1.default.promisify(fs_1.default.rm)(cloneDir, { force: true, recursive: true });
         console.log();

+ 3 - 0
scripts/init.ts

@@ -141,6 +141,9 @@ async function readJsonFile<T>(path: string, encoding: BufferEncoding = 'utf8'):
     };
     await util.promisify(fs.writeFile)(path.resolve(projectRootDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
 
+    console.log(cli.blue('[INFO]'), 'Creating chat data directory');
+    await util.promisify(fs.mkdir)(path.resolve(projectRootDir, 'chatdata'));
+
     console.log(cli.blue('[INFO]'), 'Cleanup: Removing .clone directory...');
     await util.promisify(fs.rm)(cloneDir, { force: true, recursive: true });
 

+ 32 - 8
src/controllers/chat-controller.class.ts

@@ -1,40 +1,64 @@
+import fs from 'fs';
+import path from 'path';
+import util from 'util';
+
 import { ControllerBase } from './lib/controller-base.class';
 import { ControllerPool } from './lib/controller-pool.interface';
 
 export interface ChatMessage {
+  id?: string;
   time: Date;
   author: String;
   text: string;
 }
 
-/** Example implementation of a data controller.
+/** Example file system based implementation of a data controller.
  *
  * Could also be a database client implementation of any kind to store and receive data.
  */
 export class ChatController extends ControllerBase {
-  private _messages: ChatMessage[] = [];
+  private _messages?: ChatMessage[];
 
   constructor(ctrlPool: ControllerPool) {
     super(ctrlPool);
   }
 
-  public addMessage(author: string, text: string) {
+  public async addMessage(author: string, text: string) {
+    const id = new Date().getTime().toFixed(0);
     const msg: ChatMessage = {
       author,
       text,
       time: new Date()
     };
-    this._messages.push(msg);
+    await util.promisify(fs.writeFile)(path.resolve(process.env.CHATDATA_DIR, id), JSON.stringify(msg, null, 2));
+    msg.id = id;
+    const messages = await this.getMessages();
+    messages.push(msg);
     return msg;
   }
 
-  public getMessages() {
+  public async getMessages() {
+    if (!this._messages) {
+      const dirEntries = await util.promisify(fs.readdir)(process.env.CHATDATA_DIR);
+      const messages = [];
+      for (const entry of dirEntries) {
+        const file = path.resolve(process.env.CHATDATA_DIR, entry);
+        const json = await util.promisify(fs.readFile)(file, { encoding: 'utf-8' });
+        const message = JSON.parse(json) as ChatMessage;
+        message.id = entry;
+        messages.push(message);
+      }
+      this._messages = messages;
+    }
     return this._messages;
   }
 
-  public deleteMessage(idx: number) {
-    if (this._messages.length > idx) {
-      this._messages.splice(idx, 1);
+  public async deleteMessage(id: string) {
+    const messages = await this.getMessages();
+    const idx = messages.findIndex(msg => msg.id === id);
+    if (idx >= 0) {
+      messages.splice(idx, 1);
+      await util.promisify(fs.unlink)(path.resolve(process.env.CHATDATA_DIR, id));
       return true;
     }
     return false;

+ 7 - 7
src/handlers/private-handler.class.ts

@@ -13,31 +13,31 @@ export class PrivateHandler extends RequestHandler {
     /** Enable XSRF Token Security on this router path */
     this.router.use(this.csrf());
 
-    this.router.get('/chat', (req, res, next) => {
+    this.router.get('/chat', async (req, res, next) => {
       try {
-        res.send(this.chat.getMessages());
+        res.send(await this.chat.getMessages());
       } catch (err) {
         next(err);
       }
     });
 
-    this.router.post('/chat', (req, res, next) => {
+    this.router.post('/chat', async (req, res, next) => {
       try {
         const author = req.session.user;
         const text = req.body.text;
 
-        const msg = this.chat.addMessage(author, text);
+        const msg = await this.chat.addMessage(author, text);
         res.status(201).send(msg);
       } catch (err) {
         next(err);
       }
     });
 
-    this.router.delete('/chat/:idx', (req, res, next) => {
+    this.router.delete('/chat/:id', async (req, res, next) => {
       try {
-        const index = Number(req.params.idx);
+        const id = req.params.id;
 
-        const deleted = this.chat.deleteMessage(index);
+        const deleted = await this.chat.deleteMessage(id);
         res.status(200).send({ deleted });
       } catch (err) {
         next(err);