Lichess4545/Chesster

View on GitHub
src/config.ts

Summary

Maintainability
C
1 day
Test Coverage
A
100%
// -----------------------------------------------------------------------------
// Types and parsing for the chesster config.
// -----------------------------------------------------------------------------
import moment from 'moment'
import {
    Decoder,
    array,
    object,
    number,
    string,
    boolean,
    andThen,
    oneOf,
    dict,
} from 'type-safe-json-decoder'

export interface Heltour {
    token: string
    baseEndpoint: string
}

export interface HeltourLeagueConfig extends Heltour {
    leagueTag: string
}

export const HeltourDecoder: Decoder<Heltour> = object(
    ['token', string()],
    ['baseEndpoint', string()],
    (token, baseEndpoint) => ({ token, baseEndpoint })
)
export const HeltourLeagueConfigDecoder: Decoder<HeltourLeagueConfig> = andThen(
    HeltourDecoder,
    (heltour) =>
        object(['leagueTag', string()], (leagueTag) => ({
            ...heltour,
            leagueTag,
        }))
)

export interface Welcome {
    channel: string
}
export const WelcomeDecoder: Decoder<Welcome> = object(
    ['channel', string()],
    (channel) => ({ channel })
)

export interface Results {
    channel: string
    channelId: string
}
export const ResultsDecoder: Decoder<Results> = object(
    ['channel', string()],
    ['channelId', string()],
    (channel, channelId) => ({ channel, channelId })
)

export interface GameLinksClock {
    initial: number
    increment: number
}
export const GameLinksClockDecoder: Decoder<GameLinksClock> = object(
    ['initial', number()],
    ['increment', number()],
    (initial, increment) => ({ initial, increment })
)

export interface GameLinks {
    channel: string
    channelId: string
    clock: GameLinksClock
    rated: boolean
    variant: string
}
export const GameLinksDecoder: Decoder<GameLinks> = object(
    ['channel', string()],
    ['channelId', string()],
    ['clock', GameLinksClockDecoder],
    ['rated', boolean()],
    ['variant', string()],
    (channel, channelId, clock, rated, variant) => ({
        channel,
        channelId,
        clock,
        rated,
        variant,
    })
)

export interface SchedulingExtrema {
    isoWeekday: number
    hour: number
    minute: number
    warningHours: number
    referenceDate: moment.Moment | undefined
}
export const SchedulingExtremaDecoder: Decoder<SchedulingExtrema> = object(
    ['isoWeekday', number()],
    ['hour', number()],
    ['minute', number()],
    ['warningHours', number()],
    (isoWeekday, hour, minute, warningHours) => ({
        isoWeekday,
        hour,
        minute,
        warningHours,
        referenceDate: undefined,
    })
)
export interface Scheduling {
    extrema: SchedulingExtrema
    warningMessage: string
    lateMessage: string
    format: string
    channel: string
}
export const SchedulingDecoder: Decoder<Scheduling> = object(
    ['extrema', SchedulingExtremaDecoder],
    ['warningMessage', string()],
    ['lateMessage', string()],
    ['format', string()],
    ['channel', string()],
    (extrema, warningMessage, lateMessage, format, channel) => ({
        extrema,
        warningMessage,
        lateMessage,
        format,
        channel,
    })
)
export interface Alternate {
    channelId: string
}
export const AlternateDecoder: Decoder<Alternate> = object(
    ['channelId', string()],
    (channelId) => ({ channelId })
)
export interface LeagueLinks {
    faq: string
    rules: string
    league: string
    pairings: string
    standings: string
    guide: string
    captains: string
    registration: string
    availability: string
    nominate: string
    notifications: string
}
export const LeagueLinksDecoder: Decoder<LeagueLinks> = object(
    ['faq', string()],
    ['rules', string()],
    ['league', string()],
    ['pairings', string()],
    ['standings', string()],
    ['guide', string()],
    ['captains', string()],
    ['registration', string()],
    ['availability', string()],
    ['nominate', string()],
    ['notifications', string()],
    (
        faq,
        rules,
        league,
        pairings,
        standings,
        guide,
        captains,
        registration,
        availability,
        nominate,
        notifications
    ) => ({
        faq,
        rules,
        league,
        pairings,
        standings,
        guide,
        captains,
        registration,
        availability,
        nominate,
        notifications,
    })
)

export interface League {
    name: string
    alsoKnownAs: string[]
    heltour: HeltourLeagueConfig
    results: Results
    gamelinks: GameLinks
    scheduling: Scheduling
    links: LeagueLinks
    alternate?: Alternate
}

export const LeagueWithoutAlternateDecoder: Decoder<League> = object(
    ['name', string()],
    ['alsoKnownAs', array(string())],
    ['heltour', HeltourLeagueConfigDecoder],
    ['results', ResultsDecoder],
    ['gamelinks', GameLinksDecoder],
    ['scheduling', SchedulingDecoder],
    ['links', LeagueLinksDecoder],
    (name, alsoKnownAs, heltour, results, gamelinks, scheduling, links) => ({
        name,
        alsoKnownAs,
        heltour,
        results,
        gamelinks,
        scheduling,
        links,
        alternate: undefined,
    })
)
export const LeagueWithAlternateDecoder: Decoder<League> = andThen(
    LeagueWithoutAlternateDecoder,
    (leagueWithoutAlternate) =>
        object(['alternate', AlternateDecoder], (alternate) => ({
            ...leagueWithoutAlternate,
            alternate,
        }))
)
export const LeagueDecoder: Decoder<League> = oneOf(
    LeagueWithAlternateDecoder,
    LeagueWithoutAlternateDecoder
)
export type SlackTokens = Record<string, string>
export const SlackTokensDecoder: Decoder<SlackTokens> = dict(string())
export interface Winston {
    domain: string
    channel: string
    username: string
    level: string
    handleExceptions: boolean
}
export const WinstonDecoder: Decoder<Winston> = object(
    ['domain', string()],
    ['channel', string()],
    ['username', string()],
    ['level', string()],
    ['handleExceptions', boolean()],
    (domain, channel, username, level, handleExceptions) => ({
        domain,
        channel,
        username,
        level,
        handleExceptions,
    })
)
export interface Links {
    source: string
}
export const LinksDecoder: Decoder<Links> = object(
    ['source', string()],
    (source) => ({ source })
)
export type ChannelMap = Record<string, string>
export const ChannelMapDecoder: Decoder<ChannelMap> = dict(string())

export interface MessageForwarding {
    channelId: string
}
export const MessageForwardingDecoder: Decoder<MessageForwarding> = object(
    ['channelId', string()],
    (channelId) => ({ channelId })
)

export type ChannelModMap = Record<string, string[]>
export const ChannelModMapDecoder: Decoder<ChannelModMap> = dict(
    array(string())
)

export interface Pool {
    max: number
    min: number
    idle: number
}
export const PoolDecoder: Decoder<Pool> = object(
    ['max', number()],
    ['min', number()],
    ['idle', number()],
    (max, min, idle) => ({ max, min, idle })
)
export interface Database {
    name: string
    username: string
    password: string
    host: string
    dialect: string
    logging: boolean
    pool: Pool
}
export const DatabaseDecoder: Decoder<Database> = object(
    ['name', string()],
    ['username', string()],
    ['password', string()],
    ['host', string()],
    ['dialect', string()],
    ['logging', boolean()],
    ['pool', PoolDecoder],
    (name, username, password, host, dialect, logging, pool) => ({
        name,
        username,
        password,
        host,
        dialect,
        logging,
        pool,
    })
)

export interface ChessterConfig {
    database: Database
    heltour: Heltour
    storage: string
    watcherBaseURL: string
    watcherToken: string
    slackTokens: SlackTokens
    winston: Winston
    links: Links
    leagues: Record<string, League>
    channelMap: ChannelMap
    messageForwarding: MessageForwarding
    pingMods: ChannelModMap
    welcome: Welcome
}
export const ChessterConfigDecoder: Decoder<ChessterConfig> = object(
    ['database', DatabaseDecoder],
    ['heltour', HeltourDecoder],
    ['storage', string()],
    ['watcherBaseURL', string()],
    ['watcherToken', string()],
    ['slackTokens', SlackTokensDecoder],
    ['winston', WinstonDecoder],
    ['links', LinksDecoder],
    ['leagues', dict(LeagueDecoder)],
    ['channelMap', ChannelMapDecoder],
    ['messageForwarding', MessageForwardingDecoder],
    ['pingMods', ChannelModMapDecoder],
    ['welcome', WelcomeDecoder],
    (
        database,
        heltour,
        storage,
        watcherBaseURL,
        watcherToken,
        slackTokens,
        winston,
        links,
        leagues,
        channelMap,
        messageForwarding,
        pingMods,
        welcome
    ) => ({
        database,
        heltour,
        storage,
        watcherBaseURL,
        watcherToken,
        slackTokens,
        winston,
        links,
        leagues,
        channelMap,
        messageForwarding,
        pingMods,
        welcome,
    })
)