mirror of
https://codeberg.org/timelimit/timelimit-server.git
synced 2025-10-04 18:29:42 +02:00
Add new purchase API
This commit is contained in:
parent
35acd05d08
commit
58a6359a3e
18 changed files with 534 additions and 22 deletions
153
src/api/admin.ts
153
src/api/admin.ts
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* server component for the TimeLimit App
|
||||
* Copyright (C) 2019 - 2020 Jonas Lochmann
|
||||
* Copyright (C) 2019 - 2022 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
|
||||
|
@ -18,11 +18,13 @@
|
|||
import { json } from 'body-parser'
|
||||
import { Router } from 'express'
|
||||
import { BadRequest, Conflict } from 'http-errors'
|
||||
import * as Sequelize from 'sequelize'
|
||||
import { Database } from '../database'
|
||||
import { addPurchase } from '../function/purchase'
|
||||
import { addPurchase, canDoNextPurchase } from '../function/purchase'
|
||||
import { getStatusMessage, setStatusMessage } from '../function/statusmessage'
|
||||
import { EventHandler } from '../monitoring/eventhandler'
|
||||
import { generatePurchaseId } from '../util/token'
|
||||
import { verifyIdentitifyToken, TokenValidationException } from '../util/identity-token'
|
||||
import { WebsocketApi } from '../websocket'
|
||||
|
||||
export const createAdminRouter = ({ database, websocket, eventHandler }: {
|
||||
|
@ -120,7 +122,8 @@ export const createAdminRouter = ({ database, websocket, eventHandler }: {
|
|||
database,
|
||||
familyId: userEntry.familyId,
|
||||
type,
|
||||
transactionId: 'manual-' + type + '-' + generatePurchaseId(),
|
||||
service: 'directpurchase',
|
||||
transactionId: 'legacyunlock-' + type + '-' + generatePurchaseId(),
|
||||
websocket,
|
||||
transaction
|
||||
})
|
||||
|
@ -132,5 +135,149 @@ export const createAdminRouter = ({ database, websocket, eventHandler }: {
|
|||
}
|
||||
})
|
||||
|
||||
router.post('/unlock-premium-v2', json(), async (req, res, next) => {
|
||||
try {
|
||||
if (
|
||||
typeof req.body !== 'object' ||
|
||||
typeof req.body.purchaseToken !== 'string' ||
|
||||
typeof req.body.purchaseId !== 'string'
|
||||
) {
|
||||
throw new BadRequest()
|
||||
}
|
||||
|
||||
const purchaseToken: string = req.body.purchaseToken
|
||||
const purchaseId: string = req.body.purchaseId
|
||||
|
||||
const tokenContent = await verifyIdentitifyToken(purchaseToken)
|
||||
|
||||
if (tokenContent.purpose !== 'purchase') {
|
||||
res.json({ ok: false, error: 'token invalid', detail: 'wrong purpose' })
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const response = await database.transaction(async (transaction) => {
|
||||
const userValid = await database.user.count({
|
||||
where: {
|
||||
familyId: tokenContent.familyId,
|
||||
userId: tokenContent.userId,
|
||||
mail: tokenContent.mail,
|
||||
type: 'parent'
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
if (!userValid) return {
|
||||
ok: false,
|
||||
error: 'token invalid',
|
||||
detail: 'user not found'
|
||||
}
|
||||
|
||||
let mailToReturn: string
|
||||
|
||||
if (tokenContent.mail !== '') mailToReturn = tokenContent.mail
|
||||
else {
|
||||
const userEntryWithMail = await database.user.findOne({
|
||||
where: {
|
||||
familyId: tokenContent.familyId,
|
||||
mail: {
|
||||
[Sequelize.Op.ne]: ''
|
||||
},
|
||||
type: 'parent'
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
if (!userEntryWithMail) return {
|
||||
ok: false,
|
||||
error: 'illegal state',
|
||||
detail: 'no user with mail found'
|
||||
}
|
||||
|
||||
mailToReturn = userEntryWithMail.mail
|
||||
}
|
||||
|
||||
let wasAlreadyExecuted: boolean
|
||||
|
||||
const oldPurchaseByPurchaseId = await database.purchase.findOne({
|
||||
where: {
|
||||
service: 'directpurchase',
|
||||
transactionId: purchaseId
|
||||
}
|
||||
})
|
||||
|
||||
if (oldPurchaseByPurchaseId === null) wasAlreadyExecuted = false
|
||||
else if (oldPurchaseByPurchaseId.familyId === tokenContent.familyId) wasAlreadyExecuted = true
|
||||
else return {
|
||||
ok: false,
|
||||
error: 'purchase id already used'
|
||||
}
|
||||
|
||||
if (!wasAlreadyExecuted) {
|
||||
const familyEntry = await database.family.findOne({
|
||||
where: {
|
||||
familyId: tokenContent.familyId
|
||||
},
|
||||
transaction
|
||||
})
|
||||
|
||||
if (!familyEntry) return {
|
||||
ok: false,
|
||||
error: 'family not found'
|
||||
}
|
||||
|
||||
const canDoPurchase = canDoNextPurchase({ fullVersionUntil: parseInt(familyEntry.fullVersionUntil) })
|
||||
|
||||
if (!canDoPurchase) {
|
||||
const lastPurchase = await database.purchase.findOne({
|
||||
where: {
|
||||
familyId: tokenContent.familyId
|
||||
},
|
||||
transaction,
|
||||
order: [['loggedAt', 'DESC']],
|
||||
limit: 1
|
||||
})
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
error: 'can not renew now',
|
||||
lastPurchase: lastPurchase ? {
|
||||
service: lastPurchase.service,
|
||||
transactionId: lastPurchase.transactionId,
|
||||
timestamp: parseInt(lastPurchase.loggedAt),
|
||||
timestring: new Date(parseInt(lastPurchase.loggedAt)).toISOString()
|
||||
} : undefined
|
||||
}
|
||||
}
|
||||
|
||||
await addPurchase({
|
||||
database,
|
||||
familyId: tokenContent.familyId,
|
||||
type: 'year',
|
||||
service: 'directpurchase',
|
||||
transactionId: purchaseId,
|
||||
websocket,
|
||||
transaction
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
mail: mailToReturn,
|
||||
wasAlreadyExecuted
|
||||
}
|
||||
})
|
||||
|
||||
res.json(response)
|
||||
} catch (ex) {
|
||||
if (ex instanceof TokenValidationException) res.json({
|
||||
ok: false,
|
||||
error: 'token invalid',
|
||||
detail: ex.message
|
||||
})
|
||||
else next(ex)
|
||||
}
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue