Commit graph

341 commits

Author SHA1 Message Date
f2fd019aa8 Remove unused IMPRESS_LOG_IN_AS feature
The latest version of Yarn's shell interpreter wasn't playing nice with
this undefined variable; okay! It's unused, let's just delete all
trace of it!
2024-01-28 06:36:09 -08:00
106fcf075f Use the same dev database credentials as the main impress app
This should make it easier to test both apps working together!
2024-01-28 06:32:46 -08:00
4cd5cf5800 Fix handling of assets with https://images.neopets.com/temp SWF URLs
Weird! Well! This caused two issues.

One is that we used to filter down to only assets whose urls end in
`.swf`, because I never added support for the sound ones :p

The other is that we were using the SWF URL to infer the potential
manifest URLs, which we can't do when the SWF URL is a weird
placeholder!

Thankfully, just yesterday (wow!) we happened to have Classic DTI keep
track of `manifest_url` during the modeling process, and we backfilled
everything. So the most recent `temp` assets have their manifest! But
slightly older ones (not that old tho!!) didn't, so I manually inferred
the manifest URL from the asset's `remote_id` field instead (which
worked cuz there was no hash component to the manifest URL, unlike some
manifests).

The item "Flowing Black Cloak" (86668) on the Zafara is the one we were
looking at and testing with!
2023-11-11 13:28:47 -08:00
c3a9c24d22 Fix pet loading
Needed to add some headers to satisfy StackPath etc!

(Also, latest Prettier ran on this file for the first time, so a few
changes there too!)
2023-11-06 13:24:08 -08:00
3b6bc0b6eb Oops, add Vary: Origin to GraphQL requests
Lol I guess we've probably just been having intermittent CORS issues
forever, oops. I hope the cache has been mostly warmed on the right
thing! But today I checked and the entire species/color picker on the
Rails app wasn't working for CORS reasons, so like. Yeah oof.
2023-11-02 16:35:11 -07:00
a2594d1797 Fix crash on user lists page from orphaned hangers
If you delete a closet list without deleting the items in it, it'll dump them into your default own/want lists. This isn't necessarily the behavior we really want, but oh well, that's the truth right now!

The GraphQL endpoint was assuming that all hanger `list_id` values were valid, oops! Now, we ignore list IDs that don't match a list.
2023-10-24 14:48:55 -07:00
76dd8059bb Extract IMPRESS_MYSQL_HOST environment variable
Oh beans, I made an env variable change that I thought would switch us over to db.impress.openneo.net, and I was just plain wrong, darn. impress-2020 has been fully failing since then, oops!!!

Here, we change it to be an environment variable, so that in the future it will work how I want it to lol
2023-10-12 20:45:06 -07:00
a14bc9bebd Fix Vary header for CORS
Oops, we added behavior that varies the CORS response headers according to the incoming `Origin` header, but we forgot to add `Vary: Origin`!

This doesn't cause an issue for the app when you make requests to the server directly, but since it's behind a Fastly cache layer, we ended up caching responses that didn't include CORS headers but should have.

Now, this will instruct the Fastly cache to treat requests with different `Origin` headers as being entirely different. (This means we won't be sharing caches between requests from impress-2020 and the Rails app anymore, but that should be okay in practice!)
2023-10-12 14:58:26 -07:00
756ee8dcb2 Add workaround for pet lookups with leading digits
This was Dice's idea ty!! Now, instead of crashing when looking up any pet whose name starts with a number, we do a clever lil workaround instead! We don't get *all* the data back (we're missing metadata), but that's fine for the main use case of typing your pet name in at the homepage.
2023-08-29 15:05:19 -07:00
80428e9834 Fix pet loading for most pets
Sigh, I guess xmlrpc was deleted? Back to the method that doesn't work for pets with leading digits in their names, sobbe

Dice has a neat idea for how to work around that, but I'm not sure how to fit it in our architecture, let's take a look!
2023-08-29 14:40:59 -07:00
85d68f68e1 Enable Classic DTI to access impress-2020 via CORS
owo, uwu,
2023-08-20 15:40:21 -07:00
8b3c256a5c Time out manifest requests after 2sec
We do a thing where we sometimes proactively update an appearance layer's manifest from images.neopets.com when it's been a while since the last time, _during_ user requests.

But when images.neopets.com is being slow, this makes our API requests about appearances super slow, too!

In this change, we add a 2-second timeout to those requests. That should be plenty for when images.neopets.com is in a good mood, but also give up fast enough for the site to not feel miserable lol :p (especially when the use "Use DTI's image archive" option is on!)
2022-10-13 17:00:21 -07:00
a1844f76e0 Add /api/assetImageRedirect
Okay, this is gonna be a drop-in new backend for impress-asset-images.openneo.net, to enable Classic DTI to use the same images as DTI 2020!

This will enable us to stop generating images and uploading them to S3 just for Classic's sake, so we can turn those background processes off! And the new modeling script skips that anyway, so this is an important compatibility step for the new data that went out today!
2022-10-11 12:21:14 -07:00
29d9d498bf Use aws.impress-asset-images.openneo.net
We're gonna update impress-asset-images.openneo.net to perform redirects and stuff, so Classic DTI can start using the same images that DTI 2020 does.

That should enable us to stop relying on AWS for images, which is important because the new modeling script breaks that anyway :p but this will also let us turn off the image converters that run in the background all the time, and I'm excited for that too!
2022-10-11 12:19:39 -07:00
e8d7f6678d Auto-modeling script??
It seems to be working!! How exciting!! I'm just letting it run on stuff now :3

One important issue is that Classic DTI doesn't show images for items modeled this way, because we don't download the SWFs for it. But I wanna update it to stop using AWS anyway and do the same stuff 2020 does, I think we can do that pretty sneakily!
2022-10-11 11:13:10 -07:00
052cc242e4 Modeling page performance fix
Ok so, I kinda assumed that the query engine would only compute `all_species_ids_for_this_color` on the rows we actually returned, and it's a fast subquery so it's fine. But that was wrong! I think the query engine computing that for _every_ item, and _then_ filter out stuff with `HAVING`. Which makes sense, because the `HAVING` clause references it, so computing it makes sense!

In this change, we inline the subquery, so it only gets called if the other conditions in the `HAVING` clause don't fail first. That way, it only gets run when needed, and the query runs like 2x faster (~30sec instead of ~60sec), which gets us back inside some timeouts that were triggering around 1 minute and making the page fail.

However, this meant we no longer return `all_species_ids_for_this_color`, which we actually use to determine which species are _left_ to model for! So now, we have a loader that also basically runs the same query as that condition subquery.

A reasonable question would be, at this point, is the `HAVING` clause a good idea? would it be simpler to do the filtering in JS?

and I think it might be simpler, but I would guess noticeably worse performance, because I think we really do filter out a _lot_ of results with that `HAVING` clause—like basically all items, right? So to filter on the JS side, we'd be transferring data for all items over the wire, which… like, that's not even the worst dealbreaker, but it would certainly be noticed. This hypothesis could be wrong, but it's enough of a reason for me to not bother pursuring the refactor!
2022-10-10 20:15:16 -07:00
773ec8974f petOnNeopetsDotCom GQL improvements
We now support returning `null` from `petAppearance` when a pet genuinely has no customization data.

We also deprecate some old fields, and update our own call site to match.
2022-09-17 22:07:34 -07:00
c5bd2695f6 A bit of SSR for the item page
Always been a bit annoyed to have even the item name load in so weird and slow 😅 this fixes it to come in much faster!

This also allows us to SSR the item name in the page title, since we've put it in the GraphQL cache at SSR time!
2022-09-15 03:05:14 -07:00
2887d952de Fix /outfits/new init + add more SSR
Whew, setting up a cute GraphQL SSR system! I feel like it strikes a good balance of not having actually too many moving parts, though it's still a bit extensive for the problem we're solving 😅

Anyway, by doing SSR at _all_, we solve the problem where Next's "Automatic Static Optimization" was causing problems by setting the outfit state to the default at the start of the page load.

So I figured, why not try to SSR things _good_?

Now, when you navigate to the /outfits/new page, Next.js will go get the necessary GraphQL data to show the image before even putting the page into view. This makes the image show up all snappy-like! (when images.neopets.com is behaving :p)

We could do this with the stuff in the items panel too, but it's a tiny bit more annoying in the code right now, so I'm just gonna not worry about it and see how this performs in practice!

This change _doesn't_ include making the images actually show up before JS loads in, I assume because our JS code tries to validate that the images have loaded before fading them in on the page. Idk if we want to do something smarter there for the SSR case, to try to get them loading in faster!
2022-09-15 02:46:14 -07:00
50e04385e3 Account creation is ready!
Yay it seems to be working, owo! Added the actual database work, and some additional uniqueness checking.
2022-09-13 21:11:48 -07:00
80038ed9c4 Add openneo_id to the dev database
Specifically, we rename the dev database to `openneo_impress` for consistency with the main app, and create a second schema file for `openneo_id`, so we can do local account creation.
2022-09-13 21:06:35 -07:00
08603af961 Create account endpoint skeleton + validation
It doesn't actually create the account, but it does some field validation and the form reacts to it!
2022-09-12 15:25:22 -07:00
c7ba61a0f1 Track sign-ins & IP addresses
Oh right, these are some logging-ish things that Classic DTI would perform! It's easy enough for us to keep the fields up-to-date too, so let's do it!
2022-09-12 15:24:58 -07:00
4c9dbf91fb Use latest ~owls NC trade values API
They're moving away from the bulk endpoint to individual item data lookups, so we're updating to match!
2022-09-04 01:35:05 -07:00
9982cbf93d Refactor login/logout mutations a smidge
I decided that `setAuthToken` was the more appropriate server-level API, and that letting the login/logout mutations engage with the auth library made more sense to me!
2022-08-26 16:05:11 -07:00
5bf037b193 Oops, fix bug on boot caused by login changes
I forgot that we sometimes use the Apollo server in a context where `req` and `res` aren't present! (namely in our `build-cached-data` script.)

In this change, we update the DTI-Auth-Mode HTTP header check to be cognizant that the request might be absent!
2022-08-17 16:15:09 -07:00
db8fe9f3c2 Logout button for new auth mode
Hey hey, logging out works! The server side of this was easy, but I made a few refactors to support it well on the client, like `useLoginActions` becoming just `useLogout` lol, and updating how the nav menu chooses between buttons vs menu because I wanted `<LogoutButton />` to contain some state.

We also did good Apollo cache stuff to update the page after you log in or out! I think some fields that don't derive from `User`, like `Item.currentUserOwnsThis`, are gonna fail to update until you reload the page but like that's fine idk :p

There's a known bug where logging out on the Your Outfits page turns into an infinite loop situation, because it's trying to do Auth0 stuff but the login keeps failing to have any effect because we're in db mode! I'll fix that next.
2022-08-17 16:05:36 -07:00
2dbfaf1557 Support actual login via db?? :0
Yeah cool the login button seems to. work now? And subsequent requests serve user data correctly based on that, and let you edit stuff.

I also tested the following attacks:
- Using the wrong password indeed fails! lol basic one
- Changing the userId or createdAt fields in the cookie causes the auth token to be rejected for an invalid signature.

Tbh that's all that comes to mind… like, you either attack us by tricking the login itself into giving you a token when it shouldn't, or you attack us by tricking the subsequent requests into accepting a token when it shouldn't. Seems like we're covered? 😳🤞

Still need to add logout, but yeah, this is… looking surprisingly feature-parity with our Auth0 integration already lmao. Maybe it'll be ready to launch sooner than expected?
2022-08-17 15:24:17 -07:00
28060d4d16 Whoops, actually include createdAt in auth tokens
Right, I had that idea while writing the comment, then forgot to actually do it lmao

This is important for session expiration: we don't want you to be able to hold onto an old cookie for an account that you should be locked out of. Updating the `createdAt` value requires a new signature, so the client can't forge when this token was created, so we can be confident in our ability to expire them.
2022-08-17 01:07:47 -07:00
c478e6d88c Tweak the phrasing of an auth-by-db comment
Oh, I said two different things were "finally", whoops lol
2022-08-17 01:00:54 -07:00
4d0c48ab7c Login form checks the db, and saves a cookie
Okay so one of the trickiest parts of login is done! 🤞 and now we need to make it actually show up in the UI. (and also pressure-test the security a bit, I've only really checked the happy path!)
2022-08-17 00:58:52 -07:00
38957c50c0 Merge branch 'owls-integration' into main 2022-08-16 00:16:09 -07:00
240a683e71 Finish wiring up ~owls data
Hey nice it looks like it's working! :3 "Bright Speckled Parasol" is a nice test case, it has a long text string! And when the NC value is not included in the ~owls list, we indeed don't show the badge!
2022-08-15 23:57:33 -07:00
25092b865a Add Delete button for outfits
Hey finally! I got in the mood and did it, after… a year? idk lol

The button should only appear for outfits that are already saved, that are owned by you. And the server enforces it!

I also added a new util function to give actually useful error messages when the GraphQL server throws an error. Might be wise to use this in more places where we're currently just using `error.message`!
2022-08-15 20:23:17 -07:00
5dfd67a221 Oops, fix a security error in outfit saving
Uhhh I guess I never added the check that the outfit you're editing is your own? Embarrassing.

I don't have any reason to believe anyone abused this, but 😬! Good to have fixed now!
2022-08-15 19:51:31 -07:00
b9ba650992 Use HTTPS for AMFPHP requests
This was a bit trickier to figure out how to upgrade, it's not in the `xmlrpc` package's README, but I found the answer here: https://github.com/baalexander/node-xmlrpc/issues/142
2022-08-03 14:40:00 -07:00
bd9017796e Use HTTPS for images.neopets.com and pets.neopets.com
Tbh I didn't even really validate these changes, or that the codepaths right now aren't working, they just seem like clear drop-in upgrades now that HTTPS works and HTTP requests are redirected. Simplify!
2022-08-03 14:36:07 -07:00
6df1f49208 Add a limit to the modeling query
Right now it returns 50 rows; each item that needs modeling returns 1–4 rows, usually 1. So a limit of 200 should be pretty dangerous, while also creating a release valve if there's another future bug: it'll just have the problem of returning too few items, instead of the problem of crashing everything! 😅
2022-06-23 11:57:32 -07:00
c585b1236f Oops, fix crashes in the Modeling Hub!
Neopets released a new Maraquan Koi, and it revealed a mistake in our modeling query! We already knew that the Maraquan Mynci was actually the same body type as the standard Mynci colors, but now the Koi is the same way, and because there's _two_ such species, the query started reacting by assuming that a _bunch_ of items that fit both the standard Mynci and standard Koi (which is a LOT of items!!) should also fit all _Maraquan_ pets, because it fits both the Maraquan Mynci and Maraquan Koi too. (Whereas previously, that part of the query would say "oh, it just fits the Maraquan Mynci, we don't need to assume it fits ALL maraquan pets, that's probably just species-specific.")

so yeah! This change should help the query ignore Maraquan species that have the same body type as standard species. That's fine to essentially treat them like they don't exist, because we won't lose out on any modeling that way: the standard models will cover the Maraquan versions for those two species!
2022-06-20 15:21:38 -07:00
3744a476e5 Fix db transactions with pooling
Ah okay, pools support `query` and `execute` the same way connection objects do (as a shorthand for acquiring, querying, and releasing), but it doesn't have the same helpers for transactions. Makes sense: you need those queries to go to the same connection, and an API where you just call it against the pool object can't tell that it's part of the same thing!

Now, we have our transaction code explicitly acquire a connection to use for the duration of the transaction.

An alternative considered would have been to have `connectToDb` acquire a connection, and then release it at the end of the GraphQL request. That would have made app code simpler, but added a lot of additional potential surprise failure points to the infra imo (e.g. what if we're misunderstanding the GraphQL codepath and the connection never gets released? whereas here it's relatively easy to audit that there's a `finally` in the right spot.)
2022-01-08 18:49:00 -08:00
4ed6344b3d Use a connection pool
This should both fix cases where the connection closes for various reasons, by having the pool reconnect; and also should be a second way of solving some of the blocking issues we were having with large queries, by letting faster queries use parallel connections.

Idk what a reasonable number is, 10 seems to be what various guides are saying? Might tune it down if it ends up pushing various connection limits? (We could also constrain it on dev specifically, if that matters.)
2022-01-08 09:20:45 -08:00
e051290df4 Better-scoped queries for currentUserOwnsThis etc
I hypothesize that loading people's full trade lists more often than necessary is part of the cause of the recent mega slowdown!

My hypothesis is that we're clogging up the MySQL connection socket with a ton of data, which blocks all other queries until the big ones come through and parse out. (I haven't actually validated my assumption that MySQL connections send query results in serial like that, but it makes sense to me, and fits what I've been seeing.)

There's more places we could potentially optimize, like the trade list page itself… (we currently aggressively load everything when we could limit it and load the rest on the followup pages, or even paginate the followup pages…)

…but my hope is that this helps enough, by relieving the load on the homepage (latest items) and on item searches!
2022-01-07 11:37:27 -08:00
e95f6abbe4 Closet list dropdowns on item page
Oooh this feature is feeling very nice :) :) We hid "not in a list" pretty smoothly I think!

A known bug: If you have the item in a list, then click the big colorful button, it will remove the item from *all* lists; and then if you click it again, it will add it to Not in a List. But! The UI will still show the lists it was in before, because we haven't updated the client cache. (It's not that bad in the middle state though, because the list dropdown stuff gets hidden.)
2021-11-30 16:36:00 -08:00
c4e32d92f1 Fix Typescript error with caching + new Apollo
Okay, right, I guess the Apollo Server upgrade broke type checking in our weird cache control hacks! This should force them to be compatible!
2021-11-26 15:22:01 -08:00
ad249d03ab WIP list editing on item page
Gonna have this button pop up a little thing of your lists, with checkboxes, and I guess probably quantities once I add that concept back to 2020 😅
2021-11-26 14:49:21 -08:00
2e41f7bb0b Send Vary: Authorization cache header
I don't think this is actually relevant in-app right now, but I figured sending it is More Correct, and is likely to prevent future bugs if anything (and prevent future question about why we're _not_ sending it).

I also removed the `maxAge: 0` on `currentUser`, now that I've updated Fastly to no longer default to 5-minute caching when no cache time is specified. I can see why that's a reasonable default for Fastly, but we've been pretty careful about specifying Cache-Control headers when relevant, so the extra caching is mostly incorrect.
2021-11-23 13:00:56 -08:00
b941dce9fa Private cache headers in item search
If the user is searching for things they own or want, make sure we don't CDN cache it!

For many queries, this is taken care of in practice, because the search result includes `currentUserOwnsThis` and `currentUserWantsThis`. But I noticed in testing that, if the search result has no items, so those fields aren't actually part of the _response_, then the private header doesn't get set. So this mainly makes sure we don't accidentally cache an empty result from a user who didn't have anything they owned/wanted yet!
2021-11-16 13:09:45 -08:00
b73e2e1123 Send cache-control header for max-age=0, private
Some queries, like on `/your-outfits`, had the cache hint `max-age=0, private` set. In this case, our cache code sent no cache header, on the assumption that no header would result in no caching.

This was true on Vercel, but isn't true on our new Fastly setup! (Which makes sense, Vercel was a bit more aggressive here I think.)

This was causing an arbitrary user's data to be cached by Fastly as the result for `/your-outfits`. (We found this bug before launching the Fastly cache though, don't worry! No actual user data leaked!)

Now, as of this change, the `/your-outfits` query correctly sends a header of `Cache-Control: max-age=0, private`. This directs Fastly not to cache the result.

To fix this, we made a change to our HTTP header code, which is forked from Apollo's stuff.
2021-11-16 12:34:11 -08:00
cadf7487af Mark currentUser GQL as non-cacheable
Comments explain most of this! Vercel changed around the Cache-Control headers a bit to always essentially apply max-age:0 when scope:PRIVATE was true.

I'm noticing this isn't *fully* working yet though, because we're not getting a `Cache-Control: private` header, we're just getting no header at all. Fastly might aggressively choose to cache it anyway with etag stuff! I bet that's the fault of our caching middleware plugin thing, so I'll check on that!
2021-11-16 12:12:51 -08:00
002af474f8 Don't HTTP cache currentUserOwnsThis/wants
Hmm, I see, Vercel chews on Cache-Control headers a bit more than I'm used to, so anything marked `scope: PRIVATE` would not be cached at all.

But on a more standard server, this was coming out as privately cacheable, but for an actual amount of time (1 hour in the homepage case), because of the `maxAge` on other fields. That meant the device browser cache would hold onto the result, and not always reflect Own/Want changes upon page reload.

In this change, we set `maxAge: 0`, because we want this field to be very responsive. I also left `scope: PRIVATE`, even though I think it doesn't really matter if we're saying the field isn't cacheable anyway, because I want to set the precendent that `currentUser` fields need it, to avoid a potential gotcha if someone creates a cacheable `currentUser` field in the future. (That's important to be careful with though, because is it even okay for logouts to not clear it? TODO: Can we clear the private HTTP cache somehow? I guess we would need to include the current user ID in the URL?)
2021-11-16 12:04:16 -08:00