fix tip record creation, migrate DB

This commit is contained in:
Florian Dold 2020-11-16 14:12:37 +01:00
parent cdf5cc583c
commit 292160f7e9
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
7 changed files with 89 additions and 29 deletions

View File

@ -1,6 +1,7 @@
import { Stores } from "./types/dbTypes";
import { openDatabase, Database, Store, Index } from "./util/query";
import { IDBFactory, IDBDatabase } from "idb-bridge";
import { IDBFactory, IDBDatabase, IDBObjectStore, IDBTransaction } from "idb-bridge";
import { Logger } from './util/logging';
/**
* Name of the Taler database. This is effectively the major
@ -17,7 +18,9 @@ const TALER_DB_NAME = "taler-wallet-prod-v1";
* backwards-compatible way or object stores and indices
* are added.
*/
export const WALLET_DB_MINOR_VERSION = 1;
export const WALLET_DB_MINOR_VERSION = 2;
const logger = new Logger("db.ts");
/**
* Return a promise that resolves
@ -31,24 +34,45 @@ export function openTalerDatabase(
db: IDBDatabase,
oldVersion: number,
newVersion: number,
upgradeTransaction: IDBTransaction,
): void => {
switch (oldVersion) {
case 0: // DB does not exist yet
for (const n in Stores) {
if ((Stores as any)[n] instanceof Store) {
const si: Store<any> = (Stores as any)[n];
const s = db.createObjectStore(si.name, si.storeParams);
for (const indexName in si as any) {
if ((si as any)[indexName] instanceof Index) {
const ii: Index<any, any> = (si as any)[indexName];
s.createIndex(ii.indexName, ii.keyPath, ii.options);
}
if (oldVersion === 0) {
for (const n in Stores) {
if ((Stores as any)[n] instanceof Store) {
const si: Store<any> = (Stores as any)[n];
const s = db.createObjectStore(si.name, si.storeParams);
for (const indexName in si as any) {
if ((si as any)[indexName] instanceof Index) {
const ii: Index<any, any> = (si as any)[indexName];
s.createIndex(ii.indexName, ii.keyPath, ii.options);
}
}
}
break;
default:
throw Error("unsupported existig DB version");
}
return;
}
if (oldVersion === newVersion) {
return;
}
logger.info(`upgrading database from ${oldVersion} to ${newVersion}`);
for (const n in Stores) {
if ((Stores as any)[n] instanceof Store) {
const si: Store<any> = (Stores as any)[n];
let s: IDBObjectStore;
if ((si.storeParams?.versionAdded ?? 1) > oldVersion) {
s = db.createObjectStore(si.name, si.storeParams);
} else {
s = upgradeTransaction.objectStore(si.name);
}
for (const indexName in si as any) {
if ((si as any)[indexName] instanceof Index) {
const ii: Index<any, any> = (si as any)[indexName];
if ((ii.options?.versionAdded ?? 0) > oldVersion) {
s.createIndex(ii.indexName, ii.keyPath, ii.options);
}
}
}
}
}
};

View File

@ -46,6 +46,7 @@ import { Logger } from "../util/logging";
import { checkDbInvariant } from "../util/invariants";
import { TalerErrorCode } from "../TalerErrorCode";
import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries";
import { j2s } from '../util/helpers';
const logger = new Logger("operations/tip.ts");
@ -68,7 +69,7 @@ export async function prepareTip(
merchantResp,
codecForTipPickupGetResponse(),
);
logger.trace(`status ${tipPickupStatus}`);
logger.trace(`status ${j2s(tipPickupStatus)}`);
const amount = Amounts.parseOrThrow(tipPickupStatus.tip_amount);
@ -80,6 +81,7 @@ export async function prepareTip(
]);
if (!tipRecord) {
logger.trace("new tip, creating tip record");
await updateExchangeFromUrl(ws, tipPickupStatus.exchange_url);
const withdrawDetails = await getExchangeWithdrawalInfo(
ws,
@ -232,7 +234,7 @@ async function processTipImpl(
}));
const tipStatusUrl = new URL(
`/tips/${tipRecord.merchantTipId}/pickup`,
`tips/${tipRecord.merchantTipId}/pickup`,
tipRecord.merchantBaseUrl,
);

View File

@ -361,7 +361,6 @@ export enum DenominationStatus {
VerifiedBad,
}
/**
* Denomination record as stored in the wallet's database.
*/
@ -640,7 +639,7 @@ export interface PlanchetRecord {
/**
* Public key of the reserve that this planchet
* is being withdrawn from.
*
*
* Can be the empty string (non-null/undefined for DB indexing)
* if this is a tipping reserve.
*/
@ -1532,7 +1531,6 @@ export enum ImportPayloadType {
CoreSchema = "core-schema",
}
class ExchangesStore extends Store<ExchangeRecord> {
constructor() {
super("exchanges", { keyPath: "baseUrl" });
@ -1624,6 +1622,15 @@ class TipsStore extends Store<TipRecord> {
constructor() {
super("tips", { keyPath: "walletTipId" });
}
// Added in version 2
byMerchantTipIdAndBaseUrl = new Index<[string, string], TipRecord>(
this,
"tipsByMerchantTipIdAndOriginIndex",
["merchantTipId", "merchantBaseUrl"],
{
versionAdded: 2,
}
);
}
class WithdrawalGroupsStore extends Store<WithdrawalGroupRecord> {

View File

@ -146,3 +146,7 @@ export function strcmp(s1: string, s2: string): number {
}
return 0;
}
export function j2s(x: any): string {
return JSON.stringify(x, undefined, 2);
}

View File

@ -44,14 +44,25 @@ const logger = new Logger("query.ts");
*/
export const TransactionAbort = Symbol("transaction_abort");
export interface StoreParams<T> {
validator?: (v: T) => T;
autoIncrement?: boolean;
keyPath?: string | string[] | null;
/**
* Database version that this store was added in, or
* undefined if added in the first version.
*/
versionAdded?: number;
}
/**
* Definition of an object store.
*/
export class Store<T> {
constructor(
public name: string,
public storeParams?: IDBObjectStoreParameters,
public validator?: (v: T) => T,
public storeParams?: StoreParams<T>,
) {}
}
@ -66,6 +77,12 @@ export interface IndexOptions {
* Defaults to false.
*/
multiEntry?: boolean;
/**
* Database version that this store was added in, or
* undefined if added in the first version.
*/
versionAdded?: number;
}
function requestToPromise(req: IDBRequest): Promise<any> {
@ -425,6 +442,7 @@ export function openDatabase(
db: IDBDatabase,
oldVersion: number,
newVersion: number,
upgradeTransaction: IDBTransaction,
) => void,
): Promise<IDBDatabase> {
return new Promise<IDBDatabase>((resolve, reject) => {
@ -449,7 +467,11 @@ export function openDatabase(
if (!newVersion) {
throw Error("upgrade needed, but new version unknown");
}
onUpgradeNeeded(db, e.oldVersion, newVersion);
const transaction = req.transaction;
if (!transaction) {
throw Error("no transaction handle available in upgrade handler");
}
onUpgradeNeeded(db, e.oldVersion, newVersion, transaction);
};
});
}

View File

@ -14,6 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import { canonicalizeBaseUrl } from './helpers';
import { URLSearchParams } from "./url";
export interface PayUriResult {
@ -59,7 +60,7 @@ export function parseWithdrawUri(s: string): WithdrawUriResult | undefined {
const p = [host, ...pathSegments].join("/");
return {
bankIntegrationApiBaseUrl: `${pi.innerProto}://${p}/`,
bankIntegrationApiBaseUrl: canonicalizeBaseUrl(`${pi.innerProto}://${p}/`),
withdrawalOperationId: withdrawId,
};
}
@ -155,7 +156,7 @@ export function parsePayUri(s: string): PayUriResult | undefined {
const orderId = parts[parts.length - 2];
const pathSegments = parts.slice(1, parts.length - 2);
const p = [host, ...pathSegments].join("/");
const merchantBaseUrl = `${pi.innerProto}://${p}/`;
const merchantBaseUrl = canonicalizeBaseUrl(`${pi.innerProto}://${p}/`);
return {
merchantBaseUrl,
@ -183,7 +184,7 @@ export function parseTipUri(s: string): TipUriResult | undefined {
const tipId = parts[parts.length - 1];
const pathSegments = parts.slice(1, parts.length - 1);
const p = [host, ...pathSegments].join("/");
const merchantBaseUrl = `${pi.innerProto}://${p}/`;
const merchantBaseUrl = canonicalizeBaseUrl(`${pi.innerProto}://${p}/`);
return {
merchantBaseUrl,
@ -210,7 +211,7 @@ export function parseRefundUri(s: string): RefundUriResult | undefined {
const orderId = parts[parts.length - 2];
const pathSegments = parts.slice(1, parts.length - 2);
const p = [host, ...pathSegments].join("/");
const merchantBaseUrl = `${pi.innerProto}://${p}/`;
const merchantBaseUrl = canonicalizeBaseUrl(`${pi.innerProto}://${p}/`);
return {
merchantBaseUrl,

View File

@ -233,7 +233,7 @@ importers:
taler-wallet-core: 'workspace:*'
tslib: ^2.0.1
typescript: ^3.9.7
lockfileVersion: 5.1
lockfileVersion: 5.2
packages:
/@ava/typescript/1.1.1:
dependencies: