p2p syncing refactor

This commit is contained in:
mrfry 2023-05-01 16:29:59 +02:00
parent 1a3c806e66
commit 5ececa2de5
6 changed files with 309 additions and 219 deletions

View file

@ -12,4 +12,9 @@ if (!domain) {
export default { export default {
savedQuestionsFileName: 'savedQuestions.json', savedQuestionsFileName: 'savedQuestions.json',
domain: domain, domain: domain,
// --------------------------------------------------------------------------------
// user files
// --------------------------------------------------------------------------------
userFilesDataFileName: '.data.json',
} }

View file

@ -61,11 +61,15 @@ import {
} from '../../../worker/workerPool' } from '../../../worker/workerPool'
import { WorkerResult } from '../../../worker/worker' import { WorkerResult } from '../../../worker/worker'
import { import {
isPeerSameAs,
loginToPeer, loginToPeer,
peerToString, peerToString,
updatePeersFile, updatePeersFile,
} from '../../../utils/p2putils' } from '../../../utils/p2putils'
import { Database } from 'better-sqlite3' import { Database } from 'better-sqlite3'
import constants from '../../../constants'
import path from 'node:path'
import { UserDirDataFile } from './userFiles'
interface MergeResult { interface MergeResult {
newData: Subject[] newData: Subject[]
@ -104,30 +108,31 @@ interface SyncResult {
msg?: string msg?: string
} }
interface SyncDataResBase { interface SyncDataResult {
result?: string
remoteInfo?: RemotePeerInfo remoteInfo?: RemotePeerInfo
users?: {
encryptedUsers: string
} }
questions?: {
interface UserSyncDataRes extends SyncDataResBase { questionDbs: QuestionDb[]
encryptedUsers?: string count: {
}
interface QuestionSyncDataRes extends SyncDataResBase {
questionDbs?: QuestionDb[]
count?: {
qdbs: number qdbs: number
subjects: number subjects: number
questions: number questions: number
} }
} }
userFiles?: {
newFiles: {
[key: string]: {
[key: string]: UserDirDataFile
}
}
}
}
interface NewDataResult { interface NewDataResult {
peer: PeerInfo peer: PeerInfo
result?: { result?: GetResult<SyncDataResult>
questions?: GetResult<QuestionSyncDataRes>
users?: GetResult<UserSyncDataRes>
}
error?: Error error?: Error
} }
@ -374,6 +379,7 @@ async function authAndGetNewData({
shouldSync: { shouldSync: {
questions: boolean questions: boolean
users: boolean users: boolean
userFiles: boolean
} }
}): Promise<NewDataResult> { }): Promise<NewDataResult> {
try { try {
@ -415,32 +421,31 @@ async function authAndGetNewData({
) )
} }
let result: NewDataResult['result'] = {} let result: NewDataResult['result']
const setResult = async () => { const setResult = async () => {
if (shouldSync.questions || syncAll) { let url = `/api/getnewdata?host=${encodeURIComponent(
result.questions = await getData<QuestionSyncDataRes>(
`/api/getnewdatasince?host=${encodeURIComponent(
peerToString(selfInfo) peerToString(selfInfo)
)}${ )}`
peer.lastSync && !allTime
? `&since=${peer.lastSync}` if (allTime) url += '&allTime=true'
: ''
}` if (!syncAll) {
) if (shouldSync.questions) {
url += '&questions=true'
url += `&questionsSince=${peer.lastQuestionsSync}`
}
if (shouldSync.users) {
url += '&users=true'
url += `&usersSince=${peer.lastUsersSync}`
}
if (shouldSync.userFiles) {
url += '&userFiles=true'
url += `&userFilesSince=${peer.lastUserFilesSync}`
}
} }
if (shouldSync.users || syncAll) { result = await getData<SyncDataResult>(url)
result.users = await getData<QuestionSyncDataRes>(
`/api/getnewuserssince?host=${encodeURIComponent(
peerToString(selfInfo)
)}${
peer.lastUsersSync && !allTime
? `&since=${peer.lastUsersSync}`
: ''
}`
)
}
} }
await setResult() await setResult()
@ -486,6 +491,34 @@ function addUsersToDb(
return addedUserCount return addedUserCount
} }
function getNewUserFilesSince(since: number) {
const newData: SyncDataResult['userFiles']['newFiles'] = {}
const dirs = utils.ReadDir(paths.userFilesDir)
dirs.forEach((dir) => {
const userDirPath = path.join(paths.userFilesDir, dir)
const dataFilePath = path.join(
userDirPath,
constants.userFilesDataFileName
)
if (!utils.FileExists(dataFilePath)) {
return
}
newData[dir] = {}
const dataFile =
utils.ReadJSON<Map<string, UserDirDataFile>>(dataFilePath)
Object.entries(dataFile).forEach(([fileName, data]) => {
const mtime = utils.statFile(path.join(userDirPath, fileName)).mtime
if (mtime.getTime() >= since) {
newData[dir][fileName] = data
}
})
})
return newData
}
function setup(data: SubmoduleData): Submodule { function setup(data: SubmoduleData): Submodule {
const { const {
app, app,
@ -696,6 +729,7 @@ function setup(data: SubmoduleData): Submodule {
shouldSync: { shouldSync: {
questions: boolean questions: boolean
users: boolean users: boolean
userFiles: boolean
} }
allTime: boolean allTime: boolean
}) { }) {
@ -772,44 +806,19 @@ function setup(data: SubmoduleData): Submodule {
// filtering, transforming, and counting responses // filtering, transforming, and counting responses
// ------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------
allResults.forEach((res) => { allResults.forEach((res) => {
const errors = res?.error if (res?.error) {
? [{ key: 'all', error: res.error }]
: Object.entries(res.result)
.map(([key, x]) =>
x.error ? { error: x.error, key: key } : null
)
.filter((x) => !!x)
if (errors.length > 0) {
logger.Log( logger.Log(
`\tError syncing with ${peerToString(res.peer)}`, `\tError syncing with ${peerToString(res.peer)}: ${
res.error.message
}`,
'red' 'red'
) )
errors.forEach((e) => {
logger.Log(`\t${e.key}: ${e.error.message}`)
})
} }
}) })
const resultDataWithoutErrors: {
questions?: QuestionSyncDataRes & { peer: PeerInfo }
users?: UserSyncDataRes & { peer: PeerInfo }
}[] = allResults.reduce((acc, resData) => {
const resDataWithoutErrors = Object.entries(resData.result).reduce(
(acc, [key, x]) => {
if (!x.error) {
acc[key] = { ...x.data, peer: resData.peer }
}
return acc
},
{}
)
if (Object.keys(resDataWithoutErrors).length > 0) { const resultDataWithoutErrors = allResults
return [...acc, resDataWithoutErrors] .filter((resData) => !resData.error && resData.result.data)
} else { .map((resData) => resData.result.data)
return acc
}
}, [])
if (resultDataWithoutErrors.length === 0) { if (resultDataWithoutErrors.length === 0) {
logger.Log( logger.Log(
@ -824,17 +833,14 @@ function setup(data: SubmoduleData): Submodule {
// ------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------
// third party peers handling // third party peers handling
// ------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------
const peersHosts = [...peers.map((peer) => peer.host), selfInfo.host] const peersHosts = [...peers, selfInfo]
const thirdPartyPeers = resultDataWithoutErrors const thirdPartyPeers = resultDataWithoutErrors
.map((res) => { .map((res) => res.remoteInfo)
return Object.values(res).map((x) => x.remoteInfo.myPeers) .flatMap((res) => res.myPeers)
.filter((res) => {
return !peersHosts.some((localPeer) => {
return isPeerSameAs(localPeer, res)
}) })
.filter((x) => !!x)
.flatMap((x) => x)
.flatMap((x) => {
return x.filter(
(recievedPeer) => !peersHosts.includes(recievedPeer.host)
)
}) })
if (thirdPartyPeers.length > 0) { if (thirdPartyPeers.length > 0) {
@ -852,23 +858,30 @@ function setup(data: SubmoduleData): Submodule {
// data syncing // data syncing
// ------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------
const getData = (key: keyof NewDataResult['result']) => { const getData = <T extends keyof SyncDataResult>(key: T) => {
return resultDataWithoutErrors return resultDataWithoutErrors.map((x) => ({
.filter((x) => x[key]) ...x[key],
.map((x) => x[key]) peer: x.remoteInfo.selfInfo,
}))
} }
const syncResults: SyncResult[] = [] const syncResults: SyncResult[] = []
const questionData = getData('questions')
if (questionData && questionData.length > 0) {
const res = await syncQuestions(questionData, syncStart)
syncResults.push(res)
}
const userData = getData('users') const userData = getData('users')
if (userData && userData.length > 0) { if (userData && userData.length > 0) {
const res = await syncUsers(userData, syncStart) const res = await syncUsers(userData, syncStart)
syncResults.push(res) syncResults.push(res)
} }
const questionData = getData('questions') const userFilesData = getData('userFiles')
if (questionData && questionData.length > 0) { if (userFilesData && userFilesData.length > 0) {
const res = await syncQuestions(questionData, syncStart) const res = await syncUserFiles(userFilesData, syncStart)
syncResults.push(res) syncResults.push(res)
} }
@ -885,7 +898,7 @@ function setup(data: SubmoduleData): Submodule {
} }
async function syncUsers( async function syncUsers(
userData: (UserSyncDataRes & { peer: PeerInfo })[], userData: (SyncDataResult['users'] & { peer: PeerInfo })[],
syncStart: number syncStart: number
): Promise<SyncResult> { ): Promise<SyncResult> {
logger.Log('Syncing users...') logger.Log('Syncing users...')
@ -946,19 +959,19 @@ function setup(data: SubmoduleData): Submodule {
return { return {
old: { old: {
oldUserCount: oldUserCount, users: oldUserCount,
}, },
added: { added: {
totalNewUers: newUserCount - oldUserCount, users: newUserCount - oldUserCount,
}, },
final: { final: {
newUserCount: newUserCount, users: newUserCount,
}, },
} }
} }
async function syncQuestions( async function syncQuestions(
questionData: (QuestionSyncDataRes & { peer: PeerInfo })[], questionData: (SyncDataResult['questions'] & { peer: PeerInfo })[],
syncStart: number syncStart: number
): Promise<SyncResult> { ): Promise<SyncResult> {
logger.Log('Syncing questions...') logger.Log('Syncing questions...')
@ -972,7 +985,7 @@ function setup(data: SubmoduleData): Submodule {
} }
} = {} } = {}
const resultDataWithoutEmptyDbs: (QuestionSyncDataRes & { const resultDataWithoutEmptyDbs: (SyncDataResult['questions'] & {
peer: PeerInfo peer: PeerInfo
})[] = [] })[] = []
questionData.forEach((res) => { questionData.forEach((res) => {
@ -1135,19 +1148,56 @@ function setup(data: SubmoduleData): Submodule {
return { return {
old: { old: {
oldQuestionDbCount: oldQuestionDbCount, questionDbs: oldQuestionDbCount,
oldSubjCount: oldSubjCount, subjects: oldSubjCount,
oldQuestionCount: oldQuestionCount, questions: oldQuestionCount,
}, },
added: { added: {
totalNewQdbs: totalNewQdbs, questionDbs: totalNewQdbs,
totalNewSubjects: totalNewSubjects, subjects: totalNewSubjects,
totalNewQuestions: totalNewQuestions, questions: totalNewQuestions,
}, },
final: { final: {
newQuestionDbCount: newQuestionDbCount, questionDbs: newQuestionDbCount,
newSubjCount: newSubjCount, subjects: newSubjCount,
newQuestionCount: newQuestionCount, questions: newQuestionCount,
},
}
}
async function syncUserFiles(
newData: (SyncDataResult['userFiles'] & { peer: PeerInfo })[],
syncStart: number
): Promise<SyncResult> {
logger.Log('Syncing user files...')
// ... magic magic code code code ...
console.log(newData, syncStart)
const filesToGet = []
newData.forEach((res) => {
Object.entries(res.newFiles).forEach(([dirName, userFilesDir]) => {
Object.entries(userFilesDir).forEach(([fileName, data]) => {
console.log(fileName, data)
filesToGet.push({
fileName: fileName,
path: path.join(publicDir, dirName, fileName),
data: data,
})
})
})
})
logger.Log(`Successfully synced user files!`, 'green')
return {
old: {
userFiles: 0,
},
added: {
userFiles: 0,
},
final: {
userFiles: 0,
}, },
} }
} }
@ -1183,32 +1233,97 @@ function setup(data: SubmoduleData): Submodule {
res.json(getSelfInfo(true)) res.json(getSelfInfo(true))
}) })
// TODO: get all user files app.get('/getnewdata', (req: Request, res: Response<any>) => {
app.get('/getnewfilessince', (req: Request, res: Response<any>) => {
const since = Number.isNaN(+req.query.since) ? 0 : +req.query.since
res.json({ since: since, message: 'unimplemented' })
})
app.get(
'/getnewuserssince',
(
req: Request,
res: Response<
UserSyncDataRes & { message?: string; success?: boolean }
>
) => {
logger.LogReq(req) logger.LogReq(req)
const since = Number.isNaN(+req.query.since) ? 0 : +req.query.since const questionsSince = Number.isNaN(+req.query.questionsSince)
? 0
: +req.query.questionsSince
const usersSince = Number.isNaN(+req.query.usersSince)
? 0
: +req.query.usersSince
const userFilesSince = Number.isNaN(+req.query.userFilesSince)
? 0
: +req.query.userFilesSince
const questions = !!req.query.questions
const users = !!req.query.users
const userFiles = !!req.query.userFiles
const remoteHost = req.query.host const remoteHost = req.query.host
let hostToLog = remoteHost || 'Unknown host'
let sentUsers = 0
const result: UserSyncDataRes = { const sendAll = !questions && !users && !userFiles
let hostToLog = remoteHost || 'Unknown host'
let remotePeerInfo: PeerInfo = null
if (remoteHost) {
remotePeerInfo = peers.find((peer) => {
return peerToString(peer) === remoteHost
})
if (!remotePeerInfo) {
handleNewThirdPartyPeer(remoteHost)
} else {
hostToLog = peerToString(remotePeerInfo)
}
}
const result: SyncDataResult = {
remoteInfo: getSelfInfo(), remoteInfo: getSelfInfo(),
} }
if (questions || sendAll) {
const questionDbsWithNewQuestions = Number.isNaN(questionsSince)
? getQuestionDbs()
: getQuestionDbs()
.map((qdb) => {
return {
...qdb,
data: getNewDataSince(qdb.data, questionsSince),
}
})
.filter((qdb) => {
const { questionCount: questionCount } =
countOfQdb(qdb)
return questionCount > 0
})
const { subjCount: subjects, questionCount: questions } =
countOfQdbs(questionDbsWithNewQuestions)
result.questions = {
questionDbs: questionDbsWithNewQuestions,
count: {
qdbs: questionDbsWithNewQuestions.length,
subjects: subjects,
questions: questions,
},
}
const questionsSinceDate = questionsSince
? new Date(questionsSince).toLocaleString()
: 'all time'
logger.Log(
`\tSending new data to ${logger.C(
'blue'
)}${hostToLog}${logger.C()} since ${logger.C(
'blue'
)}${questionsSinceDate}${logger.C()}`
)
logger.logTable(
[
['QDBs', 'Subjs', 'Questions'],
[
result.questions.questionDbs.length,
result.questions.count.subjects,
result.questions.count.questions,
],
],
{ rowPrefix: '\t' }
)
}
if (users || sendAll) {
let sentUsers = 0
if (!remoteHost) { if (!remoteHost) {
res.json({ res.json({
...result, ...result,
@ -1217,16 +1332,6 @@ function setup(data: SubmoduleData): Submodule {
}) })
return return
} }
const remotePeerInfo = peers.find((peer) => {
return peerToString(peer) === remoteHost
})
if (!remotePeerInfo) {
handleNewThirdPartyPeer(remoteHost)
} else {
hostToLog = peerToString(remotePeerInfo)
}
if (!remotePeerInfo) { if (!remotePeerInfo) {
res.json({ res.json({
success: false, success: false,
@ -1235,19 +1340,20 @@ function setup(data: SubmoduleData): Submodule {
}) })
return return
} }
const remotePublicKey = remotePeerInfo.publicKey const remotePublicKey = remotePeerInfo.publicKey
if (remotePublicKey) { if (remotePublicKey) {
// FIXME: sign data? // FIXME: sign data?
const newUsers = getNewUsersSince(since) const newUsers = getNewUsersSince(usersSince)
sentUsers = newUsers.length sentUsers = newUsers.length
result.encryptedUsers = encrypt( result.users = {
encryptedUsers: encrypt(
remotePublicKey, remotePublicKey,
JSON.stringify(newUsers) JSON.stringify(newUsers)
) ),
}
const usersSinceDate = since const usersSinceDate = usersSince
? new Date(since).toLocaleString() ? new Date(usersSince).toLocaleString()
: 'all time' : 'all time'
logger.Log( logger.Log(
@ -1265,93 +1371,39 @@ function setup(data: SubmoduleData): Submodule {
'yellowbg' 'yellowbg'
) )
} }
res.json(result)
}
)
app.get(
'/getnewdatasince',
(req: Request, res: Response<QuestionSyncDataRes>) => {
// FIXME: hash question db to see if different?
// it could help in determining if it should be checked for new data, but it would only save
// a getNewDataSince() call per question db
logger.LogReq(req)
const since = Number.isNaN(+req.query.since) ? 0 : +req.query.since
const remoteHost = req.query.host
const result: QuestionSyncDataRes = {
remoteInfo: getSelfInfo(),
} }
const questionDbsWithNewQuestions = Number.isNaN(since) if (userFiles || sendAll) {
? getQuestionDbs() const newFiles = getNewUserFilesSince(userFilesSince)
: getQuestionDbs() result.userFiles = { newFiles: newFiles }
.map((qdb) => {
return {
...qdb,
data: getNewDataSince(qdb.data, since),
}
})
.filter((qdb) => {
const { questionCount: questionCount } =
countOfQdb(qdb)
return questionCount > 0
})
const { subjCount: subjects, questionCount: questions } = const userFilesSinceDate = questionsSince
countOfQdbs(questionDbsWithNewQuestions) ? new Date(questionsSince).toLocaleString()
result.questionDbs = questionDbsWithNewQuestions
result.count = {
qdbs: questionDbsWithNewQuestions.length,
subjects: subjects,
questions: questions,
}
let hostToLog = remoteHost || 'Unknown host'
const dateToLog = since
? new Date(since).toLocaleString()
: 'all time' : 'all time'
if (remoteHost) {
const remotePeerInfo = peers.find((peer) => {
return peerToString(peer) === remoteHost
})
if (!remotePeerInfo) {
handleNewThirdPartyPeer(remoteHost)
} else {
hostToLog = peerToString(remotePeerInfo)
}
}
logger.Log( logger.Log(
`\tSending new data to ${logger.C( `\tSending new user files to ${logger.C(
'blue' 'blue'
)}${hostToLog}${logger.C()} since ${logger.C( )}${hostToLog}${logger.C()} since ${logger.C(
'blue' 'blue'
)}${dateToLog}${logger.C()}` )}${userFilesSinceDate}${logger.C()}`
) )
logger.logTable( logger.Log(
[ `\tSent files count: ${
['QDBs', 'Subjs', 'Questions'], Object.keys(result.userFiles.newFiles).length
[ }`
result.questionDbs.length,
result.count.subjects,
result.count.questions,
],
],
{ rowPrefix: '\t' }
) )
}
res.json(result) res.json(result)
} })
)
app.get('/syncp2pdata', (req: Request, res: Response) => { app.get('/syncp2pdata', (req: Request, res: Response) => {
logger.LogReq(req) logger.LogReq(req)
const questions = !!req.query.questions const questions = !!req.query.questions
const users = !!req.query.users const users = !!req.query.users
const userFiles = !!req.query.userFiles
const allTime = !!req.query.allTime const allTime = !!req.query.allTime
const user = req.session.user const user = req.session.user
@ -1377,6 +1429,7 @@ function setup(data: SubmoduleData): Submodule {
shouldSync: { shouldSync: {
questions: questions, questions: questions,
users: users, users: users,
userFiles: userFiles,
}, },
allTime: allTime, allTime: allTime,
}) })

View file

@ -24,13 +24,19 @@ import logger from '../../../utils/logger'
import utils from '../../../utils/utils' import utils from '../../../utils/utils'
import { Request, SubmoduleData, User } from '../../../types/basicTypes' import { Request, SubmoduleData, User } from '../../../types/basicTypes'
import { paths, publicDir } from '../../../utils/files' import { paths, publicDir } from '../../../utils/files'
import constants from '../../../constants'
const dataFileName = '.data.json' export interface UserDirDataFile {
uid: number
views: number
upvotes: number[]
downvotes: number[]
}
function listDir(subdir: string, userFilesDir: string) { function listDir(subdir: string, userFilesDir: string) {
const safeSubdir = subdir.replace(/\.+/g, '').replace(/\/+/g, '') const safeSubdir = subdir.replace(/\.+/g, '').replace(/\/+/g, '')
const dir = userFilesDir + '/' + safeSubdir const dir = userFilesDir + '/' + safeSubdir
const usersFile = dir + '/' + dataFileName const usersFile = dir + '/' + constants.userFilesDataFileName
if (!utils.FileExists(dir)) { if (!utils.FileExists(dir)) {
return { return {
@ -99,7 +105,11 @@ function setup(data: SubmoduleData): void {
const dir = x[2] const dir = x[2]
const fname = x.pop() const fname = x.pop()
const dataFilePath = const dataFilePath =
paths.userFilesDir + '/' + dir + '/' + dataFileName paths.userFilesDir +
'/' +
dir +
'/' +
constants.userFilesDataFileName
const data = utils.ReadJSON(dataFilePath) const data = utils.ReadJSON(dataFilePath)
@ -179,7 +189,11 @@ function setup(data: SubmoduleData): void {
} }
utils.deleteFile(filePath) utils.deleteFile(filePath)
const usersFile = const usersFile =
paths.userFilesDir + '/' + safeDir + '/' + dataFileName paths.userFilesDir +
'/' +
safeDir +
'/' +
constants.userFilesDataFileName
const users = utils.ReadJSON(usersFile) const users = utils.ReadJSON(usersFile)
delete users[safeFname] delete users[safeFname]
utils.WriteFile(JSON.stringify(users), usersFile) utils.WriteFile(JSON.stringify(users), usersFile)
@ -247,7 +261,11 @@ function setup(data: SubmoduleData): void {
) )
const usersFile = const usersFile =
paths.userFilesDir + '/' + safeDir + '/' + dataFileName paths.userFilesDir +
'/' +
safeDir +
'/' +
constants.userFilesDataFileName
const users = utils.ReadJSON(usersFile) const users = utils.ReadJSON(usersFile)
users[body.fileName] = { uid: user.id } users[body.fileName] = { uid: user.id }
utils.WriteFile(JSON.stringify(users), usersFile) utils.WriteFile(JSON.stringify(users), usersFile)
@ -270,7 +288,12 @@ function setup(data: SubmoduleData): void {
const x = safePath.split('/') const x = safePath.split('/')
const dir = x[1] const dir = x[1]
const fname = x.pop() const fname = x.pop()
const dataFilePath = paths.userFilesDir + '/' + dir + '/' + dataFileName const dataFilePath =
paths.userFilesDir +
'/' +
dir +
'/' +
constants.userFilesDataFileName
const data = utils.ReadJSON(dataFilePath) const data = utils.ReadJSON(dataFilePath)

View file

@ -175,7 +175,9 @@ export interface PeerInfo {
pw?: string pw?: string
sessionCookie?: string sessionCookie?: string
lastSync?: number lastSync?: number
lastQuestionsSync?: number
lastUsersSync?: number lastUsersSync?: number
lastUserFilesSync?: number
note?: string note?: string
http?: boolean http?: boolean
} }

View file

@ -52,6 +52,10 @@ const PeerInfoSchemaBase = {
export const SelfInfoSchema: Schema = { export const SelfInfoSchema: Schema = {
...PeerInfoSchemaBase, ...PeerInfoSchemaBase,
properties: {
...PeerInfoSchemaBase.properties,
lastSync: { type: 'number' },
},
required: ['name', 'host', 'port', 'contact'], required: ['name', 'host', 'port', 'contact'],
} }
@ -59,7 +63,9 @@ export const PeerInfoSchema: Schema = {
...PeerInfoSchemaBase, ...PeerInfoSchemaBase,
properties: { properties: {
...PeerInfoSchemaBase.properties, ...PeerInfoSchemaBase.properties,
lastQuestionsSync: { type: 'number' },
lastUsersSync: { type: 'number' }, lastUsersSync: { type: 'number' },
lastUserFilesSync: { type: 'number' },
publicKey: { type: 'string' }, publicKey: { type: 'string' },
pw: { type: 'string' }, pw: { type: 'string' },
sessionCookie: { type: 'string' }, sessionCookie: { type: 'string' },

View file

@ -11,6 +11,7 @@ import {
import logger from './logger' import logger from './logger'
import utils from './utils' import utils from './utils'
import { HttpsFiles } from '../types/basicTypes' import { HttpsFiles } from '../types/basicTypes'
import path from 'node:path'
// FIXME: remove all file exists checks from everywhere for files that are created / checked here // FIXME: remove all file exists checks from everywhere for files that are created / checked here
@ -324,7 +325,7 @@ export const files = {
// user files // user files
// -------------------------------------------------------------------------------- // --------------------------------------------------------------------------------
userFilesDir: { userFilesDir: {
path: publicDir + 'userFiles', path: path.join(publicDir, 'userFiles'),
isDir: true, isDir: true,
}, },
} as const satisfies Record<string, FileDescriptor> } as const satisfies Record<string, FileDescriptor>