fix tip record creation, migrate DB
This commit is contained in:
parent
cdf5cc583c
commit
292160f7e9
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
);
|
||||
|
||||
|
@ -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> {
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user