Use wikitree-js library for WikiTree API

This commit is contained in:
Przemek Więch 2022-08-17 11:18:17 +02:00
parent 7c6b1dd390
commit a92f06e43d
4 changed files with 1033 additions and 473 deletions

1318
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -33,7 +33,8 @@
"semantic-ui-css": "^2.4.1",
"semantic-ui-react": "^2.0.3",
"topola": "^3.5.0",
"unified": "^10.1.0"
"unified": "^10.1.0",
"wikitree-js": "^0.1.0"
},
"devDependencies": {
"@types/array.prototype.flatmap": "^1.2.2",
@ -71,7 +72,7 @@
"build:default": "REACT_APP_CHANGELOG=`cat CHANGELOG.md` REACT_APP_GIT_SHA=`git rev-parse --short HEAD` REACT_APP_GIT_TIME=`git log -1 --format=%ci` react-scripts build",
"build:windows": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"prettier": "prettier --write src/**/*.{ts,tsx,json} && prettier --write src/*.{ts,tsx,json}",
"prettier": "prettier --write src/**/*.{ts,tsx,json} --end-of-line lf && prettier --write src/*.{ts,tsx,json} --end-of-line lf",
"predeploy": "npm run build",
"deploy": "gh-pages -d build",
"predeploy-wikitree": "npm run build",

View File

@ -1,4 +1,3 @@
import Cookies from 'js-cookie';
import {analyticsEvent} from '../util/analytics';
import {DataSource, DataSourceEnum, SourceSelection} from './data_source';
import {
@ -15,79 +14,17 @@ import {GedcomEntry} from 'parse-gedcom';
import {IntlShape} from 'react-intl';
import {TopolaError} from '../util/error';
import {isValidDateOrRange} from '../util/date_util';
import {
getAncestors as getAncestorsApi,
getRelatives as getRelativesApi,
clientLogin,
getLoggedInUserName,
Person
} from 'wikitree-js';
/** Prefix for IDs of private individuals. */
export const PRIVATE_ID_PREFIX = '~Private';
/**
* Cookie where the logged in user name is stored. This cookie is shared
* between apps hosted on apps.wikitree.com.
*/
const USER_NAME_COOKIE = 'wikidb_wtb_UserName';
/** WikiTree API getAncestors request. */
interface GetAncestorsRequest {
action: 'getAncestors';
key: string;
fields: string;
}
/** WikiTree API getRelatives request. */
interface GetRelativesRequest {
action: 'getRelatives';
keys: string;
getChildren?: true;
getSpouses?: true;
}
/** WikiTree API clientLogin request. */
interface ClientLoginRequest {
action: 'clientLogin';
authcode: string;
}
/** WikiTree API clientLogin response. */
interface ClientLoginResponse {
result: string;
username: string;
}
type WikiTreeRequest =
| GetAncestorsRequest
| GetRelativesRequest
| ClientLoginRequest;
/** Person structure returned from WikiTree API. */
interface Person {
Id: number;
Name: string;
FirstName: string;
LastNameAtBirth: string;
RealName: string;
Spouses?: {[key: number]: Person};
Children: {[key: number]: Person};
Mother: number;
Father: number;
Gender: string;
BirthDate: string;
DeathDate: string;
BirthLocation: string;
DeathLocation: string;
BirthDateDecade: string;
DeathDateDecade: string;
marriage_location: string;
marriage_date: string;
DataStatus?: {
BirthDate: string;
DeathDate: string;
};
Photo: string;
PhotoData?: {
path: string;
url: string;
};
}
/** Gets item from session storage. Logs exception if one is thrown. */
function getSessionStorageItem(key: string): string | null {
try {
@ -107,23 +44,13 @@ function setSessionStorageItem(key: string, value: string) {
}
}
/** Sends a request to the WikiTree API. Returns the parsed response JSON. */
async function wikiTreeGet(request: WikiTreeRequest, handleCors: boolean) {
const requestData = new FormData();
requestData.append('format', 'json');
for (const key in request) {
requestData.append(key, request[key]);
}
const apiUrl = handleCors
? 'https://topola-cors.herokuapp.com/https://api.wikitree.com/api.php'
: 'https://api.wikitree.com/api.php';
const response = await window.fetch(apiUrl, {
method: 'POST',
body: requestData,
credentials: handleCors ? undefined : 'include',
});
const responseBody = await response.text();
return JSON.parse(responseBody);
function getApiOptions(handleCors: boolean) {
return handleCors
? {
apiUrl:
'https://topola-cors.herokuapp.com/https://api.wikitree.com/api.php',
}
: {};
}
/**
@ -139,15 +66,7 @@ async function getAncestors(
if (cachedData) {
return JSON.parse(cachedData);
}
const response = await wikiTreeGet(
{
action: 'getAncestors',
key: key,
fields: '*',
},
handleCors,
);
const result = response[0].ancestors as Person[];
const result = getAncestorsApi(key, {}, getApiOptions(handleCors));
setSessionStorageItem(cacheKey, JSON.stringify(result));
return result;
}
@ -173,16 +92,12 @@ async function getRelatives(
if (keysToFetch.length === 0) {
return result;
}
const response = await wikiTreeGet(
{
action: 'getRelatives',
keys: keysToFetch.join(','),
getChildren: true,
getSpouses: true,
},
handleCors,
const response = await getRelativesApi(
keysToFetch,
{getChildren: true, getSpouses: true},
getApiOptions(handleCors),
);
if (response[0].items === null) {
if (response === []) {
const id = keysToFetch[0];
throw new TopolaError(
'WIKITREE_PROFILE_NOT_FOUND',
@ -190,41 +105,13 @@ async function getRelatives(
{id},
);
}
const fetchedResults = response[0].items.map(
(x: {person: Person}) => x.person,
) as Person[];
fetchedResults.forEach((person) => {
response.forEach((person) => {
setSessionStorageItem(
`wikitree:relatives:${person.Name}`,
JSON.stringify(person),
);
});
return result.concat(fetchedResults);
}
export async function clientLogin(
authcode: string,
): Promise<ClientLoginResponse> {
const response = await wikiTreeGet(
{
action: 'clientLogin',
authcode,
},
false,
);
return response.clientLogin;
}
/**
* Returns the logged in user name or undefined if not logged in.
*
* This is not an authoritative answer. The result of this function relies on
* the cookies set on the apps.wikitree.com domain under which this application
* is hosted. The authoritative source of login information is in cookies set on
* the api.wikitree.com domain.
*/
export function getLoggedInUserName(): string | undefined {
return Cookies.get(USER_NAME_COOKIE);
return result.concat(response);
}
/**
@ -243,7 +130,6 @@ export async function loadWikiTree(
const loginResult = await clientLogin(authcode);
if (loginResult.result === 'Success') {
sessionStorage.clear();
Cookies.set(USER_NAME_COOKIE, loginResult.username);
}
}
@ -334,7 +220,7 @@ export async function loadWikiTree(
everyone.push(...allSpouses);
// Fetch all children.
toFetch = people.flatMap((person) =>
Object.values(person.Children).map((c) => c.Name),
Object.values(person.Children || {}).map((c) => c.Name),
);
generation++;
}
@ -474,7 +360,7 @@ function convertPerson(person: Person, intl: IntlShape): JsonIndi {
) {
const parsedDate = parseDate(
person.BirthDate,
person.DataStatus && person.DataStatus.BirthDate,
(person.DataStatus && person.DataStatus.BirthDate) || undefined,
);
const date = parsedDate || parseDecade(person.BirthDateDecade);
indi.birth = Object.assign({}, date, {place: person.BirthLocation});
@ -486,7 +372,7 @@ function convertPerson(person: Person, intl: IntlShape): JsonIndi {
) {
const parsedDate = parseDate(
person.DeathDate,
person.DataStatus && person.DataStatus.DeathDate,
(person.DataStatus && person.DataStatus.DeathDate) || undefined,
);
const date = parsedDate || parseDecade(person.DeathDateDecade);
indi.death = Object.assign({}, date, {place: person.DeathLocation});

View File

@ -3,10 +3,13 @@ import wikitreeLogo from './wikitree.png';
import {analyticsEvent} from '../util/analytics';
import {Button, Form, Header, Input, Modal} from 'semantic-ui-react';
import {FormattedMessage, useIntl} from 'react-intl';
import {getLoggedInUserName} from '../datasource/wikitree';
import {MenuItem, MenuType} from './menu_item';
import {useEffect, useRef, useState} from 'react';
import {useHistory, useLocation} from 'react-router';
import {
getLoggedInUserName,
navigateToLoginPage,
} from 'wikitree-js';
interface Props {
menuType: MenuType;
@ -150,8 +153,6 @@ export function WikiTreeMenu(props: Props) {
/** Displays and handles the "Log in to WikiTree" menu. */
export function WikiTreeLoginMenu(props: Props) {
const formRef = useRef<HTMLFormElement>(null);
const returnUrlRef = useRef<HTMLInputElement>(null);
const intl = useIntl();
/**
@ -163,8 +164,7 @@ export function WikiTreeLoginMenu(props: Props) {
'https://apps.wikitree.com/apps/wiech13/topola-viewer';
// TODO: remove authcode if it is in the current URL.
const returnUrl = `${wikiTreeTopolaUrl}${window.location.hash}`;
returnUrlRef.current!.value = returnUrl;
formRef.current!.submit();
navigateToLoginPage(returnUrl);
}
const username = getLoggedInUserName();
@ -178,15 +178,6 @@ export function WikiTreeLoginMenu(props: Props) {
defaultMessage="Log in to WikiTree"
/>
</MenuItem>
<form
action="https://api.wikitree.com/api.php"
method="POST"
style={{display: 'hidden'}}
ref={formRef}
>
<input type="hidden" name="action" value="clientLogin" />
<input type="hidden" name="returnURL" ref={returnUrlRef} />
</form>
</>
);
}