feat: add fluxer upstream source and self-hosting documentation
- Clone of github.com/fluxerapp/fluxer (official upstream) - SELF_HOSTING.md: full VM rebuild procedure, architecture overview, service reference, step-by-step setup, troubleshooting, seattle reference - dev/.env.example: all env vars with secrets redacted and generation instructions - dev/livekit.yaml: LiveKit config template with placeholder keys - fluxer-seattle/: existing seattle deployment setup scripts
This commit is contained in:
122
fluxer/packages/api/src/connection/ConnectionRequestService.tsx
Normal file
122
fluxer/packages/api/src/connection/ConnectionRequestService.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Fluxer Contributors
|
||||
*
|
||||
* This file is part of Fluxer.
|
||||
*
|
||||
* Fluxer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Fluxer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import type {UserID} from '@fluxer/api/src/BrandedTypes';
|
||||
import {signInitiationToken, verifyInitiationToken} from '@fluxer/api/src/connection/ConnectionInitiationToken';
|
||||
import {mapConnectionToResponse} from '@fluxer/api/src/connection/ConnectionMappers';
|
||||
import {ConnectionInitiationTokenInvalidError} from '@fluxer/api/src/connection/errors/ConnectionInitiationTokenInvalidError';
|
||||
import type {IConnectionService} from '@fluxer/api/src/connection/IConnectionService';
|
||||
import {
|
||||
CONNECTION_INITIATION_TOKEN_EXPIRY_MS,
|
||||
type ConnectionType,
|
||||
ConnectionTypes,
|
||||
} from '@fluxer/constants/src/ConnectionConstants';
|
||||
import type {
|
||||
ConnectionResponse,
|
||||
ConnectionVerificationResponse,
|
||||
CreateConnectionRequest,
|
||||
UpdateConnectionRequest,
|
||||
VerifyAndCreateConnectionRequest,
|
||||
} from '@fluxer/schema/src/domains/connection/ConnectionSchemas';
|
||||
|
||||
export class ConnectionRequestService {
|
||||
constructor(
|
||||
private readonly connectionService: IConnectionService,
|
||||
private readonly connectionInitiationSecret: string,
|
||||
) {}
|
||||
|
||||
async listConnections(userId: UserID): Promise<Array<ConnectionResponse>> {
|
||||
const rows = await this.connectionService.getConnectionsForUser(userId);
|
||||
return rows.sort((a, b) => a.sort_order - b.sort_order).map((row) => mapConnectionToResponse(row));
|
||||
}
|
||||
|
||||
async initiateConnection(userId: UserID, body: CreateConnectionRequest): Promise<ConnectionVerificationResponse> {
|
||||
const result = await this.connectionService.initiateConnection(userId, body.type, body.identifier);
|
||||
const instructions = this.generateVerificationInstructions(body.type, body.identifier);
|
||||
const initiationToken = signInitiationToken(
|
||||
{
|
||||
userId: String(userId),
|
||||
type: body.type,
|
||||
identifier: body.identifier,
|
||||
verificationCode: result.verificationCode,
|
||||
expiresAt: Date.now() + CONNECTION_INITIATION_TOKEN_EXPIRY_MS,
|
||||
},
|
||||
this.connectionInitiationSecret,
|
||||
);
|
||||
|
||||
return {
|
||||
token: result.verificationCode,
|
||||
type: body.type,
|
||||
id: body.identifier,
|
||||
instructions,
|
||||
initiation_token: initiationToken,
|
||||
};
|
||||
}
|
||||
|
||||
async verifyAndCreateConnection(userId: UserID, body: VerifyAndCreateConnectionRequest): Promise<ConnectionResponse> {
|
||||
const payload = verifyInitiationToken(body.initiation_token, this.connectionInitiationSecret);
|
||||
if (!payload || payload.userId !== String(userId)) {
|
||||
throw new ConnectionInitiationTokenInvalidError();
|
||||
}
|
||||
|
||||
const row = await this.connectionService.verifyAndCreateConnection(
|
||||
userId,
|
||||
payload.type,
|
||||
payload.identifier,
|
||||
payload.verificationCode,
|
||||
body.visibility_flags ?? 1,
|
||||
);
|
||||
return mapConnectionToResponse(row);
|
||||
}
|
||||
|
||||
async updateConnection(
|
||||
userId: UserID,
|
||||
connectionType: ConnectionType,
|
||||
connectionId: string,
|
||||
body: UpdateConnectionRequest,
|
||||
): Promise<void> {
|
||||
await this.connectionService.updateConnection(userId, connectionType, connectionId, body);
|
||||
}
|
||||
|
||||
async deleteConnection(userId: UserID, connectionType: ConnectionType, connectionId: string): Promise<void> {
|
||||
await this.connectionService.deleteConnection(userId, connectionType, connectionId);
|
||||
}
|
||||
|
||||
async verifyConnection(
|
||||
userId: UserID,
|
||||
connectionType: ConnectionType,
|
||||
connectionId: string,
|
||||
): Promise<ConnectionResponse> {
|
||||
const row = await this.connectionService.verifyConnection(userId, connectionType, connectionId);
|
||||
return mapConnectionToResponse(row);
|
||||
}
|
||||
|
||||
async reorderConnections(userId: UserID, connectionIds: Array<string>): Promise<void> {
|
||||
await this.connectionService.reorderConnections(userId, connectionIds);
|
||||
}
|
||||
|
||||
private generateVerificationInstructions(connectionType: ConnectionType, identifier: string): string {
|
||||
switch (connectionType) {
|
||||
case ConnectionTypes.DOMAIN:
|
||||
return `Add a DNS TXT record at _fluxer.${identifier} with the value fluxer-verification=<token>, or serve the token at https://${identifier}/.well-known/fluxer-verification`;
|
||||
default:
|
||||
return 'Follow the platform-specific verification instructions';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user