// @ts-check
/**
* @import {ExpressRequestAuthorized, ExpressResponse} from '../../types.js'
*/
import {query,transaction,UUID2hex,HEX2uuid,HEX2base64} from '@commtool/sql-query'
import { filterObjects } from '../../utils/objectfilter/filters/index.js'
import {queueAdd} from '../../tree/treeQueue/treeQueue.js'
/**
* Consolidates all achievement UIDs for a person into their Data object.
*
* This function fetches all achievements linked to a specific person, encodes their UIDs in base64 format,
* and stores this consolidated array in the person's Data object. This approach enables faster filtering
* and querying of persons based on their achievements without needing to perform complex joins each time.
*
* The function only updates the person's record if the achievements data has actually changed,
* minimizing unnecessary database operations.
*
* @param {Buffer} UID - The unique identifier (hex string or Buffer) of the person whose achievements are to be consolidated.
* @returns {Promise<void>} A promise that resolves when the consolidation is complete.
* @async
*/
export const updateAchievements=async (UID)=>
{
// updates the achievements for a person with the provided UID
const persons= await query(`
SELECT person.Data,target.targetData, UNIX_TIMESTAMP(person.validFrom) AS validFrom, person.UID FROM ObjectBase AS person INNER JOIN
(
SELECT JSON_ARRAYAGG(TO_BASE64(Links.UIDTarget)) AS targetData, ObjectBase.UIDBelongsTo
FROM ObjectBase INNER JOIN Links ON (Links.UID=ObjectBase.UID AND Links.Type='achievement')
INNER JOIN ObjectBase AS Template ON (Template.UID=Links.UIDTarget)
WHERE ObjectBase.Type='achievement'
AND ( ObjectBase.dindex IS NULL)
GROUP BY ObjectBase.UIDBelongsTo) AS target
ON(target.UIDBelongsTo=person.UID)
WHERE person.UID=? AND person.Type IN ('person','extern')
`,[UID],
{
cast:['json'],
log:false
}
)
const updated=new Set()
if(persons.length>0)
{
const person=persons[0]
if(!person.Data.achievements || JSON.stringify(person.Data.achievements)!==JSON.stringify(person.targetData))
{
await query(`UPDATE ObjectBase SET Data=? WHERE UID=? `,
[JSON.stringify({achievements:person.targetData}), person.UID],
{backDate:person.validFrom})
updated.add(HEX2base64(person.UID))
}
}
}
/**
* Expires achievements based on their renewal period
*
* This function checks all achievements linked to a membership organization (root entity)
* and expires those whose renewal period has elapsed. For each expired achievement,
* it updates the dindex to mark it as expired and then updates the achievement lists
* for affected persons.
*
* @param {string|Buffer} root - The UID of the membership organization containing the achievements
* @param {boolean} [complete=false] - If true, processes all achievements regardless of
* current expiration status
* @returns {Promise<void>} A promise that resolves when all achievements have been processed
*
* @async
* @throws Will throw an error if database operations fail
*/
export const expireAchievements=async (root,complete=false)=>
{
root=UUID2hex(root)
// we need a Buffer version of root for queue operations
const rootBuf = HEX2uuid(root)
const [{UID:sysUser}]= await query(`SELECT ObjectBase.UID
FROM Member AS SysUser
INNER JOIN ObjectBase ON (SysUser.UID=ObjectBase.UIDBelongsTo AND ObjectBase.Type='extern')
INNER JOIN Links ON (ObjectBase.UID= Links.UID AND Links.UIDTarget=? AND Links.Type = 'memberSys')
`,
[root],
)
const aas= await query(`SELECT achievement.UID,achievement.UIDBelongsTo,
achievement.dindex,JSON_VALUE(achievementT.Data,'$.renewal') AS renewal,UNIX_TIMESTAMP(achievement.validFrom) AS validFrom
FROM ObjectBase AS achievement
INNER JOIN Links AS aLink ON (aLink.UID=achievement.UID AND aLink.Type='achievement')
INNER JOIN ObjectBase AS achievementT ON (achievementT.UID=aLink.UIDTarget AND achievementT.Type='achievementT')
WHERE achievement.Type='achievement'
AND achievementT.UIDBelongsTo=?`,
[root],
{cast:['json']})
const updatedPersons=new Set()
await Promise.all( aas.map( achievement=>
(
new Promise(async (resolve,reject)=>
{
// do not expire, if renewal =0 or negative
const expire=parseInt(achievement.renewal) >0 ?
Math.floor(parseInt(achievement.renewal)-(Date.now()-achievement.validFrom*1000)/(24*3600000)):
Number.MAX_SAFE_INTEGER
// days to expire - (actual Date in ms - achievemnt date in sec* 1000) / millisceonds for one day
// days to expire - days passed since achievement
// >0: expired since number of days <0 still valid for result days
const dindex=expire<0 ? 1 : null
// dindex 1 means expired
if((achievement.dindex !== dindex || (complete && dindex)))
{
const pKey=HEX2base64(achievement.UIDBelongsTo)
// we have to do it like this to preserve validFrom
updatedPersons.add(pKey)
if(achievement.dindex !== dindex)
{
query(`UPDATE ObjectBase SET dindex=${dindex} WHERE UID=?`,
[achievement.UID],
{backDate:achievement.validFrom}
)
}
}
resolve()
})
)))
for(const person64 of updatedPersons)
{
const UIDperson= Buffer.from(person64,'base64')
await updateAchievements( UIDperson)
// update lists
await queueAdd(root,sysUser,'listMember',UIDperson,UIDperson,null,UIDperson)
}
}
/**
* Checks if a user is authorized to manage achievements.
*
* @async
* @param {string} user - The user's ID to check for authorization.
* @param {Object|null} filter - The filter to apply on user's jobs.
* If null or undefined, user is automatically authorized.
* @returns {Promise<boolean>} - Returns true if user is authorized, false otherwise.
* @description The function retrieves all jobs associated with the user from the database,
* applies the provided filter to these jobs, and determines if the user has
* the necessary permissions to manage achievements for a person. This is used
* to control who can add, modify, or delete achievement records based on their
* job roles and permissions.
*/
export const authorizeUser=async (user,filter)=>
{
// checks if current user is authorized to change this data
if(!filter)
{
return true
}
else
{
// get all jobs of the user and run them through the authorized filter
const userJobs= await query(`
SELECT ObjectBase.UID,ObjectBase.Data AS ExtraData,ObjectBase.Type,
Main.Data AS MainBaseData, Member.Data
FROM ObjectBase
INNER JOIN Member ON (Member.UID=ObjectBase.UIDBelongsTo)
INNER JOIN ObjectBase AS Main ON (Main.UID=ObjectBase.UIDBelongsTo)
WHERE ObjectBase.Type='job' AND ObjectBase.UIDBelongsTo=?`,
[UUID2hex(user)],
{cast:['json']}
)
const filteredJobs=filterObjects(userJobs,{job:filter})
if(filteredJobs.length)
return true
}
return false
}