From 33c4472e24f6b65b9183c7c7478c95c4464612fc Mon Sep 17 00:00:00 2001 From: Fairpost Date: Tue, 4 Nov 2025 08:58:03 +0100 Subject: [PATCH 1/3] feat: Add Platform.connected and save in storage --- src/mappers/PlatformMapper.ts | 9 +++++++++ src/models/Platform.ts | 1 + src/models/User.ts | 22 ++++++++++++++++++++-- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/mappers/PlatformMapper.ts b/src/mappers/PlatformMapper.ts index 58a9ec3..5f50b36 100644 --- a/src/mappers/PlatformMapper.ts +++ b/src/mappers/PlatformMapper.ts @@ -30,6 +30,12 @@ export default class PlatformMapper extends AbstractMapper { get: ["managePlatforms"], set: ["managePlatforms"], }, + connected: { + type: "boolean", + label: "Connected", + get: ["managePlatforms"], + set: ["none"], + }, // more fields from platform.settings // added in mapper constructor }; @@ -61,6 +67,9 @@ export default class PlatformMapper extends AbstractMapper { case "active": dto[field] = !!this.platform.active; break; + case "connected": + dto[field] = !!this.platform.connected; + break; case "model": case "id": case "user_id": diff --git a/src/models/Platform.ts b/src/models/Platform.ts index 51e4d61..dee258b 100644 --- a/src/models/Platform.ts +++ b/src/models/Platform.ts @@ -18,6 +18,7 @@ import User from "./User.ts"; export default class Platform { id: PlatformId = PlatformId.UNKNOWN; active: boolean = false; + connected: boolean = false; user: User; cache: { [id: string]: Post } = {}; defaultBody: string = "Fairpost feed"; diff --git a/src/models/User.ts b/src/models/User.ts index 8e4135e..094dbac 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -215,14 +215,20 @@ export default class User { */ private loadPlatforms(): void { this.log.trace("User", "loadPlatforms"); - const platformIds = this.data + const activeIds = this.data .get("settings", "FEED_PLATFORMS", "") .split(","); + const connectedIds = this.data + .get("settings", "FEED_CONNECTED", "") + .split(","); Object.values(platformClasses).forEach((platformClass) => { if (typeof platformClass === "function") { - if (platformIds.includes(platformClass.id())) { + if (activeIds.includes(platformClass.id())) { const platform = new platformClass(this); platform.active = true; + if (connectedIds.includes(platformClass.id())) { + platform.connected = true; + } if (this.platforms === undefined) { this.platforms = {}; } @@ -247,10 +253,22 @@ export default class User { return platform; } + const activeIds = this.data + .get("settings", "FEED_PLATFORMS", "") + .split(","); + const connectedIds = this.data + .get("settings", "FEED_CONNECTED", "") + .split(","); Object.values(platformClasses).forEach((platformClass) => { if (typeof platformClass === "function") { if (platformClass.id() === platformId) { platform = new platformClass(this); + if (activeIds.includes(platform.id)) { + platform.active = true; + } + if (connectedIds.includes(platform.id)) { + platform.connected = true; + } } } }); From 18ad067742b85489fef9cd97a0d1145daba78c0f Mon Sep 17 00:00:00 2001 From: Fairpost Date: Tue, 4 Nov 2025 09:52:19 +0100 Subject: [PATCH 2/3] feat: Add Platform.save and use that to enable/disable platforms --- src/mappers/PlatformMapper.ts | 9 ++-- src/models/Platform.ts | 54 +++++++++++++++++++ src/models/User.ts | 98 +++++------------------------------ src/services/Fairpost.ts | 6 ++- src/types/PlatformDto.ts | 1 + 5 files changed, 78 insertions(+), 90 deletions(-) diff --git a/src/mappers/PlatformMapper.ts b/src/mappers/PlatformMapper.ts index 5f50b36..447026e 100644 --- a/src/mappers/PlatformMapper.ts +++ b/src/mappers/PlatformMapper.ts @@ -34,7 +34,7 @@ export default class PlatformMapper extends AbstractMapper { type: "boolean", label: "Connected", get: ["managePlatforms"], - set: ["none"], + set: ["managePlatforms"], }, // more fields from platform.settings // added in mapper constructor @@ -125,8 +125,10 @@ export default class PlatformMapper extends AbstractMapper { if (fields.includes(field)) { switch (field) { case "active": - if (dto[field]) await this.user.addPlatform(this.platform.id); - else await this.user.removePlatform(this.platform.id); + this.platform.active = !!dto[field]; + break; + case "connected": + this.platform.connected = !!dto[field]; break; default: { switch (this.mapping[field].type) { @@ -163,6 +165,7 @@ export default class PlatformMapper extends AbstractMapper { this.user.log.trace("Ignoring field: " + field); } } + await this.platform.save(); await this.user.data.save(); return true; } diff --git a/src/models/Platform.ts b/src/models/Platform.ts index dee258b..71b3f3f 100644 --- a/src/models/Platform.ts +++ b/src/models/Platform.ts @@ -76,6 +76,60 @@ export default class Platform { return "No tests implemented for " + this.id; } + /** + * save + * + * Save the platform - this is only 'active' + * and 'connected', as loaded in the User object + */ + async save() { + this.user.log.trace( + "Platform", + `Save ${this.id} (${this.active}, ${this.connected})`, + ); + const activeIds = this.user.data + .get("settings", "FEED_PLATFORMS", "") + .split(","); + if (this.active) { + if (!activeIds.includes(this.id)) { + activeIds.push(this.id); + this.user.data.set("settings", "FEED_PLATFORMS", activeIds.join(",")); + this.user.addPlatform(this); + } + } else { + const index = activeIds.indexOf(this.id); + if (index !== -1) { + activeIds.splice(index, 1); + this.user.data.set("settings", "FEED_PLATFORMS", activeIds.join(",")); + this.user.removePlatform(this); + } + } + const connectedIds = this.user.data + .get("settings", "FEED_CONNECTED", "") + .split(","); + if (this.connected) { + if (!connectedIds.includes(this.id)) { + connectedIds.push(this.id); + this.user.data.set( + "settings", + "FEED_CONNECTED", + connectedIds.join(","), + ); + } + } else { + const index = connectedIds.indexOf(this.id); + if (index !== -1) { + connectedIds.splice(index, 1); + this.user.data.set( + "settings", + "FEED_CONNECTED", + connectedIds.join(","), + ); + } + } + await this.user.data.save(); + } + /** * refresh * diff --git a/src/models/User.ts b/src/models/User.ts index 094dbac..f1b8ab4 100644 --- a/src/models/User.ts +++ b/src/models/User.ts @@ -1,5 +1,4 @@ import { basename } from "path"; -import * as readline from "node:readline/promises"; import * as platformClasses from "../platforms/index.ts"; import { PlatformId } from "../platforms/index.ts"; @@ -13,7 +12,6 @@ import UserData from "./User/UserData.ts"; import UserFiles from "./User/UserFiles.ts"; import UserLog from "./User/UserLog.ts"; import UserMapper from "../mappers/UserMapper.ts"; -import { FieldMapping } from "../types/index.ts"; /** * User - represents one fairpost user @@ -294,96 +292,24 @@ export default class User { } /** - * Enable a platform on this user - * @param platformId - * @returns the enabled platform + * Add one platform on this users platforms[] after it has been set + * active. Does not save. + * @param platform */ - public async addPlatform(platformId: PlatformId): Promise { - this.log.trace("User", "addPlatform", platformId); - if ( - Object.values(PlatformId).includes(platformId) && - platformId != PlatformId.UNKNOWN - ) { - const platforms = this.data.get("settings", "FEED_PLATFORMS", ""); - const platformIds = platforms ? platforms.split(",") : []; - if (!platformIds.includes(platformId)) { - platformIds.push(platformId); - this.data.set("settings", "FEED_PLATFORMS", platformIds.join(",")); - await this.data.save(); - } - this.loadPlatforms(); - this.log.info(`Platform ${platformId} enabled for user ${this.id}`); - } else { - throw this.log.error("addPlatform: no such platform", platformId); + public addPlatform(platform: Platform) { + if (this.platforms && platform.active) { + this.platforms[platform.id] = platform; } - return this.getPlatform(platformId); } /** - * Disable a platform on this user - * @param platformId + * Remove one platform on this users platforms[] after it has been set + * inactive. Does not save. + * @param platform */ - public async removePlatform(platformId: PlatformId): Promise { - this.log.trace("User", "removePlatforms", platformId); - if ( - Object.values(PlatformId).includes(platformId) && - platformId != PlatformId.UNKNOWN - ) { - const platforms = this.data.get("settings", "FEED_PLATFORMS", ""); - const platformIds = platforms ? platforms.split(",") : []; - const index = platformIds.indexOf(platformId); - if (index !== -1) { - platformIds.splice(index, 1); - this.data.set("settings", "FEED_PLATFORMS", platformIds.join(",")); - await this.data.save(); - } - this.loadPlatforms(); - this.log.info(`Platform ${platformId} disabled for user ${this.id}`); - } else { - throw this.log.error("removePlatform: no such platform", platformId); + public removePlatform(platform: Platform) { + if (this.platforms && !platform.active) { + delete this.platforms[platform.id]; } } - - /** - * @returns all data from the settings store - - public getSettings(): { [key: string]: string } { - return this.data.getStore("settings"); - } - */ - - public async promptCliFields( - fields: FieldMapping, - ): Promise<{ [key: string]: string }> { - const settings = {} as { [key: string]: string }; - const reader = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - for (const key in fields) { - const current = this.data.get( - "settings", - key, - String(fields[key].default ?? ""), - ); - const value = - (await reader.question(`${fields[key].label} ( ${current} ): `)) || - current; - settings[key] = value; - } - reader.close(); - return settings; - } - - /** - * Update settings with values from payload - * @param payload - key/value object to save under settings store - - public async putSettings(payload: { [key: string]: string }): Promise { - for (const key in payload) { - this.data.set("settings", key, payload[key]); - } - await this.data.save(); - } - */ } diff --git a/src/services/Fairpost.ts b/src/services/Fairpost.ts index 73751a6..7991988 100644 --- a/src/services/Fairpost.ts +++ b/src/services/Fairpost.ts @@ -302,7 +302,11 @@ class Fairpost { "Connect payload must be an object", ); } - const platform = await user.addPlatform(args.platform); + const platform = await user.getPlatform(args.platform); + if (!platform.active) { + platform.active = true; + await platform.save(); + } const result = await platform.connect( operator, args.payload as object, diff --git a/src/types/PlatformDto.ts b/src/types/PlatformDto.ts index 3da61b4..97d6312 100644 --- a/src/types/PlatformDto.ts +++ b/src/types/PlatformDto.ts @@ -3,6 +3,7 @@ export default interface PlatformDto { id: string; user_id: string; active?: boolean; + connected?: boolean; // more fields added by platform [key: string]: string | string[] | number | boolean | undefined; } From 43c807f6020f9a955efd04c36026d8b02d6dc790 Mon Sep 17 00:00:00 2001 From: Fairpost Date: Tue, 4 Nov 2025 09:57:11 +0100 Subject: [PATCH 3/3] feat: On platform instances, after connect succeeds, save connected --- src/platforms/Bluesky/Bluesky.ts | 10 ++++++++-- src/platforms/LinkedIn/LinkedIn.ts | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/platforms/Bluesky/Bluesky.ts b/src/platforms/Bluesky/Bluesky.ts index 2fa7090..d7ee572 100644 --- a/src/platforms/Bluesky/Bluesky.ts +++ b/src/platforms/Bluesky/Bluesky.ts @@ -55,14 +55,20 @@ export default class Bluesky extends Platform { async connect(operator: Operator, payload?: object) { if (operator.ui === "cli") { await this.auth.connectCli(); - return await this.test(); + const test = await this.test(); + this.connected = true; + await this.save(); + return test; } if (operator.ui === "api") { if (!payload) { throw this.user.log.error("Bluesky connect requires a payload"); } await this.auth.connectApi(payload); - return await this.test(); + const test = await this.test(); + this.connected = true; + await this.save(); + return test; } throw this.user.log.error( `${this.id} connect: ui ${operator.ui} not supported`, diff --git a/src/platforms/LinkedIn/LinkedIn.ts b/src/platforms/LinkedIn/LinkedIn.ts index 3ce6bc2..afb4340 100644 --- a/src/platforms/LinkedIn/LinkedIn.ts +++ b/src/platforms/LinkedIn/LinkedIn.ts @@ -65,7 +65,10 @@ export default class LinkedIn extends Platform { async connect(operator: Operator, payload?: object) { if (operator.ui === "cli") { await this.auth.connectCli(); - return await this.test(); + const test = await this.test(); + this.connected = true; + await this.save(); + return test; } if (operator.ui === "api") { if (!payload) { @@ -74,9 +77,12 @@ export default class LinkedIn extends Platform { const result = await this.auth.connectApi(payload); const ready = "ready" in result && result.ready; if (!ready) return result; + const test = await this.test(); + this.connected = true; + await this.save(); return { ...result, - test: await this.test(), + test: test, }; }