diff --git a/README.md b/README.md index a83e650..e472471 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,7 @@ to get a new pair of tokens. fairpost: help fairpost: @userid get-user fairpost: @userid get-feed +fairpost: @userid get-fields --model=user|feed|platform|source|post [--platform=xxx] fairpost: @userid get-platform --platform=xxx fairpost: @userid put-platform --platform=xxx << payload fairpost: @userid edit-platform --platform=xxx (cli only) diff --git a/src/cli.ts b/src/cli.ts index 3a0a3d3..742d8d8 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -29,6 +29,7 @@ const COMMAND = process.argv[2]?.includes("@") const DRY_RUN = !!getOption("dry-run"); const OPERATOR = (getOption("operator") as string) ?? "admin"; const PASSWORD = (getOption("password") as string) ?? undefined; +const MODEL = (getOption("model") as string) ?? undefined; const PLATFORMS = ((getOption("platforms") as string)?.split(",") as PlatformId[]) ?? undefined; const SOURCES = (getOption("sources") as string)?.split(",") ?? undefined; @@ -94,6 +95,7 @@ async function execute(command: string): Promise { dryrun: DRY_RUN, user: USER, password: PASSWORD, + model: MODEL, platforms: PLATFORMS, platform: PLATFORM, sources: SOURCES, diff --git a/src/mappers/FeedMapper.ts b/src/mappers/FeedMapper.ts index 0aaf114..2a4be5c 100644 --- a/src/mappers/FeedMapper.ts +++ b/src/mappers/FeedMapper.ts @@ -5,7 +5,7 @@ import Feed from "../models/Feed.ts"; export default class FeedMapper extends AbstractMapper { private feed: Feed; - mapping: FieldMapping = { + static feedMapping: FieldMapping = { model: { type: "string", label: "Model", @@ -38,6 +38,7 @@ export default class FeedMapper extends AbstractMapper { required: false, }, }; + mapping = FeedMapper.feedMapping; constructor(feed: Feed) { super(feed.user); diff --git a/src/mappers/PlatformMapper.ts b/src/mappers/PlatformMapper.ts index a55ab62..58a9ec3 100644 --- a/src/mappers/PlatformMapper.ts +++ b/src/mappers/PlatformMapper.ts @@ -5,7 +5,7 @@ import Platform from "../models/Platform.ts"; export default class PlatformMapper extends AbstractMapper { private platform: Platform; - mapping: FieldMapping = { + private static platformMapping: FieldMapping = { model: { type: "string", label: "Model", @@ -31,9 +31,11 @@ export default class PlatformMapper extends AbstractMapper { set: ["managePlatforms"], }, // more fields from platform.settings - // added in constructor + // added in mapper constructor }; + mapping = structuredClone(PlatformMapper.platformMapping); + constructor(platform: Platform) { super(platform.user); this.platform = platform; diff --git a/src/mappers/PostMapper.ts b/src/mappers/PostMapper.ts index b1128eb..ab4d0c6 100644 --- a/src/mappers/PostMapper.ts +++ b/src/mappers/PostMapper.ts @@ -1,11 +1,11 @@ import AbstractMapper from "./AbstractMapper.ts"; -import { PostDto, FieldMapping, FileInfo } from "../types/index.ts"; +import { PostDto, FileInfo, FieldMapping } from "../types/index.ts"; import Operator from "../models/Operator.ts"; import Post from "../models/Post.ts"; export default class PostMapper extends AbstractMapper { private post: Post; - mapping: FieldMapping = { + static postMapping: FieldMapping = { model: { type: "string", label: "Model", @@ -121,6 +121,7 @@ export default class PostMapper extends AbstractMapper { set: ["none"], }, }; + mapping = PostMapper.postMapping; constructor(post: Post) { super(post.platform.user); diff --git a/src/mappers/SourceMapper.ts b/src/mappers/SourceMapper.ts index 7c3a758..62a3604 100644 --- a/src/mappers/SourceMapper.ts +++ b/src/mappers/SourceMapper.ts @@ -1,11 +1,11 @@ import AbstractMapper from "./AbstractMapper.ts"; -import { SourceDto, FieldMapping, FileInfo } from "../types/index.ts"; +import { SourceDto, FileInfo, FieldMapping } from "../types/index.ts"; import Operator from "../models/Operator.ts"; import Source from "../models/Source.ts"; export default class SourceMapper extends AbstractMapper { private source: Source; - mapping: FieldMapping = { + static sourceMapping: FieldMapping = { model: { type: "string", label: "Model", @@ -49,6 +49,7 @@ export default class SourceMapper extends AbstractMapper { set: ["manageSources"], }, }; + mapping = SourceMapper.sourceMapping; constructor(source: Source) { super(source.feed.user); diff --git a/src/mappers/UserMapper.ts b/src/mappers/UserMapper.ts index 57baa1e..3599039 100644 --- a/src/mappers/UserMapper.ts +++ b/src/mappers/UserMapper.ts @@ -3,7 +3,7 @@ import { UserDto, FieldMapping } from "../types/index.ts"; import Operator from "../models/Operator.ts"; export default class UserMapper extends AbstractMapper { - mapping: FieldMapping = { + static userMapping: FieldMapping = { model: { type: "string", label: "Model", @@ -39,6 +39,7 @@ export default class UserMapper extends AbstractMapper { required: false, }, }; + mapping = UserMapper.userMapping; /** * Return a dto based on the operator and operation diff --git a/src/models/Operator.ts b/src/models/Operator.ts index b06db67..83af827 100644 --- a/src/models/Operator.ts +++ b/src/models/Operator.ts @@ -1,4 +1,10 @@ import User from "./User.ts"; +import { FieldMapping, ProcessedFieldMapping } from "../types/index.ts"; +import Platform from "../models/Platform.ts"; +import UserMapper from "../mappers/UserMapper.ts"; +import FeedMapper from "../mappers/FeedMapper.ts"; +import SourceMapper from "../mappers/SourceMapper.ts"; +import PostMapper from "../mappers/PostMapper.ts"; /** * Operator - represents the user executing an operation or command. @@ -79,4 +85,67 @@ export default class Operator { //user?.log.info(permissions); return permissions; } + + public getFieldMapping( + user: User, + model: string, + instance?: object, + ): ProcessedFieldMapping { + let rawFieldMapping: FieldMapping | undefined = undefined; + let processedFieldMapping: ProcessedFieldMapping = {}; + switch (model) { + case "user": + rawFieldMapping = UserMapper.userMapping; + break; + + case "feed": + rawFieldMapping = FeedMapper.feedMapping; + break; + + case "source": + rawFieldMapping = SourceMapper.sourceMapping; + break; + + case "platform": + if (!instance || !(instance instanceof Platform)) { + throw user.log.error( + "Operator.getFieldMapping", + "Platform is required", + ); + } + const platform = instance as Platform; + rawFieldMapping = platform.mapper.mapping; + break; + + case "post": + rawFieldMapping = PostMapper.postMapping; + break; + } + if (!rawFieldMapping) { + throw user.log.error("Operator.getFieldMapping: no such mapping", model); + } + const permissions = this.getPermissions(user); + for (const fieldName of Object.keys(rawFieldMapping)) { + const rawField = rawFieldMapping[fieldName]; + const processedField = { ...rawField, get: true, set: true }; + for (const operation of ["get", "set"] as const) { + if (rawField[operation].includes("any")) + processedField[operation] = true; + else if (rawField[operation].includes("none")) + processedField[operation] = false; + else if ( + rawField[operation].some( + (permission) => + permission in permissions && + permissions[permission as keyof typeof permissions], + ) + ) + processedField[operation] = true; + } + if (processedField["get"]) { + processedFieldMapping[fieldName] = processedField; + } + } + return processedFieldMapping; + } } diff --git a/src/models/Platform.ts b/src/models/Platform.ts index 895eebd..0128560 100644 --- a/src/models/Platform.ts +++ b/src/models/Platform.ts @@ -23,7 +23,7 @@ export default class Platform { defaultBody: string = "Fairpost feed"; assetsFolder: string = "_fairpost"; postFileName: string = "post.json"; - mapper: PlatformMapper; + mapper!: PlatformMapper; // child *must* set this settings: FieldMapping = {}; interval: number; constructor(user: User) { @@ -32,7 +32,6 @@ export default class Platform { this.interval = Number( this.user.data.get("settings", "FEED_INTERVAL", "7"), ); - this.mapper = new PlatformMapper(this); } /** diff --git a/src/services/Fairpost.ts b/src/services/Fairpost.ts index 7b0a237..86c50f8 100644 --- a/src/services/Fairpost.ts +++ b/src/services/Fairpost.ts @@ -1,7 +1,11 @@ import log4js from "log4js"; import log4jsConfig from "../config/log4js.json" with { type: "json" }; -import { CommandArguments, CombinedResult } from "../types/index.ts"; +import { + CommandArguments, + CombinedResult, + ProcessedFieldMapping, +} from "../types/index.ts"; import { PlatformId } from "../platforms/index.ts"; import { FeedDto, @@ -20,6 +24,7 @@ import Operator from "../models/Operator.ts"; import User from "../models/User.ts"; type FairpostOutput = + | ProcessedFieldMapping | FeedDto | PlatformDto | PostDto @@ -188,6 +193,34 @@ class Fairpost { } */ + case "get-fields": { + if (!permissions.manageAccount) { + throw new Error("Missing permissions for command " + command); + } + if (!user) { + throw new Error("user is required for command " + command); + } + if (!args.model) { + throw user.log.error( + "CommandHandler " + command, + "Missing argument: model", + ); + } + let instance: object | undefined = undefined; + if (args.model === "platform") { + if (!args.platform) { + throw user.log.error( + "CommandHandler " + command, + "Missing argument: platform", + ); + } + instance = user.getPlatform(args.platform); + } + + output = operator.getFieldMapping(user, args.model, instance); + break; + } + case "refresh-token": { if (!permissions.manageAccount) { throw new Error("Missing permissions for command " + command); @@ -972,6 +1005,7 @@ class Fairpost { `${cmd} help`, `${cmd} @userid get-user`, `${cmd} @userid get-feed`, + `${cmd} @userid get-fields --model=user|feed|platform|source|post [--platform=xxx]`, `${cmd} @userid get-platform --platform=xxx`, `${cmd} @userid put-platform --platform=xxx << payload`, `${cmd} @userid edit-platform --platform=xxx (cli only)`, diff --git a/src/services/Server.ts b/src/services/Server.ts index ffdc95d..00224e5 100644 --- a/src/services/Server.ts +++ b/src/services/Server.ts @@ -80,6 +80,7 @@ export default class Server { // read other params from query const password = parsed.searchParams.get("password") || undefined; + const model = parsed.searchParams.get("model") || undefined; const dryrun = parsed.searchParams.get("dry-run") === "true"; const date = parsed.searchParams.get("date"); const post = parsed.searchParams.get("post"); @@ -112,6 +113,7 @@ export default class Server { const args = { password: password, dryrun: dryrun || undefined, + model: model, platforms: platforms, platform: platform, sources: sources, diff --git a/src/types/CommandArguments.ts b/src/types/CommandArguments.ts index 5a0d85a..011d040 100644 --- a/src/types/CommandArguments.ts +++ b/src/types/CommandArguments.ts @@ -9,6 +9,7 @@ export default interface CommandArguments { dryrun?: boolean; user?: string; password?: string; + model?: string; platforms?: PlatformId[]; platform?: PlatformId; sources?: string[]; diff --git a/src/types/FieldMapping.ts b/src/types/FieldMapping.ts index 566c8d8..c4c53e1 100644 --- a/src/types/FieldMapping.ts +++ b/src/types/FieldMapping.ts @@ -22,3 +22,12 @@ export default interface FieldMapping { default?: string | string[] | number | boolean | Date | object; }; } + +// after applying operator and user, get and set +// can be represented by simple booleans: +export interface ProcessedFieldMapping { + [field: string]: Omit & { + get: boolean; + set: boolean; + }; +} diff --git a/src/types/index.ts b/src/types/index.ts index 6e707b9..71e8bad 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,6 +1,7 @@ export type { default as CommandArguments } from "./CommandArguments.ts"; export type { default as CombinedResult } from "./CombinedResult.ts"; export type { default as FieldMapping } from "./FieldMapping.ts"; +export type { ProcessedFieldMapping } from "./FieldMapping.ts"; export type { default as Dto } from "./Dto.ts"; export type { default as FeedDto } from "./FeedDto.ts"; export { default as FileGroup } from "./FileGroup.ts";