2022-01-10 14:01:24 -08:00
|
|
|
import {
|
|
|
|
Map as ImmutableMap,
|
|
|
|
List as ImmutableList,
|
|
|
|
Set as ImmutableSet,
|
2022-03-11 12:42:52 -08:00
|
|
|
Record as ImmutableRecord,
|
2022-01-10 14:01:24 -08:00
|
|
|
OrderedSet as ImmutableOrderedSet,
|
|
|
|
fromJS,
|
|
|
|
is,
|
|
|
|
} from 'immutable';
|
2022-01-10 14:25:06 -08:00
|
|
|
|
2020-12-29 13:55:04 -08:00
|
|
|
import {
|
2020-12-29 20:17:03 -08:00
|
|
|
ADMIN_CONFIG_FETCH_SUCCESS,
|
2021-07-28 15:06:21 -07:00
|
|
|
ADMIN_CONFIG_UPDATE_SUCCESS,
|
2020-12-29 13:55:04 -08:00
|
|
|
ADMIN_REPORTS_FETCH_SUCCESS,
|
2020-12-31 16:24:52 -08:00
|
|
|
ADMIN_REPORTS_PATCH_REQUEST,
|
|
|
|
ADMIN_REPORTS_PATCH_SUCCESS,
|
2020-12-29 13:55:04 -08:00
|
|
|
ADMIN_USERS_FETCH_SUCCESS,
|
2020-12-29 16:38:58 -08:00
|
|
|
ADMIN_USERS_DELETE_REQUEST,
|
2020-12-29 16:22:31 -08:00
|
|
|
ADMIN_USERS_DELETE_SUCCESS,
|
2020-12-29 16:38:58 -08:00
|
|
|
ADMIN_USERS_APPROVE_REQUEST,
|
2020-12-29 16:22:31 -08:00
|
|
|
ADMIN_USERS_APPROVE_SUCCESS,
|
2022-05-15 06:11:59 -07:00
|
|
|
} from 'soapbox/actions/admin';
|
|
|
|
import { normalizeAdminReport, normalizeAdminAccount } from 'soapbox/normalizers';
|
|
|
|
import { normalizeId } from 'soapbox/utils/normalizers';
|
2020-08-24 15:18:53 -07:00
|
|
|
|
2022-03-31 16:10:34 -07:00
|
|
|
import type { AnyAction } from 'redux';
|
2022-09-11 10:28:12 -07:00
|
|
|
import type { APIEntity } from 'soapbox/types/entities';
|
2022-11-15 12:46:23 -08:00
|
|
|
import type { Config } from 'soapbox/utils/config-db';
|
2022-03-31 16:10:34 -07:00
|
|
|
|
2022-03-11 12:42:52 -08:00
|
|
|
const ReducerRecord = ImmutableRecord({
|
2022-05-15 06:11:59 -07:00
|
|
|
reports: ImmutableMap<string, ReducerAdminReport>(),
|
2022-03-31 16:10:34 -07:00
|
|
|
openReports: ImmutableOrderedSet<string>(),
|
2022-05-15 06:11:59 -07:00
|
|
|
users: ImmutableMap<string, ReducerAdminAccount>(),
|
2022-03-31 16:10:34 -07:00
|
|
|
latestUsers: ImmutableOrderedSet<string>(),
|
|
|
|
awaitingApproval: ImmutableOrderedSet<string>(),
|
|
|
|
configs: ImmutableList<Config>(),
|
2020-12-29 20:17:03 -08:00
|
|
|
needsReboot: false,
|
2020-08-24 15:18:53 -07:00
|
|
|
});
|
|
|
|
|
2022-03-31 16:10:34 -07:00
|
|
|
type State = ReturnType<typeof ReducerRecord>;
|
|
|
|
|
2022-05-15 06:11:59 -07:00
|
|
|
type AdminAccountRecord = ReturnType<typeof normalizeAdminAccount>;
|
|
|
|
type AdminReportRecord = ReturnType<typeof normalizeAdminReport>;
|
|
|
|
|
|
|
|
export interface ReducerAdminAccount extends AdminAccountRecord {
|
|
|
|
account: string | null,
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface ReducerAdminReport extends AdminReportRecord {
|
|
|
|
account: string | null,
|
|
|
|
target_account: string | null,
|
|
|
|
action_taken_by_account: string | null,
|
|
|
|
assigned_account: string | null,
|
|
|
|
statuses: ImmutableList<string | null>,
|
|
|
|
}
|
|
|
|
|
2022-03-31 16:10:34 -07:00
|
|
|
// Umm... based?
|
|
|
|
// https://itnext.io/typescript-extract-unpack-a-type-from-a-generic-baca7af14e51
|
|
|
|
type InnerRecord<R> = R extends ImmutableRecord<infer TProps> ? TProps : never;
|
|
|
|
|
|
|
|
type InnerState = InnerRecord<State>;
|
|
|
|
|
|
|
|
// Lol https://javascript.plainenglish.io/typescript-essentials-conditionally-filter-types-488705bfbf56
|
|
|
|
type FilterConditionally<Source, Condition> = Pick<Source, {[K in keyof Source]: Source[K] extends Condition ? K : never}[keyof Source]>;
|
|
|
|
|
|
|
|
type SetKeys = keyof FilterConditionally<InnerState, ImmutableOrderedSet<string>>;
|
|
|
|
type APIReport = { id: string, state: string, statuses: any[] };
|
|
|
|
type APIUser = { id: string, email: string, nickname: string, registration_reason: string };
|
|
|
|
|
|
|
|
type Filter = 'local' | 'need_approval' | 'active';
|
2021-07-14 10:27:51 -07:00
|
|
|
|
2022-03-31 16:10:34 -07:00
|
|
|
const FILTER_UNAPPROVED: Filter[] = ['local', 'need_approval'];
|
|
|
|
const FILTER_LATEST: Filter[] = ['local', 'active'];
|
2021-07-14 10:27:51 -07:00
|
|
|
|
2022-03-31 16:10:34 -07:00
|
|
|
const filtersMatch = (f1: string[], f2: string[]) => is(ImmutableSet(f1), ImmutableSet(f2));
|
|
|
|
const toIds = (items: any[]) => items.map(item => item.id);
|
|
|
|
|
|
|
|
const mergeSet = (state: State, key: SetKeys, users: APIUser[]): State => {
|
2021-07-14 10:27:51 -07:00
|
|
|
const newIds = toIds(users);
|
2022-03-31 16:10:34 -07:00
|
|
|
return state.update(key, (ids: ImmutableOrderedSet<string>) => ids.union(newIds));
|
2021-07-14 10:27:51 -07:00
|
|
|
};
|
|
|
|
|
2022-03-31 16:10:34 -07:00
|
|
|
const replaceSet = (state: State, key: SetKeys, users: APIUser[]): State => {
|
2021-07-14 10:27:51 -07:00
|
|
|
const newIds = toIds(users);
|
|
|
|
return state.set(key, ImmutableOrderedSet(newIds));
|
|
|
|
};
|
|
|
|
|
2022-03-31 16:10:34 -07:00
|
|
|
const maybeImportUnapproved = (state: State, users: APIUser[], filters: Filter[]): State => {
|
2021-07-14 10:27:51 -07:00
|
|
|
if (filtersMatch(FILTER_UNAPPROVED, filters)) {
|
|
|
|
return mergeSet(state, 'awaitingApproval', users);
|
|
|
|
} else {
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-03-31 16:10:34 -07:00
|
|
|
const maybeImportLatest = (state: State, users: APIUser[], filters: Filter[], page: number): State => {
|
2021-07-14 10:27:51 -07:00
|
|
|
if (page === 1 && filtersMatch(FILTER_LATEST, filters)) {
|
|
|
|
return replaceSet(state, 'latestUsers', users);
|
|
|
|
} else {
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-05-15 06:11:59 -07:00
|
|
|
const minifyUser = (user: AdminAccountRecord): ReducerAdminAccount => {
|
|
|
|
return user.mergeWith((o, n) => n || o, {
|
|
|
|
account: normalizeId(user.getIn(['account', 'id'])),
|
|
|
|
}) as ReducerAdminAccount;
|
|
|
|
};
|
|
|
|
|
|
|
|
const fixUser = (user: APIEntity): ReducerAdminAccount => {
|
|
|
|
return normalizeAdminAccount(user).withMutations(user => {
|
|
|
|
minifyUser(user);
|
|
|
|
}) as ReducerAdminAccount;
|
|
|
|
};
|
2021-07-14 10:27:51 -07:00
|
|
|
|
2022-03-31 16:10:34 -07:00
|
|
|
function importUsers(state: State, users: APIUser[], filters: Filter[], page: number): State {
|
2020-12-29 13:55:04 -08:00
|
|
|
return state.withMutations(state => {
|
2021-07-14 10:27:51 -07:00
|
|
|
maybeImportUnapproved(state, users, filters);
|
|
|
|
maybeImportLatest(state, users, filters, page);
|
|
|
|
|
2020-12-29 13:55:04 -08:00
|
|
|
users.forEach(user => {
|
2022-05-15 06:11:59 -07:00
|
|
|
const normalizedUser = fixUser(user);
|
|
|
|
state.setIn(['users', user.id], normalizedUser);
|
2020-12-29 16:22:31 -08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-03-31 16:10:34 -07:00
|
|
|
function deleteUsers(state: State, accountIds: string[]): State {
|
2020-12-29 16:22:31 -08:00
|
|
|
return state.withMutations(state => {
|
2021-07-13 16:11:11 -07:00
|
|
|
accountIds.forEach(id => {
|
|
|
|
state.update('awaitingApproval', orderedSet => orderedSet.delete(id));
|
|
|
|
state.deleteIn(['users', id]);
|
2020-12-29 16:22:31 -08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-03-31 16:10:34 -07:00
|
|
|
function approveUsers(state: State, users: APIUser[]): State {
|
2020-12-29 16:22:31 -08:00
|
|
|
return state.withMutations(state => {
|
|
|
|
users.forEach(user => {
|
2022-05-15 06:11:59 -07:00
|
|
|
const normalizedUser = fixUser(user);
|
|
|
|
state.update('awaitingApproval', orderedSet => orderedSet.delete(user.id));
|
|
|
|
state.setIn(['users', user.id], normalizedUser);
|
2020-12-29 13:55:04 -08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-05-15 06:11:59 -07:00
|
|
|
const minifyReport = (report: AdminReportRecord): ReducerAdminReport => {
|
|
|
|
return report.mergeWith((o, n) => n || o, {
|
|
|
|
account: normalizeId(report.getIn(['account', 'id'])),
|
|
|
|
target_account: normalizeId(report.getIn(['target_account', 'id'])),
|
|
|
|
action_taken_by_account: normalizeId(report.getIn(['action_taken_by_account', 'id'])),
|
|
|
|
assigned_account: normalizeId(report.getIn(['assigned_account', 'id'])),
|
|
|
|
|
|
|
|
statuses: report.get('statuses').map((status: any) => normalizeId(status.get('id'))),
|
|
|
|
}) as ReducerAdminReport;
|
|
|
|
};
|
|
|
|
|
|
|
|
const fixReport = (report: APIEntity): ReducerAdminReport => {
|
|
|
|
return normalizeAdminReport(report).withMutations(report => {
|
|
|
|
minifyReport(report);
|
|
|
|
}) as ReducerAdminReport;
|
|
|
|
};
|
|
|
|
|
|
|
|
function importReports(state: State, reports: APIEntity[]): State {
|
2020-12-31 12:29:31 -08:00
|
|
|
return state.withMutations(state => {
|
|
|
|
reports.forEach(report => {
|
2022-05-15 06:11:59 -07:00
|
|
|
const normalizedReport = fixReport(report);
|
|
|
|
if (!normalizedReport.action_taken) {
|
2020-12-31 12:29:31 -08:00
|
|
|
state.update('openReports', orderedSet => orderedSet.add(report.id));
|
|
|
|
}
|
2022-05-15 06:11:59 -07:00
|
|
|
state.setIn(['reports', report.id], normalizedReport);
|
2020-12-31 12:29:31 -08:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-03-31 16:10:34 -07:00
|
|
|
function handleReportDiffs(state: State, reports: APIReport[]) {
|
2020-12-31 16:24:52 -08:00
|
|
|
// Note: the reports here aren't full report objects
|
|
|
|
// hence the need for a new function.
|
|
|
|
return state.withMutations(state => {
|
|
|
|
reports.forEach(report => {
|
2022-05-11 10:40:34 -07:00
|
|
|
switch (report.state) {
|
2022-05-11 14:06:35 -07:00
|
|
|
case 'open':
|
|
|
|
state.update('openReports', orderedSet => orderedSet.add(report.id));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
state.update('openReports', orderedSet => orderedSet.delete(report.id));
|
2020-12-31 16:24:52 -08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-03-31 16:10:34 -07:00
|
|
|
const normalizeConfig = (config: any): Config => ImmutableMap(fromJS(config));
|
|
|
|
|
|
|
|
const normalizeConfigs = (configs: any): ImmutableList<Config> => {
|
|
|
|
return ImmutableList(fromJS(configs)).map(normalizeConfig);
|
|
|
|
};
|
|
|
|
|
|
|
|
const importConfigs = (state: State, configs: any): State => {
|
|
|
|
return state.set('configs', normalizeConfigs(configs));
|
|
|
|
};
|
|
|
|
|
|
|
|
export default function admin(state: State = ReducerRecord(), action: AnyAction): State {
|
2022-05-11 10:40:34 -07:00
|
|
|
switch (action.type) {
|
2022-05-11 14:06:35 -07:00
|
|
|
case ADMIN_CONFIG_FETCH_SUCCESS:
|
|
|
|
case ADMIN_CONFIG_UPDATE_SUCCESS:
|
|
|
|
return importConfigs(state, action.configs);
|
|
|
|
case ADMIN_REPORTS_FETCH_SUCCESS:
|
|
|
|
return importReports(state, action.reports);
|
|
|
|
case ADMIN_REPORTS_PATCH_REQUEST:
|
|
|
|
case ADMIN_REPORTS_PATCH_SUCCESS:
|
|
|
|
return handleReportDiffs(state, action.reports);
|
|
|
|
case ADMIN_USERS_FETCH_SUCCESS:
|
|
|
|
return importUsers(state, action.users, action.filters, action.page);
|
|
|
|
case ADMIN_USERS_DELETE_REQUEST:
|
|
|
|
case ADMIN_USERS_DELETE_SUCCESS:
|
|
|
|
return deleteUsers(state, action.accountIds);
|
|
|
|
case ADMIN_USERS_APPROVE_REQUEST:
|
|
|
|
return state.update('awaitingApproval', set => set.subtract(action.accountIds));
|
|
|
|
case ADMIN_USERS_APPROVE_SUCCESS:
|
|
|
|
return approveUsers(state, action.users);
|
|
|
|
default:
|
|
|
|
return state;
|
2020-08-24 15:18:53 -07:00
|
|
|
}
|
2021-08-03 12:22:51 -07:00
|
|
|
}
|