Files
sky_phone-sky-systems/frontend/testserver/index.cjs
T
Type f79400a691 ADD - add SkyPhone banking app
Includes the required banking configuration, SQL migration, and English locale entries.
2026-08-06 17:35:59 +02:00

378 lines
11 KiB
JavaScript

const cors = require('cors')
const express = require('express')
const app = express()
const port = Number(process.argv[2]) || 3001
app.use(cors())
app.use(express.json())
let authenticated = false
let draft = null
let linkedAccount = null
let mockNotes = []
let mockBankBalance = 24787
let mockCashBalance = 2350
let nextBankTransactionId = 7
const mockBankTransactions = [
{ id: 1, kind: 'transfer_in', amount: 3200, label: 'Sofia Turner', reference: 'mock-1', createdAt: Date.now() - 45 * 60 * 1000 },
{ id: 2, kind: 'transfer_out', amount: 680, label: 'Vincent Cole', reference: 'mock-2', createdAt: Date.now() - 5 * 60 * 60 * 1000 },
{ id: 3, kind: 'deposit', amount: 1250, label: '', reference: 'mock-3', createdAt: Date.now() - 25 * 60 * 60 * 1000 },
{ id: 4, kind: 'transfer_out', amount: 420, label: 'Maya Brooks', reference: 'mock-4', createdAt: Date.now() - 50 * 60 * 60 * 1000 },
{ id: 5, kind: 'withdrawal', amount: 300, label: '', reference: 'mock-5', createdAt: Date.now() - 76 * 60 * 60 * 1000 },
{ id: 6, kind: 'transfer_in', amount: 950, label: 'Noah Bennett', reference: 'mock-6', createdAt: Date.now() - 120 * 60 * 60 * 1000 },
]
const deviceData = {}
const accountDevices = [
{
created_at: '2026-08-04 12:00:00',
current: true,
device_name: 'iFruit Phone',
imei: '356938035643809',
updated_at: '2026-08-04 12:00:00',
},
]
const messages = [
{
body: 'Welcome to iFruit Mail. Your shared mailbox is ready to use.',
created_at: '2026-08-04 11:30:00',
folder: 'inbox',
id: 1,
is_read: false,
message_id: 'b73f3872-54cc-4d74-a058-6caa24f0dfff',
preview: 'Welcome to iFruit Mail. Your shared mailbox is ready to use.',
recipients: ['demo@ifruit.com'],
sender: 'support@ifruit.com',
subject: 'Welcome to iFruit Mail',
trashed_at: null,
},
{
body: 'The meet is behind the casino at nine. Bring the blue car.',
created_at: '2026-08-04 10:15:00',
folder: 'inbox',
id: 2,
is_read: true,
message_id: '79042d13-f86d-4418-a490-8b558539c20b',
preview: 'The meet is behind the casino at nine. Bring the blue car.',
recipients: ['demo@ifruit.com', 'jamie@ifruit.com'],
sender: 'morgan@ifruit.com',
subject: 'Tonight',
trashed_at: null,
},
{
body: 'I will be there.',
created_at: '2026-08-03 18:05:00',
folder: 'sent',
id: 3,
is_read: true,
message_id: 'c4414c1e-5fe3-46ea-bc0b-b5b9785c91bd',
preview: 'I will be there.',
recipients: ['morgan@ifruit.com'],
sender: 'demo@ifruit.com',
subject: 'Re: Tonight',
trashed_at: null,
},
{
body: 'This is an old message.',
created_at: '2026-08-02 09:20:00',
folder: 'inbox',
id: 4,
is_read: true,
message_id: 'd99669ab-c1ac-42d4-8ed4-3c4fe7fa37fb',
preview: 'This is an old message.',
recipients: ['demo@ifruit.com'],
sender: 'news@ifruit.com',
subject: 'Old mail',
trashed_at: '2026-08-04 08:00:00',
},
]
function counts() {
return {
drafts: draft ? 1 : 0,
inbox: messages.filter(
(item) => item.folder === 'inbox' && !item.trashed_at,
).length,
sent: messages.filter((item) => item.folder === 'sent' && !item.trashed_at)
.length,
trash: messages.filter((item) => item.trashed_at).length,
unread: messages.filter(
(item) => item.folder === 'inbox' && !item.trashed_at && !item.is_read,
).length,
}
}
app.post('/api/:endpoint', (request, response) => {
console.log(`[NUI] ${request.params.endpoint}`, request.body)
const endpoint = request.params.endpoint
const bankingOverview = () => ({
bank: mockBankBalance,
cash: mockCashBalance,
currency: '$',
playerId: 42,
playerName: 'Alex Morgan',
transactions: mockBankTransactions,
})
if (endpoint === 'banking:overview') {
response.json({ success: true, data: bankingOverview() })
return
}
if (
endpoint === 'banking:deposit' ||
endpoint === 'banking:withdraw' ||
endpoint === 'banking:transfer'
) {
const amount = Number(request.body.amount)
if (!Number.isSafeInteger(amount) || amount <= 0) {
response.json({ success: false, error: 'invalid_request' })
return
}
if (endpoint === 'banking:deposit' && mockCashBalance < amount) {
response.json({ success: false, error: 'insufficient_funds' })
return
}
if (endpoint !== 'banking:deposit' && mockBankBalance < amount) {
response.json({ success: false, error: 'insufficient_funds' })
return
}
const kind = endpoint === 'banking:deposit'
? 'deposit'
: endpoint === 'banking:withdraw'
? 'withdrawal'
: 'transfer_out'
if (kind === 'deposit') {
mockCashBalance -= amount
mockBankBalance += amount
} else if (kind === 'withdrawal') {
mockBankBalance -= amount
mockCashBalance += amount
} else {
mockBankBalance -= amount
}
mockBankTransactions.unshift({
amount,
createdAt: Date.now(),
id: nextBankTransactionId++,
kind,
label: kind === 'transfer_out' ? `Player #${request.body.target}` : '',
reference: `mock-${Date.now()}`,
})
response.json({ success: true, data: bankingOverview() })
return
}
if (endpoint === 'weather:get') {
response.json({
success: true,
data: {
clock: { year: 2026, month: 8, day: 5, hour: 17, minute: 20 },
condition: 'partly_cloudy',
rainLevel: 0.08,
region: 'los_santos',
windSpeed: 3.2,
},
})
return
}
if (endpoint === 'map:getPlayerCoords') {
response.json({
success: true,
data: { coords: { x: -75.2, y: -818.9, z: 326.2 } },
})
return
}
if (endpoint === 'account:login' || endpoint === 'account:register') {
authenticated = true
linkedAccount = {
devices: accountDevices,
email: request.body.email.includes('@')
? request.body.email
: `${request.body.email}@ifruit.com`,
}
response.json({ success: true, data: linkedAccount })
return
}
if (endpoint === 'account:logout') {
authenticated = false
linkedAccount = null
response.json({ success: true })
return
}
if (endpoint === 'account:devices') {
response.json({ success: true, data: accountDevices })
return
}
if (endpoint === 'account:remove-device') {
response.json({ success: true, data: accountDevices })
return
}
if (endpoint === 'device:save') {
const current = deviceData[request.body.namespace]
const revision = (current?.revision ?? 0) + 1
deviceData[request.body.namespace] = {
payload: request.body.payload,
revision,
}
response.json({ success: true, data: { revision } })
return
}
if (endpoint === 'device:factory-reset') {
authenticated = false
linkedAccount = null
mockNotes = []
for (const key of Object.keys(deviceData)) delete deviceData[key]
response.json({ success: true })
return
}
if (
endpoint === 'sim:insert' &&
request.body.imei === '356938035643810' &&
!request.body.confirmed
) {
response.json({ success: false, error: 'confirmation_required' })
return
}
if (endpoint === 'notes:list') {
response.json({ success: true, data: mockNotes })
return
}
if (endpoint === 'notes:create') {
mockNotes.unshift({ ...request.body, revision: 1 })
response.json({ success: true, data: mockNotes })
return
}
if (endpoint === 'notes:update') {
const index = mockNotes.findIndex((note) => note.id === request.body.id)
if (index >= 0) {
mockNotes[index] = {
...request.body,
revision: mockNotes[index].revision + 1,
updatedAt: Date.now(),
}
}
response.json({ success: true, data: mockNotes })
return
}
if (endpoint === 'notes:delete') {
mockNotes = mockNotes.filter((note) => note.id !== request.body.id)
response.json({ success: true, data: mockNotes })
return
}
if (endpoint === 'mail:login' || endpoint === 'mail:register') {
authenticated = true
linkedAccount = {
devices: accountDevices,
email: 'demo@ifruit.com',
}
response.json({ success: true, data: linkedAccount })
return
}
if (endpoint === 'mail:logout') {
authenticated = false
response.json({ success: true })
return
}
if (endpoint.startsWith('mail:') && !authenticated) {
response.json({ success: false, error: 'not_authenticated' })
return
}
if (endpoint === 'mail:counts') {
response.json({ success: true, data: counts() })
return
}
if (endpoint === 'mail:list') {
const { folder, search = '' } = request.body
let items =
folder === 'drafts'
? draft
? [{ ...draft, created_at: draft.updated_at, preview: draft.body }]
: []
: messages.filter((item) =>
folder === 'trash'
? item.trashed_at
: item.folder === folder && !item.trashed_at,
)
const query = String(search).toLowerCase()
if (query) {
items = items.filter((item) =>
`${item.sender ?? ''} ${item.subject} ${item.preview}`
.toLowerCase()
.includes(query),
)
}
response.json({ success: true, data: { hasMore: false, items, offset: 0 } })
return
}
if (endpoint === 'mail:get') {
const message = messages.find((item) => item.id === Number(request.body.id))
if (message) message.is_read = true
response.json(
message
? { success: true, data: message }
: { success: false, error: 'message_not_found' },
)
return
}
if (endpoint === 'mail:get-draft') {
response.json(
draft
? { success: true, data: draft }
: { success: false, error: 'draft_not_found' },
)
return
}
if (endpoint === 'mail:save-draft') {
draft = {
...request.body,
id: request.body.id ?? '748836d2-2308-4c21-a4d8-3af6df6dd7eb',
created_at: '2026-08-04 11:40:00',
updated_at: '2026-08-04 11:40:00',
}
response.json({ success: true, data: { id: draft.id } })
return
}
if (endpoint === 'mail:delete-draft') {
draft = null
response.json({ success: true })
return
}
if (endpoint === 'mail:send') {
draft = null
response.json({ success: true, data: { id: 'mock-sent-message' } })
return
}
if (endpoint === 'mail:trash' || endpoint === 'mail:restore') {
const message = messages.find((item) => item.id === Number(request.body.id))
if (message)
message.trashed_at =
endpoint === 'mail:trash' ? '2026-08-04 12:00:00' : null
response.json({ success: true })
return
}
if (endpoint === 'mail:delete-forever') {
const index = messages.findIndex(
(item) => item.id === Number(request.body.id),
)
if (index >= 0) messages.splice(index, 1)
response.json({ success: true })
return
}
if (endpoint === 'mail:empty-trash') {
for (let index = messages.length - 1; index >= 0; index -= 1) {
if (messages[index].trashed_at) messages.splice(index, 1)
}
response.json({ success: true })
return
}
if (endpoint === 'mail:set-read') {
const message = messages.find((item) => item.id === Number(request.body.id))
if (message) message.is_read = request.body.read
response.json({ success: true })
return
}
response.json({ success: true })
})
app.listen(port, () => {
console.log(`Mock NUI server listening on http://localhost:${port}`)
})