From 235115168f1c2c74213c96aeb9d25f8d54c517ec Mon Sep 17 00:00:00 2001 From: Jonas Lochmann Date: Mon, 15 Jun 2026 02:00:00 +0200 Subject: [PATCH] add unpaid14 to the premium unlock api --- docs/api/admin.md | 1 + src/api/admin.ts | 10 ++++++-- src/database/purchase.ts | 6 ++--- src/function/purchase/add-purchase.ts | 36 ++++++++++++++++++++++----- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/docs/api/admin.md b/docs/api/admin.md index a713342..2ede89d 100644 --- a/docs/api/admin.md +++ b/docs/api/admin.md @@ -101,6 +101,7 @@ request properties: ``purchaseToken``, ``purchaseId`` and ``dryRun`` - ``purchasetoken`` is a string which the client shows at the purchase screen - ``purchaseId`` is the ID that is used at the bill - ``dryRun`` is a boolean; setting true will skip the actual unlocking +- ``type`` is a string and must be ``year``, ``month`` or ``unpaid14`` ### response diff --git a/src/api/admin.ts b/src/api/admin.ts index fec059a..b980b07 100644 --- a/src/api/admin.ts +++ b/src/api/admin.ts @@ -141,7 +141,8 @@ export const createAdminRouter = ({ database, websocket, eventHandler }: { typeof req.body !== 'object' || typeof req.body.purchaseToken !== 'string' || typeof req.body.purchaseId !== 'string' || - typeof req.body.dryRun !== 'boolean' + typeof req.body.dryRun !== 'boolean' || + typeof req.body.type !== 'string' ) { throw new BadRequest() } @@ -149,6 +150,11 @@ export const createAdminRouter = ({ database, websocket, eventHandler }: { const purchaseToken: string = req.body.purchaseToken const purchaseId: string = req.body.purchaseId const dryRun: boolean = req.body.dryRun + const type: string = req.body.type + + if (type !== 'month' && type !== 'year' && type !== 'unpaid14') { + throw new BadRequest() + } const tokenContent = await verifyIdentitifyToken(purchaseToken) @@ -259,7 +265,7 @@ export const createAdminRouter = ({ database, websocket, eventHandler }: { await addPurchase({ database, familyId: tokenContent.familyId, - type: 'year', + type, service: 'directpurchase', transactionId: purchaseId, websocket, diff --git a/src/database/purchase.ts b/src/database/purchase.ts index e85692d..44cc8f0 100644 --- a/src/database/purchase.ts +++ b/src/database/purchase.ts @@ -1,6 +1,6 @@ /* * server component for the TimeLimit App - * Copyright (C) 2019 - 2022 Jonas Lochmann + * Copyright (C) 2019 - 2026 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -23,7 +23,7 @@ export interface PurchaseAttributes { familyId: string service: 'googleplay' | 'directpurchase' transactionId: string - type: 'month' | 'year' + type: 'month' | 'year' | 'unpaid14' loggedAt: string previousFullVersionEndTime: string newFullVersionEndTime: string @@ -44,7 +44,7 @@ export const attributes: SequelizeAttributes = { type: Sequelize.STRING, primaryKey: true }, - type: createEnumColumn(['month', 'year']), + type: createEnumColumn(['month', 'year', 'unpaid14']), loggedAt: { ...timestampColumn }, previousFullVersionEndTime: { ...timestampColumn }, newFullVersionEndTime: { ...timestampColumn } diff --git a/src/function/purchase/add-purchase.ts b/src/function/purchase/add-purchase.ts index e2f2168..a676b85 100644 --- a/src/function/purchase/add-purchase.ts +++ b/src/function/purchase/add-purchase.ts @@ -21,13 +21,14 @@ import { notifyClientsAboutChangesDelayed } from '../../function/websocket' import { WebsocketApi } from '../../websocket' const day = 1000 * 60 * 60 * 24 +const week = day * 7 const month = day * 31 const year = day * 366 export const addPurchase = async ({ database, familyId, type, service, transactionId, websocket, transaction }: { database: Database familyId: string - type: 'month' | 'year' + type: 'month' | 'year' | 'unpaid14' service: 'googleplay' | 'directpurchase' transactionId: string websocket: WebsocketApi @@ -59,16 +60,39 @@ export const addPurchase = async ({ database, familyId, type, service, transacti const previousFullVersionEndTime = familyEntry.fullVersionUntil const previousFullVersionDebts = parseInt(familyEntry.fullVersionDebts, 10) - const typeDuration = type === 'year' ? year : month + if (type === 'month' || type === 'year') { + const typeDuration = type === 'year' ? year : month - if (typeDuration > previousFullVersionDebts) { - const newFullVersionUntil = Math.max(parseInt(familyEntry.fullVersionUntil, 10), Date.now()) + typeDuration - previousFullVersionDebts + if (typeDuration > previousFullVersionDebts) { + const newFullVersionUntil = Math.max(parseInt(familyEntry.fullVersionUntil, 10), Date.now()) + typeDuration - previousFullVersionDebts + + familyEntry.fullVersionUntil = newFullVersionUntil.toString(10) + familyEntry.fullVersionDebts = '0' + familyEntry.hasFullVersion = true + } else { + familyEntry.fullVersionDebts = (previousFullVersionDebts - typeDuration).toString(10) + } + } else if (type === 'unpaid14') { + const debtsAdd = 2 * week + const debtsMax = 3 * week + + const newDebts = Math.min(debtsMax, previousFullVersionDebts + debtsAdd) + + if (newDebts <= previousFullVersionDebts) { + // do not save anything + + return + } + + const durationToAdd = newDebts - previousFullVersionDebts + + const newFullVersionUntil = Math.max(parseInt(familyEntry.fullVersionUntil, 10), Date.now()) + durationToAdd familyEntry.fullVersionUntil = newFullVersionUntil.toString(10) - familyEntry.fullVersionDebts = '0' + familyEntry.fullVersionDebts = newDebts.toString(10) familyEntry.hasFullVersion = true } else { - familyEntry.fullVersionDebts = (previousFullVersionDebts - typeDuration).toString(10) + throw new Error() } await familyEntry.save({ transaction })