diff --git a/app/pages/bridge/connect.vue b/app/pages/bridge/connect.vue new file mode 100644 index 0000000..e758561 --- /dev/null +++ b/app/pages/bridge/connect.vue @@ -0,0 +1,503 @@ + + + + + + diff --git a/server/api/bridge/connect.post.ts b/server/api/bridge/connect.post.ts new file mode 100644 index 0000000..dfb2b5e --- /dev/null +++ b/server/api/bridge/connect.post.ts @@ -0,0 +1,50 @@ +import { createError, readBody } from 'h3' +import { requireUserSession } from '../../utils/auth' +import { normalizeBridgeToken } from '../../utils/bridge' +import { BridgeToken } from '../../models/BridgeToken' + +interface ConnectBody { + token?: string +} + +export default defineEventHandler(async (event) => { + const user = await requireUserSession(event) + const body = await readBody(event) + const token = normalizeBridgeToken(body.token) + + if (!token) { + throw createError({ statusCode: 400, statusMessage: 'Ungültiger Token übergeben.' }) + } + + const now = new Date() + + const document = await BridgeToken.findOneAndUpdate( + { token }, + { + $set: { + user: user._id, + connectedAt: now, + }, + $setOnInsert: { + token, + }, + }, + { upsert: true, new: true, runValidators: true }, + ).populate('user', 'name email') + + if (!document) { + throw createError({ statusCode: 500, statusMessage: 'Token konnte nicht gespeichert werden.' }) + } + + return { + success: true, + token: document.token, + connectedAt: document.connectedAt?.toISOString() ?? now.toISOString(), + user: { + id: String(user._id), + email: user.email, + name: user.name, + }, + } +}) + diff --git a/server/api/bridge/me.get.ts b/server/api/bridge/me.get.ts new file mode 100644 index 0000000..ee36616 --- /dev/null +++ b/server/api/bridge/me.get.ts @@ -0,0 +1,44 @@ +import { createError, getQuery } from 'h3' +import { BridgeToken } from '../../models/BridgeToken' +import { normalizeBridgeToken } from '../../utils/bridge' +import type { UserDocument } from '../../models/User' + +export default defineEventHandler(async (event) => { + const query = getQuery(event) + const token = normalizeBridgeToken(query.token as string | undefined) + + if (!token) { + throw createError({ statusCode: 400, statusMessage: 'Token muss übergeben werden.' }) + } + + const document = await BridgeToken.findOne({ token }).populate('user', 'name email') + + if (!document || !document.user) { + return { + token, + connected: false, + user: null, + simConnected: false, + flightActive: false, + connectedAt: document?.connectedAt ? document.connectedAt.toISOString() : null, + lastStatusAt: document?.lastStatusAt ? document.lastStatusAt.toISOString() : null, + } + } + + const bridgeUser = document.user as UserDocument + + return { + token: document.token, + connected: true, + user: { + id: String(bridgeUser._id), + email: bridgeUser.email, + name: bridgeUser.name, + }, + simConnected: document.simConnected, + flightActive: document.flightActive, + connectedAt: document.connectedAt?.toISOString() ?? document.updatedAt.toISOString(), + lastStatusAt: document.lastStatusAt ? document.lastStatusAt.toISOString() : null, + } +}) + diff --git a/server/api/bridge/status.post.ts b/server/api/bridge/status.post.ts new file mode 100644 index 0000000..89c8f1d --- /dev/null +++ b/server/api/bridge/status.post.ts @@ -0,0 +1,63 @@ +import { createError, readBody } from 'h3' +import { BridgeToken } from '../../models/BridgeToken' +import { normalizeBridgeToken } from '../../utils/bridge' +import type { UserDocument } from '../../models/User' + +interface StatusBody { + token?: string + simConnected?: boolean + flightActive?: boolean +} + +export default defineEventHandler(async (event) => { + const body = await readBody(event) + const token = normalizeBridgeToken(body.token) + + if (!token) { + throw createError({ statusCode: 400, statusMessage: 'Token muss übergeben werden.' }) + } + + const updatePayload: Record = { + lastStatusAt: new Date(), + } + + if (typeof body.simConnected === 'boolean') { + updatePayload.simConnected = body.simConnected + } + + if (typeof body.flightActive === 'boolean') { + updatePayload.flightActive = body.flightActive + } + + const document = await BridgeToken.findOneAndUpdate( + { token }, + { + $set: updatePayload, + $setOnInsert: { token }, + }, + { new: true, upsert: true, runValidators: true }, + ).populate('user', 'name email') + + if (!document) { + throw createError({ statusCode: 500, statusMessage: 'Status konnte nicht aktualisiert werden.' }) + } + + const user = document.user as UserDocument | undefined + + return { + token: document.token, + connected: Boolean(user), + user: user + ? { + id: String(user._id), + email: user.email, + name: user.name, + } + : null, + simConnected: document.simConnected, + flightActive: document.flightActive, + connectedAt: document.connectedAt ? document.connectedAt.toISOString() : null, + lastStatusAt: document.lastStatusAt ? document.lastStatusAt.toISOString() : null, + } +}) + diff --git a/server/middleware/auth.global.ts b/server/middleware/auth.global.ts index 1d91e00..52808e7 100644 --- a/server/middleware/auth.global.ts +++ b/server/middleware/auth.global.ts @@ -9,6 +9,9 @@ export default defineEventHandler(async (event) => { if (url.pathname.startsWith('/api/service/')) { return } + if (url.pathname.startsWith('/api/bridge/')) { + return + } if (event.node.req.method === 'OPTIONS') { return } diff --git a/server/models/BridgeToken.ts b/server/models/BridgeToken.ts new file mode 100644 index 0000000..b13d18e --- /dev/null +++ b/server/models/BridgeToken.ts @@ -0,0 +1,32 @@ +import mongoose from 'mongoose' +import type { UserDocument } from './User' + +export interface BridgeTokenDocument extends mongoose.Document { + token: string + user?: mongoose.Types.ObjectId | UserDocument + connectedAt?: Date + lastStatusAt?: Date + simConnected: boolean + flightActive: boolean + createdAt: Date + updatedAt: Date +} + +const bridgeTokenSchema = new mongoose.Schema( + { + token: { type: String, required: true, unique: true, index: true }, + user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, + connectedAt: { type: Date }, + lastStatusAt: { type: Date }, + simConnected: { type: Boolean, default: false }, + flightActive: { type: Boolean, default: false }, + }, + { + timestamps: true, + }, +) + +export const BridgeToken = + (mongoose.models.BridgeToken as mongoose.Model | undefined) || + mongoose.model('BridgeToken', bridgeTokenSchema) + diff --git a/server/utils/bridge.ts b/server/utils/bridge.ts new file mode 100644 index 0000000..4b9ab33 --- /dev/null +++ b/server/utils/bridge.ts @@ -0,0 +1,14 @@ +export function normalizeBridgeToken(input: unknown) { + if (typeof input !== 'string') { + return null + } + const token = input.trim() + if (!token) { + return null + } + if (token.length < 8 || token.length > 256) { + return null + } + return token +} +