redeem proffered tickets, db schema 1.1.0
[squeep-indie-auther] / src / db / postgres / index.js
index 5899a26fb436fd7a2bd708cfa1d3f129b350a346..535a90153c50170662d1f57f9a1772c01c94c690 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: 1,
     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 })));
         });
@@ -235,6 +238,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) {
+        throw new DBErrors.UnexpectedResult('did not upsert almanac event');
+      }
+    } 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 });
@@ -464,7 +483,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) => {
 
@@ -543,7 +562,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) => {
 
@@ -633,6 +652,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) {
+        throw new DBErrors.UnexpectedResult('did not update almanac');
+      }
+    } 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;