Use preprocess for instance v1 to v2 conversion, add test for instance schema
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
144e13e674
commit
4a6a76ddd9
3 changed files with 317 additions and 92 deletions
|
@ -4,7 +4,6 @@ import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||||
import { ADMIN_CONFIG_UPDATE_REQUEST, ADMIN_CONFIG_UPDATE_SUCCESS } from 'soapbox/actions/admin';
|
import { ADMIN_CONFIG_UPDATE_REQUEST, ADMIN_CONFIG_UPDATE_SUCCESS } from 'soapbox/actions/admin';
|
||||||
import { PLEROMA_PRELOAD_IMPORT } from 'soapbox/actions/preload';
|
import { PLEROMA_PRELOAD_IMPORT } from 'soapbox/actions/preload';
|
||||||
import { type Instance, instanceSchema } from 'soapbox/schemas';
|
import { type Instance, instanceSchema } from 'soapbox/schemas';
|
||||||
import { instanceV1ToV2 } from 'soapbox/schemas/instance';
|
|
||||||
import KVStore from 'soapbox/storage/kv-store';
|
import KVStore from 'soapbox/storage/kv-store';
|
||||||
import { ConfigDB } from 'soapbox/utils/config-db';
|
import { ConfigDB } from 'soapbox/utils/config-db';
|
||||||
|
|
||||||
|
@ -20,8 +19,7 @@ import type { APIEntity } from 'soapbox/types/entities';
|
||||||
const initialState: Instance = instanceSchema.parse({});
|
const initialState: Instance = instanceSchema.parse({});
|
||||||
|
|
||||||
const importInstance = (_state: typeof initialState, instance: APIEntity) => {
|
const importInstance = (_state: typeof initialState, instance: APIEntity) => {
|
||||||
if (typeof instance.domain === 'string') return instanceSchema.parse(instance);
|
return instanceSchema.parse(instance);
|
||||||
return instanceV1ToV2.parse(instance);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const preloadImport = (state: typeof initialState, action: Record<string, any>, path: string) => {
|
const preloadImport = (state: typeof initialState, action: Record<string, any>, path: string) => {
|
||||||
|
|
214
src/schemas/instance.test.ts
Normal file
214
src/schemas/instance.test.ts
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
import { instanceSchema } from './instance';
|
||||||
|
|
||||||
|
describe('instanceSchema.parse()', () => {
|
||||||
|
it('normalizes an empty Map', () => {
|
||||||
|
const expected = {
|
||||||
|
configuration: {
|
||||||
|
media_attachments: {},
|
||||||
|
chats: {
|
||||||
|
max_characters: 5000,
|
||||||
|
max_media_attachments: 1,
|
||||||
|
},
|
||||||
|
groups: {
|
||||||
|
max_characters_name: 50,
|
||||||
|
max_characters_description: 160,
|
||||||
|
},
|
||||||
|
polls: {
|
||||||
|
max_options: 4,
|
||||||
|
max_characters_per_option: 25,
|
||||||
|
min_expiration: 300,
|
||||||
|
max_expiration: 2629746,
|
||||||
|
},
|
||||||
|
statuses: {
|
||||||
|
max_characters: 500,
|
||||||
|
max_media_attachments: 4,
|
||||||
|
},
|
||||||
|
translation: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
urls: {},
|
||||||
|
},
|
||||||
|
contact: {
|
||||||
|
email: '',
|
||||||
|
},
|
||||||
|
description: '',
|
||||||
|
domain: '',
|
||||||
|
feature_quote: false,
|
||||||
|
fedibird_capabilities: [],
|
||||||
|
languages: [],
|
||||||
|
pleroma: {
|
||||||
|
metadata: {
|
||||||
|
account_activation_required: false,
|
||||||
|
birthday_min_age: 0,
|
||||||
|
birthday_required: false,
|
||||||
|
description_limit: 1500,
|
||||||
|
features: [],
|
||||||
|
federation: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stats: {},
|
||||||
|
},
|
||||||
|
registrations: {
|
||||||
|
approval_required: false,
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
rules: [],
|
||||||
|
stats: {},
|
||||||
|
title: '',
|
||||||
|
thumbnail: {
|
||||||
|
url: '',
|
||||||
|
},
|
||||||
|
usage: {
|
||||||
|
users: {
|
||||||
|
active_month: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
version: '0.0.0',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = instanceSchema.parse({});
|
||||||
|
expect(result).toMatchObject(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normalizes Pleroma instance with Mastodon configuration format', () => {
|
||||||
|
const instance = require('soapbox/__fixtures__/pleroma-instance.json');
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
configuration: {
|
||||||
|
statuses: {
|
||||||
|
max_characters: 5000,
|
||||||
|
max_media_attachments: Infinity,
|
||||||
|
},
|
||||||
|
polls: {
|
||||||
|
max_options: 20,
|
||||||
|
max_characters_per_option: 200,
|
||||||
|
min_expiration: 0,
|
||||||
|
max_expiration: 31536000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = instanceSchema.parse(instance);
|
||||||
|
expect(result).toMatchObject(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normalizes Mastodon instance with retained configuration', () => {
|
||||||
|
const instance = require('soapbox/__fixtures__/mastodon-instance.json');
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
configuration: {
|
||||||
|
statuses: {
|
||||||
|
max_characters: 500,
|
||||||
|
max_media_attachments: 4,
|
||||||
|
characters_reserved_per_url: 23,
|
||||||
|
},
|
||||||
|
media_attachments: {
|
||||||
|
image_size_limit: 10485760,
|
||||||
|
image_matrix_limit: 16777216,
|
||||||
|
video_size_limit: 41943040,
|
||||||
|
video_frame_rate_limit: 60,
|
||||||
|
video_matrix_limit: 2304000,
|
||||||
|
},
|
||||||
|
polls: {
|
||||||
|
max_options: 4,
|
||||||
|
max_characters_per_option: 50,
|
||||||
|
min_expiration: 300,
|
||||||
|
max_expiration: 2629746,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = instanceSchema.parse(instance);
|
||||||
|
expect(result).toMatchObject(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normalizes Mastodon 3.0.0 instance with default configuration', () => {
|
||||||
|
const instance = require('soapbox/__fixtures__/mastodon-3.0.0-instance.json');
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
configuration: {
|
||||||
|
statuses: {
|
||||||
|
max_characters: 500,
|
||||||
|
max_media_attachments: 4,
|
||||||
|
},
|
||||||
|
polls: {
|
||||||
|
max_options: 4,
|
||||||
|
max_characters_per_option: 25,
|
||||||
|
min_expiration: 300,
|
||||||
|
max_expiration: 2629746,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = instanceSchema.parse(instance);
|
||||||
|
expect(result).toMatchObject(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normalizes Fedibird instance', () => {
|
||||||
|
const instance = require('soapbox/__fixtures__/fedibird-instance.json');
|
||||||
|
const result = instanceSchema.parse(instance);
|
||||||
|
|
||||||
|
// Sets description_limit
|
||||||
|
expect(result.pleroma.metadata.description_limit).toEqual(1500);
|
||||||
|
|
||||||
|
// Preserves fedibird_capabilities
|
||||||
|
expect(result.fedibird_capabilities).toEqual(instance.fedibird_capabilities);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normalizes Mitra instance', () => {
|
||||||
|
const instance = require('soapbox/__fixtures__/mitra-instance.json');
|
||||||
|
const result = instanceSchema.parse(instance);
|
||||||
|
|
||||||
|
// Adds configuration and description_limit
|
||||||
|
expect(result.configuration).toBeTruthy();
|
||||||
|
expect(result.pleroma.metadata.description_limit).toBe(1500);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normalizes GoToSocial instance', () => {
|
||||||
|
const instance = require('soapbox/__fixtures__/gotosocial-instance.json');
|
||||||
|
const result = instanceSchema.parse(instance);
|
||||||
|
|
||||||
|
// Normalizes max_toot_chars
|
||||||
|
expect(result.configuration.statuses.max_characters).toEqual(5000);
|
||||||
|
expect('max_toot_chars' in result).toBe(false);
|
||||||
|
|
||||||
|
// Adds configuration and description_limit
|
||||||
|
expect(result.configuration).toBeTruthy();
|
||||||
|
expect(result.pleroma.metadata.description_limit).toBe(1500);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normalizes Friendica instance', () => {
|
||||||
|
const instance = require('soapbox/__fixtures__/friendica-instance.json');
|
||||||
|
const result = instanceSchema.parse(instance);
|
||||||
|
|
||||||
|
// Normalizes max_toot_chars
|
||||||
|
expect(result.configuration.statuses.max_characters).toEqual(200000);
|
||||||
|
expect('max_toot_chars' in result).toBe(false);
|
||||||
|
|
||||||
|
// Adds configuration and description_limit
|
||||||
|
expect(result.configuration).toBeTruthy();
|
||||||
|
expect(result.pleroma.metadata.description_limit).toBe(1500);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normalizes a Mastodon RC version', () => {
|
||||||
|
const instance = require('soapbox/__fixtures__/mastodon-instance-rc.json');
|
||||||
|
const result = instanceSchema.parse(instance);
|
||||||
|
|
||||||
|
expect(result.version).toEqual('3.5.0-rc1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normalizes Pixelfed instance', () => {
|
||||||
|
const instance = require('soapbox/__fixtures__/pixelfed-instance.json');
|
||||||
|
const result = instanceSchema.parse(instance);
|
||||||
|
expect(result.title).toBe('pixelfed');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renames Akkoma to Pleroma', () => {
|
||||||
|
const instance = require('soapbox/__fixtures__/akkoma-instance.json');
|
||||||
|
const result = instanceSchema.parse(instance);
|
||||||
|
|
||||||
|
expect(result.version).toEqual('2.7.2 (compatible; Pleroma 2.4.50+akkoma)');
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,11 +1,15 @@
|
||||||
/* eslint sort-keys: "error" */
|
/* eslint sort-keys: "error" */
|
||||||
import z from 'zod';
|
import z from 'zod';
|
||||||
|
|
||||||
|
import { PLEROMA, parseVersion } from 'soapbox/utils/features';
|
||||||
|
|
||||||
import { accountSchema } from './account';
|
import { accountSchema } from './account';
|
||||||
import { mrfSimpleSchema } from './pleroma';
|
import { mrfSimpleSchema } from './pleroma';
|
||||||
import { ruleSchema } from './rule';
|
import { ruleSchema } from './rule';
|
||||||
import { coerceObject, filteredArray, mimeSchema } from './utils';
|
import { coerceObject, filteredArray, mimeSchema } from './utils';
|
||||||
|
|
||||||
|
const getAttachmentLimit = (software: string | null) => software === PLEROMA ? Infinity : 4;
|
||||||
|
|
||||||
const fixVersion = (version: string) => {
|
const fixVersion = (version: string) => {
|
||||||
// Handle Mastodon release candidates
|
// Handle Mastodon release candidates
|
||||||
if (new RegExp(/[0-9.]+rc[0-9]+/g).test(version)) {
|
if (new RegExp(/[0-9.]+rc[0-9]+/g).test(version)) {
|
||||||
|
@ -50,8 +54,10 @@ const configurationSchema = coerceObject({
|
||||||
min_expiration: z.number().optional().catch(undefined),
|
min_expiration: z.number().optional().catch(undefined),
|
||||||
}),
|
}),
|
||||||
statuses: coerceObject({
|
statuses: coerceObject({
|
||||||
|
characters_reserved_per_url: z.number().optional().catch(undefined),
|
||||||
max_characters: z.number().optional().catch(undefined),
|
max_characters: z.number().optional().catch(undefined),
|
||||||
max_media_attachments: z.number().optional().catch(undefined),
|
max_media_attachments: z.number().optional().catch(undefined),
|
||||||
|
|
||||||
}),
|
}),
|
||||||
translation: coerceObject({
|
translation: coerceObject({
|
||||||
enabled: z.boolean().catch(false),
|
enabled: z.boolean().catch(false),
|
||||||
|
@ -131,9 +137,9 @@ const registrations = coerceObject({
|
||||||
});
|
});
|
||||||
|
|
||||||
const statsSchema = coerceObject({
|
const statsSchema = coerceObject({
|
||||||
domain_count: z.number().catch(0),
|
domain_count: z.number().optional().catch(undefined),
|
||||||
status_count: z.number().catch(0),
|
status_count: z.number().optional().catch(undefined),
|
||||||
user_count: z.number().catch(0),
|
user_count: z.number().optional().catch(undefined),
|
||||||
});
|
});
|
||||||
|
|
||||||
const thumbnailSchema = coerceObject({
|
const thumbnailSchema = coerceObject({
|
||||||
|
@ -146,7 +152,96 @@ const usageSchema = coerceObject({
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const instanceSchema = coerceObject({
|
const instanceV1Schema = coerceObject({
|
||||||
|
approval_required: z.boolean().catch(false),
|
||||||
|
configuration: configurationSchema,
|
||||||
|
contact_account: accountSchema.optional().catch(undefined),
|
||||||
|
description: z.string().catch(''),
|
||||||
|
description_limit: z.number().catch(1500),
|
||||||
|
email: z.string().email().catch(''),
|
||||||
|
feature_quote: z.boolean().catch(false),
|
||||||
|
fedibird_capabilities: z.array(z.string()).catch([]),
|
||||||
|
languages: z.string().array().catch([]),
|
||||||
|
max_media_attachments: z.number().optional().catch(undefined),
|
||||||
|
max_toot_chars: z.number().optional().catch(undefined),
|
||||||
|
nostr: nostrSchema.optional().catch(undefined),
|
||||||
|
pleroma: pleromaSchema,
|
||||||
|
poll_limits: pleromaPollLimitsSchema,
|
||||||
|
registrations: z.boolean().catch(false),
|
||||||
|
rules: filteredArray(ruleSchema),
|
||||||
|
short_description: z.string().catch(''),
|
||||||
|
stats: statsSchema,
|
||||||
|
thumbnail: z.string().catch(''),
|
||||||
|
title: z.string().catch(''),
|
||||||
|
urls: coerceObject({
|
||||||
|
streaming_api: z.string().url().optional().catch(undefined),
|
||||||
|
}),
|
||||||
|
usage: usageSchema,
|
||||||
|
version: z.string().catch('0.0.0'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const instanceSchema = z.preprocess((data: any) => {
|
||||||
|
if (data.domain) return data;
|
||||||
|
|
||||||
|
const {
|
||||||
|
approval_required,
|
||||||
|
configuration,
|
||||||
|
contact_account,
|
||||||
|
description,
|
||||||
|
description_limit,
|
||||||
|
email,
|
||||||
|
max_media_attachments,
|
||||||
|
max_toot_chars,
|
||||||
|
poll_limits,
|
||||||
|
pleroma,
|
||||||
|
registrations,
|
||||||
|
short_description,
|
||||||
|
thumbnail,
|
||||||
|
urls,
|
||||||
|
...instance
|
||||||
|
} = instanceV1Schema.parse(data);
|
||||||
|
|
||||||
|
const { software } = parseVersion(instance.version);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...instance,
|
||||||
|
configuration: {
|
||||||
|
...configuration,
|
||||||
|
polls: {
|
||||||
|
...configuration.polls,
|
||||||
|
max_characters_per_option: configuration.polls.max_characters_per_option ?? poll_limits.max_option_chars ?? 25,
|
||||||
|
max_expiration: configuration.polls.max_expiration ?? poll_limits.max_expiration ?? 2629746,
|
||||||
|
max_options: configuration.polls.max_options ?? poll_limits.max_options ?? 4,
|
||||||
|
min_expiration: configuration.polls.min_expiration ?? poll_limits.min_expiration ?? 300,
|
||||||
|
},
|
||||||
|
statuses: {
|
||||||
|
...configuration.statuses,
|
||||||
|
max_characters: configuration.statuses.max_characters ?? max_toot_chars ?? 500,
|
||||||
|
max_media_attachments: configuration.statuses.max_media_attachments ?? max_media_attachments ?? getAttachmentLimit(software),
|
||||||
|
},
|
||||||
|
urls: {
|
||||||
|
streaming: urls.streaming_api,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
contact: {
|
||||||
|
account: contact_account,
|
||||||
|
email: email,
|
||||||
|
},
|
||||||
|
description: short_description || description,
|
||||||
|
pleroma: {
|
||||||
|
...pleroma,
|
||||||
|
metadata: {
|
||||||
|
...pleroma.metadata,
|
||||||
|
description_limit,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
registrations: {
|
||||||
|
approval_required: approval_required,
|
||||||
|
enabled: registrations,
|
||||||
|
},
|
||||||
|
thumbnail: { url: thumbnail },
|
||||||
|
};
|
||||||
|
}, coerceObject({
|
||||||
configuration: configurationSchema,
|
configuration: configurationSchema,
|
||||||
contact: contactSchema,
|
contact: contactSchema,
|
||||||
description: z.string().catch(''),
|
description: z.string().catch(''),
|
||||||
|
@ -162,7 +257,7 @@ const instanceSchema = coerceObject({
|
||||||
thumbnail: thumbnailSchema,
|
thumbnail: thumbnailSchema,
|
||||||
title: z.string().catch(''),
|
title: z.string().catch(''),
|
||||||
usage: usageSchema,
|
usage: usageSchema,
|
||||||
version: z.string().catch(''),
|
version: z.string().catch('0.0.0'),
|
||||||
}).transform(({ configuration, ...instance }) => {
|
}).transform(({ configuration, ...instance }) => {
|
||||||
const version = fixVersion(instance.version);
|
const version = fixVersion(instance.version);
|
||||||
|
|
||||||
|
@ -189,90 +284,8 @@ const instanceSchema = coerceObject({
|
||||||
},
|
},
|
||||||
version,
|
version,
|
||||||
};
|
};
|
||||||
});
|
}));
|
||||||
|
|
||||||
const instanceV1ToV2 = coerceObject({
|
|
||||||
approval_required: z.boolean().catch(false),
|
|
||||||
configuration: configurationSchema,
|
|
||||||
contact_account: accountSchema.optional().catch(undefined),
|
|
||||||
description: z.string().catch(''),
|
|
||||||
description_limit: z.number().catch(1500),
|
|
||||||
email: z.string().email().catch(''),
|
|
||||||
feature_quote: z.boolean().catch(false),
|
|
||||||
fedibird_capabilities: z.array(z.string()).catch([]),
|
|
||||||
languages: z.string().array().catch([]),
|
|
||||||
max_media_attachments: z.number().optional().catch(undefined),
|
|
||||||
max_toot_chars: z.number().optional().catch(undefined),
|
|
||||||
nostr: nostrSchema.optional().catch(undefined),
|
|
||||||
pleroma: pleromaSchema,
|
|
||||||
poll_limits: pleromaPollLimitsSchema,
|
|
||||||
registrations: z.boolean().catch(false),
|
|
||||||
rules: filteredArray(ruleSchema),
|
|
||||||
short_description: z.string().catch(''),
|
|
||||||
stats: statsSchema,
|
|
||||||
thumbnail: z.string().catch(''),
|
|
||||||
title: z.string().catch(''),
|
|
||||||
urls: coerceObject({
|
|
||||||
streaming_api: z.string().url().optional().catch(undefined),
|
|
||||||
}),
|
|
||||||
usage: usageSchema,
|
|
||||||
version: z.string().catch(''),
|
|
||||||
}).transform(({
|
|
||||||
approval_required,
|
|
||||||
configuration,
|
|
||||||
contact_account,
|
|
||||||
description,
|
|
||||||
description_limit,
|
|
||||||
email,
|
|
||||||
max_media_attachments,
|
|
||||||
max_toot_chars,
|
|
||||||
poll_limits,
|
|
||||||
pleroma,
|
|
||||||
registrations,
|
|
||||||
short_description,
|
|
||||||
thumbnail,
|
|
||||||
urls,
|
|
||||||
...instance
|
|
||||||
}) => instanceSchema.parse({
|
|
||||||
...instance,
|
|
||||||
configuration: {
|
|
||||||
...configuration,
|
|
||||||
polls: {
|
|
||||||
...configuration.polls,
|
|
||||||
max_characters_per_option: configuration.polls.max_characters_per_option ?? poll_limits.max_option_chars ?? 25,
|
|
||||||
max_expiration: configuration.polls.max_expiration ?? poll_limits.max_expiration ?? 2629746,
|
|
||||||
max_options: configuration.polls.max_options ?? poll_limits.max_options ?? 4,
|
|
||||||
min_expiration: configuration.polls.min_expiration ?? poll_limits.min_expiration ?? 300,
|
|
||||||
},
|
|
||||||
statuses: {
|
|
||||||
...configuration.statuses,
|
|
||||||
max_characters: configuration.statuses.max_characters ?? max_toot_chars ?? 500,
|
|
||||||
max_media_attachments: configuration.statuses.max_media_attachments ?? max_media_attachments ?? 4,
|
|
||||||
},
|
|
||||||
urls: {
|
|
||||||
streaming: urls.streaming_api,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
contact: {
|
|
||||||
account: contact_account,
|
|
||||||
email: email,
|
|
||||||
},
|
|
||||||
description: short_description || description,
|
|
||||||
pleroma: {
|
|
||||||
...pleroma,
|
|
||||||
metadata: {
|
|
||||||
...pleroma.metadata,
|
|
||||||
description_limit,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
registrations: {
|
|
||||||
approval_required: approval_required,
|
|
||||||
enabled: registrations,
|
|
||||||
},
|
|
||||||
thumbnail: { url: thumbnail },
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
type Instance = z.infer<typeof instanceSchema>;
|
type Instance = z.infer<typeof instanceSchema>;
|
||||||
|
|
||||||
export { instanceSchema, instanceV1ToV2, Instance };
|
export { instanceSchema, Instance };
|
||||||
|
|
Loading…
Reference in a new issue