move neopets.com loading into server

the client stuff worked locally, but not in prod because you can't request http from https, even with cors set up correctly 😅
This commit is contained in:
Matt Dunn-Rankin 2020-04-25 06:50:34 -07:00
parent b1a60e4e9d
commit 90ad9feb0c
8 changed files with 155 additions and 53 deletions

View file

@ -3,3 +3,9 @@
* Restore good download behavior: use crossOrigin for everything, and remove cache-buster in the URL we use for canvas * Restore good download behavior: use crossOrigin for everything, and remove cache-buster in the URL we use for canvas
* Undo the local linking we did for @chakra-ui/core, react, and react-dom on Matchu's machine 😅 * Undo the local linking we did for @chakra-ui/core, react, and react-dom on Matchu's machine 😅
* Present invalidated items somewhere on the screen, instead of them just vanishing? :/ * Present invalidated items somewhere on the screen, instead of them just vanishing? :/
Features:
* Outfit sharing
* Search by zone
* Outfit saving
* Use the space better on big screens?

View file

@ -20,6 +20,7 @@
"graphql": "^15.0.0", "graphql": "^15.0.0",
"immer": "^6.0.3", "immer": "^6.0.3",
"mysql2": "^2.1.0", "mysql2": "^2.1.0",
"node-fetch": "^2.6.0",
"react": "^16.13.1", "react": "^16.13.1",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-scripts": "3.4.1", "react-scripts": "3.4.1",

View file

@ -1,4 +1,6 @@
import React from "react"; import React from "react";
import gql from "graphql-tag";
import { useQuery } from "@apollo/react-hooks";
import { import {
Text, Text,
Modal, Modal,
@ -17,24 +19,45 @@ import {
function OutfitResetModal({ isOpen, onClose, dispatchToOutfit }) { function OutfitResetModal({ isOpen, onClose, dispatchToOutfit }) {
const [petName, setPetName] = React.useState(""); const [petName, setPetName] = React.useState("");
const [submittedPetName, submitPetName] = React.useState("");
const onComplete = ({ custom_pet, object_info_registry }) => { const { loading, error } = useQuery(
dispatchToOutfit({ gql`
type: "reset", query($petName: String!) {
name: custom_pet.name, petOnNeopetsDotCom(petName: $petName) {
speciesId: custom_pet.species_id, color {
colorId: custom_pet.color_id, id
wornItemIds: Object.values(object_info_registry).map( }
(o) => o.obj_info_id species {
), id
closetedItemIds: [], }
}); items {
onClose(); id
setPetName(""); }
}; }
const { loading, error, loadOutfitData } = useLoadOutfitData( }
petName, `,
onComplete {
variables: { petName: submittedPetName },
skip: !submittedPetName,
fetchPolicy: "network-only",
onCompleted: (data) => {
if (!data) return;
const { species, color, items } = data.petOnNeopetsDotCom;
dispatchToOutfit({
type: "reset",
name: petName,
speciesId: species.id,
colorId: color.id,
wornItemIds: items.map((i) => i.id),
closetedItemIds: [],
});
onClose();
setPetName("");
submitPetName("");
},
}
); );
const clearOutfit = () => { const clearOutfit = () => {
@ -46,6 +69,7 @@ function OutfitResetModal({ isOpen, onClose, dispatchToOutfit }) {
}); });
onClose(); onClose();
setPetName(""); setPetName("");
submitPetName("");
}; };
return ( return (
@ -55,7 +79,7 @@ function OutfitResetModal({ isOpen, onClose, dispatchToOutfit }) {
<form <form
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
loadOutfitData(); submitPetName(petName);
}} }}
> >
<ModalHeader> <ModalHeader>
@ -110,38 +134,4 @@ function OutfitResetModal({ isOpen, onClose, dispatchToOutfit }) {
); );
} }
function useLoadOutfitData(petName, onComplete) {
const [loading, setLoading] = React.useState(false);
const [error, setError] = React.useState(null);
const loadOutfitData = async () => {
setLoading(true);
setError(null);
let json;
try {
const res = await fetch(
`http://www.neopets.com/amfphp/json.php/CustomPetService.getViewerData` +
`/${petName}`
);
if (!res.ok) {
throw new Error(res.statusText);
}
json = await res.json();
if (!json.custom_pet) {
throw new Error(`missing custom_pet data`);
}
} catch (e) {
setLoading(false);
setError(e);
return;
}
setLoading(false);
onComplete(json);
};
return { loading, error, loadOutfitData };
}
export default OutfitResetModal; export default OutfitResetModal;

View file

@ -1,5 +1,44 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Pet looks up a pet 1`] = `
Object {
"petOnNeopetsDotCom": Object {
"color": Object {
"id": "75",
},
"items": Array [
Object {
"id": "37229",
},
Object {
"id": "37375",
},
Object {
"id": "38911",
},
Object {
"id": "38912",
},
Object {
"id": "38913",
},
Object {
"id": "43014",
},
Object {
"id": "43397",
},
Object {
"id": "48313",
},
],
"species": Object {
"id": "54",
},
},
}
`;
exports[`SpeciesColorPair gets them all 1`] = ` exports[`SpeciesColorPair gets them all 1`] = `
Object { Object {
"allValidSpeciesColorPairs": Array [ "allValidSpeciesColorPairs": Array [

View file

@ -2,6 +2,7 @@ const { gql } = require("apollo-server");
const connectToDb = require("./db"); const connectToDb = require("./db");
const buildLoaders = require("./loaders"); const buildLoaders = require("./loaders");
const neopets = require("./neopets");
const { capitalize } = require("./util"); const { capitalize } = require("./util");
const typeDefs = gql` const typeDefs = gql`
@ -55,6 +56,12 @@ const typeDefs = gql`
color: Color! color: Color!
} }
type Outfit {
species: Species!
color: Color!
items: [Item!]!
}
type Query { type Query {
allColors: [Color!]! allColors: [Color!]!
allSpecies: [Species!]! allSpecies: [Species!]!
@ -70,6 +77,8 @@ const typeDefs = gql`
limit: Int limit: Int
): ItemSearchResult! ): ItemSearchResult!
petAppearance(speciesId: ID!, colorId: ID!): Appearance petAppearance(speciesId: ID!, colorId: ID!): Appearance
petOnNeopetsDotCom(petName: String!): Outfit
} }
`; `;
@ -210,6 +219,17 @@ const resolvers = {
const swfAssets = await petSwfAssetLoader.load(petState.id); const swfAssets = await petSwfAssetLoader.load(petState.id);
return { layers: swfAssets, restrictedZones: [] }; return { layers: swfAssets, restrictedZones: [] };
}, },
petOnNeopetsDotCom: async (_, { petName }) => {
const petData = await neopets.loadPetData(petName);
const outfit = {
species: { id: petData.custom_pet.species_id },
color: { id: petData.custom_pet.color_id },
items: Object.values(petData.object_info_registry).map((o) => ({
id: o.obj_info_id,
})),
};
return outfit;
},
}, },
}; };

View file

@ -1680,6 +1680,32 @@ describe("SpeciesColorPair", () => {
}); });
}); });
describe("Pet", () => {
it("looks up a pet", async () => {
const res = await query({
query: gql`
query {
petOnNeopetsDotCom(petName: "roopal27") {
species {
id
}
color {
id
}
items {
id
}
}
}
`,
});
expect(res).toHaveNoErrors();
expect(res.data).toMatchSnapshot();
expect(queryFn.mock.calls).toMatchInlineSnapshot(`Array []`);
});
});
expect.extend({ expect.extend({
toHaveNoErrors(res) { toHaveNoErrors(res) {
if (res.errors) { if (res.errors) {

20
src/server/neopets.js Normal file
View file

@ -0,0 +1,20 @@
const fetch = require("node-fetch");
async function loadPetData(petName) {
const res = await fetch(
`http://www.neopets.com/amfphp/json.php/CustomPetService.getViewerData` +
`/${petName}`
);
if (!res.ok) {
throw new Error(`neopets.com returned: ${res.statusText}`);
}
const json = await res.json();
if (!json.custom_pet) {
throw new Error(`missing custom_pet data`);
}
return json;
}
module.exports = { loadPetData };

View file

@ -7905,7 +7905,7 @@ no-case@^3.0.3:
lower-case "^2.0.1" lower-case "^2.0.1"
tslib "^1.10.0" tslib "^1.10.0"
node-fetch@^2.1.2, node-fetch@^2.2.0: node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@^2.6.0:
version "2.6.0" version "2.6.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==