update depedencies, changes to support updated authentication-module
[squeep-indie-auther] / src / db / postgres / index.js
index 5899a26fb436fd7a2bd708cfa1d3f129b350a346..8995ab189cfe7a6cc9888dd1d9c8388aedc943d1 100644 (file)
@@ -7,18 +7,19 @@ const pgpInitOptions = {
 
 const path = require('path');
 const pgp = require('pg-promise')(pgpInitOptions);
-const svh = require('../schema-version-helper');
+const { unappliedSchemaVersions } = require('../schema-version-helper');
 const Database = require('../abstract');
 const DBErrors = require('../errors');
 const common = require('../../common');
+const Enum = require('../../enum');
 
 const _fileScope = common.fileScope(__filename);
 
 const PGTypeIdINT8 = 20; // Type Id 20 == INT8 (BIGINT)
-const PGTYpeIdINT8Array = 1016; //Type Id 1016 == INT8[] (BIGINT[])
+const PGTypeIdINT8Array = 1016; //Type Id 1016 == INT8[] (BIGINT[])
 pgp.pg.types.setTypeParser(PGTypeIdINT8, BigInt); // Type Id 20 = INT8 (BIGINT)
-const parseBigIntArray = pgp.pg.types.getTypeParser(PGTYpeIdINT8Array); // Type Id 1016 = INT8[] (BIGINT[])
-pgp.pg.types.setTypeParser(PGTYpeIdINT8Array, (a) => parseBigIntArray(a).map(BigInt));
+const parseBigIntArray = pgp.pg.types.getTypeParser(PGTypeIdINT8Array); // Type Id 1016 = INT8[] (BIGINT[])
+pgp.pg.types.setTypeParser(PGTypeIdINT8Array, (a) => parseBigIntArray(a).map(BigInt));
 
 const schemaVersionsSupported = {
   min: {
@@ -28,7 +29,7 @@ const schemaVersionsSupported = {
   },
   max: {
     major: 1,
-    minor: 0,
+    minor: 2,
     patch: 0,
   },
 };
@@ -142,7 +143,7 @@ class DatabasePostgres extends Database {
 
     // Apply migrations
     const currentSchema = await this._currentSchema();
-    const migrationsWanted = svh.unappliedSchemaVersions(__dirname, currentSchema, this.schemaVersionsSupported);
+    const migrationsWanted = unappliedSchemaVersions(__dirname, currentSchema, this.schemaVersionsSupported);
     this.logger.debug(_scope, 'schema migrations wanted', { migrationsWanted });
     for (const v of migrationsWanted) {
       const fPath = path.join(__dirname, 'sql', 'schema', v, 'apply.sql');
@@ -197,9 +198,11 @@ class DatabasePostgres extends Database {
       if (really) {
         await this.db.tx(async (t) => {
           await t.batch([
+            'almanac',
             'authentication',
-            'resource',
             'profile',
+            'redeemed_ticket',
+            'resource',
             'token',
           ].map(async (table) => t.query('TRUNCATE TABLE $(table:name) CASCADE', { table })));
         });
@@ -222,6 +225,11 @@ class DatabasePostgres extends Database {
   }
 
 
+  static _almanacErrorThrow() {
+    throw new DBErrors.UnexpectedResult('did not update almanac');
+  }
+
+
   async almanacGetAll(dbCtx) {
     const _scope = _fileScope('almanacGetAll');
     this.logger.debug(_scope, 'called');
@@ -235,6 +243,22 @@ class DatabasePostgres extends Database {
   }
 
 
+  async almanacUpsert(dbCtx, event, date) {
+    const _scope = _fileScope('almanacUpsert');
+    this.logger.debug(_scope, 'called', { event, date });
+
+    try {
+      const result = await dbCtx.result(this.statement.almanacUpsert, { event, date: date ?? new Date() });
+      if (result.rowCount != 1) {
+        this.constructor._almanacErrorThrow();
+      }
+    } catch (e) {
+      this.logger.error(_scope, 'failed', { error: e, event, date });
+      throw e;
+    }
+  }
+
+
   async authenticationGet(dbCtx, identifier) {
     const _scope = _fileScope('authenticationGet');
     this.logger.debug(_scope, 'called', { identifier });
@@ -264,20 +288,56 @@ class DatabasePostgres extends Database {
   }
 
 
-  async authenticationUpsert(dbCtx, identifier, credential) {
+  async authenticationUpsert(dbCtx, identifier, credential, otpKey) {
     const _scope = _fileScope('authenticationUpsert');
     const scrubbedCredential = '*'.repeat((credential || '').length);
-    this.logger.debug(_scope, 'called', { identifier, scrubbedCredential });
+    const scrubbedOTPKey = '*'.repeat((otpKey || '').length);
+    this.logger.debug(_scope, 'called', { identifier, scrubbedCredential, scrubbedOTPKey });
 
     try {
-      const result = await dbCtx.result(this.statement.authenticationUpsert, { identifier, credential });
+      const result = await dbCtx.result(this.statement.authenticationUpsert, { identifier, credential, otpKey });
       if (result.rowCount != 1) {
         throw new DBErrors.UnexpectedResult('did not upsert authentication');
       }
+    } catch (e) {
+      this.logger.error(_scope, 'failed', { error: e, identifier, scrubbedCredential, scrubbedOTPKey });
+      throw e;
+    }
+  }
+
+
+  async authenticationUpdateOTPKey(dbCtx, identifier, otpKey = null) {
+    const _scope = _fileScope('authenticationUpdateOTPKey');
+    const scrubbedOTPKey = '*'.repeat((otpKey || '').length);
+    this.logger.debug(_scope, 'called', { identifier, scrubbedOTPKey });
+
+    try {
+      const result = await dbCtx.result(this.statement.authenticationUpdateOtpKey, { identifier, otpKey });
+      if (result.rowCount != 1) {
+        throw new DBErrors.UnexpectedResult('did not update otpKey');
+      }
+    } catch (e) {
+      this.logger.error(_scope, 'failed', { error: e, identifier, scrubbedOTPKey });
+      throw e;
+    }
+  }
+
+
+  async authenticationUpdateCredential(dbCtx, identifier, credential) {
+    const _scope = _fileScope('authenticationUpdateCredential');
+    const scrubbedCredential = '*'.repeat((credential || '').length);
+    this.logger.debug(_scope, 'called', { identifier, scrubbedCredential });
+
+    try {
+      const result = await dbCtx.result(this.statement.authenticationUpdateCredential, { identifier, credential });
+      if (result.rowCount != 1) {
+        throw new DBErrors.UnexpectedResult('did not update credential');
+      }
     } catch (e) {
       this.logger.error(_scope, 'failed', { error: e, identifier, scrubbedCredential });
       throw e;
     }
+
   }
 
 
@@ -464,7 +524,7 @@ class DatabasePostgres extends Database {
     const _scope = _fileScope('scopeCleanup');
     this.logger.debug(_scope, 'called', { atLeastMsSinceLast });
 
-    const almanacEvent = 'scopeCleanup';
+    const almanacEvent = Enum.AlmanacEntry.ScopeCleanup;
     try {
       return await this.transaction(dbCtx, async (txCtx) => {
 
@@ -483,7 +543,7 @@ class DatabasePostgres extends Database {
         // Update the last cleanup time
         const result = await txCtx.result(this.statement.almanacUpsert, { event: almanacEvent, date: now });
         if (result.rowCount != 1) {
-          throw new DBErrors.UnexpectedResult('did not update almanac');
+          this.constructor._almanacErrorThrow();
         }
 
         this.logger.debug(_scope, 'completed', { scopesRemoved, atLeastMsSinceLast });
@@ -543,7 +603,7 @@ class DatabasePostgres extends Database {
     const _scope = _fileScope('tokenCleanup');
     this.logger.debug(_scope, 'called', { codeLifespanSeconds, atLeastMsSinceLast });
 
-    const almanacEvent = 'tokenCleanup';
+    const almanacEvent = Enum.AlmanacEntry.TokenCleanup;
     try {
       return await this.transaction(dbCtx, async (txCtx) => {
 
@@ -562,7 +622,7 @@ class DatabasePostgres extends Database {
         // Update the last cleanup time
         const result = await txCtx.result(this.statement.almanacUpsert, { event: almanacEvent, date: now });
         if (result.rowCount != 1) {
-          throw new DBErrors.UnexpectedResult('did not update almanac');
+          this.constructor._almanacErrorThrow();
         }
 
         this.logger.debug(_scope, 'completed', { tokensRemoved, codeLifespanSeconds, atLeastMsSinceLast });
@@ -633,6 +693,55 @@ class DatabasePostgres extends Database {
     }
   }
 
+
+  async ticketRedeemed(dbCtx, redeemedData) {
+    const _scope = _fileScope('ticketRedeemed');
+    this.logger.debug(_scope, 'called', { ...redeemedData });
+
+    try {
+      const result = await dbCtx.result(this.statement.ticketRedeemed, redeemedData);
+      if (result.rowCount != 1) {
+        throw new DBErrors.UnexpectedResult('did not store redeemed ticket');
+      }
+    } catch (e) {
+      this.logger.error(_scope, 'failed', { error: e, ...redeemedData });
+      throw e;
+    }
+  }
+
+
+  async ticketTokenPublished(dbCtx, redeemedData) {
+    const _scope = _fileScope('ticketRedeemed');
+    this.logger.debug(_scope, 'called', { ...redeemedData });
+
+    const almanacEvent = Enum.AlmanacEntry.TicketPublished;
+    try {
+      const result = await dbCtx.result(this.statement.ticketTokenPublished, redeemedData);
+      if (result.rowCount != 1) {
+        throw new DBErrors.UnexpectedResult('did not store redeemed ticket');
+      }
+      const almanacResult = await dbCtx.result(this.statement.almanacUpsert, { event: almanacEvent, date: new Date() });
+      if (almanacResult.rowCount != 1) {
+        this.constructor._almanacErrorThrow();
+      }
+    } catch (e) {
+      this.logger.error(_scope, 'failed', { error: e, ...redeemedData });
+      throw e;
+    }
+  }
+
+  async ticketTokenGetUnpublished(dbCtx) {
+    const _scope = _fileScope('ticketTokenGetUnpublished');
+    this.logger.debug(_scope, 'called');
+
+    try {
+      return await dbCtx.manyOrNone(this.statement.ticketTokenGetUnpublished);
+    } catch (e) {
+      this.logger.error(_scope, 'failed', { error: e });
+      throw e;
+    }
+  }
+
 }
 
 module.exports = DatabasePostgres;