Skip to content

Conversation

@AmarTrebinjac
Copy link
Contributor

@AmarTrebinjac AmarTrebinjac commented Oct 12, 2025

Jira ticket

Made changes to updateUserProfile to conform with the new user profile. This includes the addition of readme, uploading cover photo, as well as logic for deleting removed photos in the same endpoint, rather than being a separate action, as we want the user to hit "save" to actually change their profile, rather than have some actions be auto, others not.

Also added the new locationId column to user, and location to boot.

MI-1067

@AmarTrebinjac AmarTrebinjac changed the base branch from main to MI-1027 October 12, 2025 23:41
@pulumi
Copy link

pulumi bot commented Oct 12, 2025

🍹 The Update (preview) for dailydotdev/api/prod (at 508e87c) was successful.

Resource Changes

    Name                                                   Type                           Operation
+   vpc-native-api-db-migration-51f53d00                   kubernetes:batch/v1:Job        create
~   vpc-native-personalized-digest-deployment              kubernetes:apps/v1:Deployment  update
~   vpc-native-private-deployment                          kubernetes:apps/v1:Deployment  update
~   vpc-native-deployment                                  kubernetes:apps/v1:Deployment  update
~   vpc-native-bg-deployment                               kubernetes:apps/v1:Deployment  update
~   vpc-native-update-tag-recommendations-cron             kubernetes:batch/v1:CronJob    update
-   vpc-native-api-clickhouse-migration-9d29c5e3           kubernetes:batch/v1:Job        delete
+-  vpc-native-k8s-secret                                  kubernetes:core/v1:Secret      create-replacement
~   vpc-native-ws-deployment                               kubernetes:apps/v1:Deployment  update
~   vpc-native-clean-gifted-plus-cron                      kubernetes:batch/v1:CronJob    update
~   vpc-native-hourly-notification-cron                    kubernetes:batch/v1:CronJob    update
~   vpc-native-update-trending-cron                        kubernetes:batch/v1:CronJob    update
~   vpc-native-clean-zombie-images-cron                    kubernetes:batch/v1:CronJob    update
~   vpc-native-calculate-top-readers-cron                  kubernetes:batch/v1:CronJob    update
~   vpc-native-update-current-streak-cron                  kubernetes:batch/v1:CronJob    update
~   vpc-native-personalized-digest-cron                    kubernetes:batch/v1:CronJob    update
~   vpc-native-generate-search-invites-cron                kubernetes:batch/v1:CronJob    update
~   vpc-native-validate-active-users-cron                  kubernetes:batch/v1:CronJob    update
~   vpc-native-update-source-public-threshold-cron         kubernetes:batch/v1:CronJob    update
~   vpc-native-clean-stale-user-transactions-cron          kubernetes:batch/v1:CronJob    update
~   vpc-native-update-views-cron                           kubernetes:batch/v1:CronJob    update
~   vpc-native-clean-zombie-users-cron                     kubernetes:batch/v1:CronJob    update
-   vpc-native-api-db-migration-9d29c5e3                   kubernetes:batch/v1:Job        delete
~   vpc-native-sync-subscription-with-cio-cron             kubernetes:batch/v1:CronJob    update
~   vpc-native-update-source-tag-view-cron                 kubernetes:batch/v1:CronJob    update
~   vpc-native-post-analytics-history-day-clickhouse-cron  kubernetes:batch/v1:CronJob    update
~   vpc-native-update-tags-str-cron                        kubernetes:batch/v1:CronJob    update
~   vpc-native-check-analytics-report-cron                 kubernetes:batch/v1:CronJob    update
~   vpc-native-daily-digest-cron                           kubernetes:batch/v1:CronJob    update
~   vpc-native-generic-referral-reminder-cron              kubernetes:batch/v1:CronJob    update
+   vpc-native-api-clickhouse-migration-51f53d00           kubernetes:batch/v1:Job        create
~   vpc-native-clean-zombie-user-companies-cron            kubernetes:batch/v1:CronJob    update
~   vpc-native-post-analytics-clickhouse-cron              kubernetes:batch/v1:CronJob    update
~   vpc-native-temporal-deployment                         kubernetes:apps/v1:Deployment  update
~   vpc-native-update-highlighted-views-cron               kubernetes:batch/v1:CronJob    update


const cloudinaryUrl = process.env.CLOUDINARY_URL || null;

const [avatar, cover] = await Promise.all([
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've not actually tested that this work, since uploading images locally doesn't work. Not sure what the best way to test this is E2E wise ? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generic question, why are we adding cover and readme to this mutation when we already have separate workable mutations for cover and readme?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if only to call in a single call, we can merge mutations on client side and use existing ones

mutation {
updateUserProfile() {}
uploadCoverImage() {}
updateReadme() {}
}

Copy link
Contributor Author

@AmarTrebinjac AmarTrebinjac Oct 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generic question, why are we adding cover and readme to this mutation when we already have separate workable mutations for cover and readme?

@capJavert

I mention it in the description, but in general the new form is being re-designed with react-hook-form, and I'd like to take the opportunity to build good form practices. It's not great that some parts of the form are "autosave" while others require you to click the save button, all in the same form.

As for your second comment: First of all I didn't know you could combo mutations like that ? 😅 Do we have any samples of this? Wonder what responses and errors look like

But secondly, and more importantly, I think its better and cleaner to just do it all within the same endpoint, since its all handling the same form, rather than multiple.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the fact that we already have it I would rather just use what we have, I don't want duplicates when we can easily do it through graphql

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Errors wise you would get errors the same way as you would with single one, errors in gql response are array, so you would get multiple

Copy link
Member

@sshanzel sshanzel Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AmarTrebinjac one example of Ante's suggestion is in graphql/users.ts - the query USER_READING_HISTORY_QUERY but this time, just make the query a mutation then it should work.

The response is similar to any query/mutation we have; the object will contain a property for each caller, in the image below, the response would be something like:

interface Response {
  userReadingRankHistory: object;
  userReadHistory: object;
  // and so on..
}
Screenshot 2025-10-15 at 4 11 21 PM

You can even put conditionals if image exists, then you execute upload mutation.

Copy link
Contributor

@capJavert capJavert Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was this change, to unify cover and readme mentioned anywhere in the RFC?

Copy link
Contributor Author

@AmarTrebinjac AmarTrebinjac Oct 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about when the user is just trying to update name? Would this clear the cover? If not, how would you clear the user's cover if the user is trying to clear it?

@sshanzel No, it would not clear it if the user is specifically not clearing it.

The logic for clearing is if there is a coverUpload, or if there's an existing image, but its not in the form, we clear

      if ((!data.cover || !!coverUpload) && user.cover) {
        filesToClear.push(
          clearFile({
            referenceId: user.id,
            preset: UploadPreset.ProfileCover,
          }),
        );
      }

@capJavert No, it was not in the RFC.

As for the broader question, I concede that, had I known about this graphql pairing, doing it that way might have worked as well. That's on me.
But now that I've updated it this way, I don't really see the benefit of undoing it and going back? This endpoint already handled uploading avatars, I just added the logic for cover + clearing.

Also, wouldn't this multi-request potentially still cause partial updates? The image requests succeed, but the validation of the form fields doesn't, so now the image is updated, but rest of the user is not? Sounds like we'd just be adding more logic to the frontend.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you even have to clear it because we replace the file itself. The filename ends up tied to a certain pattern.

@AmarTrebinjac AmarTrebinjac marked this pull request as ready for review October 13, 2025 19:15
@AmarTrebinjac AmarTrebinjac requested review from a team and capJavert as code owners October 13, 2025 19:15
@AmarTrebinjac AmarTrebinjac requested review from ilasw, nensidosari, omBratteng, rebelchris and sshanzel and removed request for a team October 13, 2025 19:15
src/types.ts Outdated
garmr: GarmrService;
};

export type Location = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Location is a reserved word due to window.Location - it might produce some confusion down the line 😞

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I think you can just use the DatasetLocation and pick the props you need.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, let's name it something like TLocation

Copy link
Contributor

@capJavert capJavert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, question about why we are adding handling when we have existing mutations.


const cloudinaryUrl = process.env.CLOUDINARY_URL || null;

const [avatar, cover] = await Promise.all([
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generic question, why are we adding cover and readme to this mutation when we already have separate workable mutations for cover and readme?

src/types.ts Outdated
garmr: GarmrService;
};

export type Location = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, let's name it something like TLocation

Comment on lines +309 to +314
export type TLocation = {
id: string;
country: string;
subdivision: string | null;
city: string | null;
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this used? Doesn't seem to be applied anywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In LoggedInBoot

Copy link
Member

@sshanzel sshanzel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't have a strong opinion whether we upgrade this endpoint or use a grouped mutation. Other than that, looks good to me! 🚀

@sshanzel
Copy link
Member

Although I would've liked it if we had converted this with Zod. I'm kinda obsessed with it lately.

Comment on lines +2254 to +2261
(async () => {
if (upload && cloudinaryUrl) {
const file = await upload;
return (await uploadAvatar(user.id, file.createReadStream())).url;
}
return data.image || null;
})(),
(async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are these inline async calls, lets change them up, if we are merging mutations at the end you could just export the current mutation logic as helper and then user it here so its streamlined

Base automatically changed from MI-1027 to main October 16, 2025 09:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants