Skip to content

Commit

Permalink
Display version in footer (#905)
Browse files Browse the repository at this point in the history
* ci: add separate entrypoint script

* feat(backend): do not get config dump path from config

* feat(backend): do not return error for core details

* feat(backend): dump core details when arg supplied

* feat(backend): always dump core details

* ci: setup server before execution

* chore(backend): remove useless changes

* feat(frontend): get core details using react query

* refactor(frontend): extract into function

* chore(frontend): do not set core details cookie

* feat(backend): return version from core details

* feat(frontend): display version in footer

* chore(frontend): remove biome suppression comment

* ci: remove entrypoint script

* feat(backend): cache new user preferences

* chore(frontend): remove from queries

* chore(frontend): do not check for additional stuff

* feat(frontend): cache user details using react query

* chore(frontend): remove useless function calls

* feat(frontend): invalidate user details

* chore(frontend): change value passed
  • Loading branch information
IgnisDa committed Jul 7, 2024
1 parent 182e4e7 commit c88f758
Show file tree
Hide file tree
Showing 15 changed files with 111 additions and 135 deletions.
7 changes: 3 additions & 4 deletions apps/backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,9 @@ async fn main() -> Result<()> {
let pull_every_minutes = config.integration.pull_every_minutes;
let max_file_size = config.server.max_file_size;
let disable_background_jobs = config.server.disable_background_jobs;
fs::write(
&config.server.config_dump_path,
serde_json::to_string_pretty(&config)?,
)?;

let config_dump_path = PathBuf::new().join(TEMP_DIR).join("config.json");
fs::write(config_dump_path, serde_json::to_string_pretty(&config)?)?;

let mut aws_conf = aws_sdk_s3::Config::builder()
.region(Region::new(config.file_storage.s3_region.clone()))
Expand Down
14 changes: 8 additions & 6 deletions apps/backend/src/miscellaneous/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ use crate::{
add_entity_to_collection, associate_user_with_entity, entity_in_collections,
get_current_date, get_stored_asset, get_user_to_entity_association, ilike_sql,
partial_user_by_id, user_by_id, user_id_from_token, AUTHOR, SHOW_SPECIAL_SEASON_NAMES,
TEMP_DIR,
TEMP_DIR, VERSION,
},
};

Expand Down Expand Up @@ -541,10 +541,11 @@ struct MediaConsumedInput {
lot: MediaLot,
}

#[derive(SimpleObject)]
#[derive(Debug, SimpleObject, Serialize, Deserialize)]
struct CoreDetails {
is_pro: bool,
page_limit: i32,
version: String,
timezone: String,
docs_link: String,
oidc_enabled: bool,
Expand Down Expand Up @@ -761,7 +762,7 @@ pub struct MiscellaneousQuery;
#[Object]
impl MiscellaneousQuery {
/// Get some primary information about the service.
async fn core_details(&self, gql_ctx: &Context<'_>) -> Result<CoreDetails> {
async fn core_details(&self, gql_ctx: &Context<'_>) -> CoreDetails {
let service = gql_ctx.data_unchecked::<Arc<MiscellaneousService>>();
service.core_details().await
}
Expand Down Expand Up @@ -1441,9 +1442,10 @@ impl MiscellaneousService {
type EntityBeingMonitoredByMap = HashMap<String, Vec<String>>;

impl MiscellaneousService {
async fn core_details(&self) -> Result<CoreDetails> {
Ok(CoreDetails {
async fn core_details(&self) -> CoreDetails {
CoreDetails {
is_pro: false,
version: VERSION.to_owned(),
author_name: AUTHOR.to_owned(),
timezone: self.timezone.to_string(),
oidc_enabled: self.oidc_client.is_some(),
Expand All @@ -1453,7 +1455,7 @@ impl MiscellaneousService {
local_auth_disabled: self.config.users.disable_local_auth,
token_valid_for_days: self.config.users.token_valid_for_days,
repository_link: "https://github.com/ignisda/ryot".to_owned(),
})
}
}

fn get_integration_service(&self) -> IntegrationService {
Expand Down
20 changes: 17 additions & 3 deletions apps/frontend/app/lib/generals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ export const LOGO_IMAGE_URL =
"https://raw.githubusercontent.com/IgnisDa/ryot/main/libs/assets/icon-512x512.png";
export const redirectToQueryParam = "redirectTo";
export const AUTH_COOKIE_NAME = "Auth";
export const USER_PREFERENCES_COOKIE_NAME = "UserPreferences";
export const CORE_DETAILS_COOKIE_NAME = "CoreDetails";
export const USER_DETAILS_COOKIE_NAME = "UserDetails";

const enhancedSearchParamCookie = "SearchParams__";
export const enhancedCookieName = (name: string) =>
Expand Down Expand Up @@ -231,6 +228,15 @@ export const getSurroundingElements = <T>(
return [elementIndex - 1, elementIndex, elementIndex + 1];
};

const usersQueryKeys = createQueryKeys("users", {
details: (token: string) => ({
queryKey: ["userDetails", token],
}),
preferences: (userId: string) => ({
queryKey: ["userPreferences", userId],
}),
});

const mediaQueryKeys = createQueryKeys("media", {
metadataDetails: (metadataId: string) => ({
queryKey: ["metadataDetails", metadataId],
Expand Down Expand Up @@ -258,10 +264,18 @@ const fitnessQueryKeys = createQueryKeys("fitness", {
}),
});

const miscellaneousQueryKeys = createQueryKeys("miscellaneous", {
coreDetails: () => ({
queryKey: ["coreDetails"],
}),
});

export const queryFactory = mergeQueryKeys(
usersQueryKeys,
mediaQueryKeys,
collectionQueryKeys,
fitnessQueryKeys,
miscellaneousQueryKeys,
);

export const convertEntityToIndividualId = (
Expand Down
127 changes: 49 additions & 78 deletions apps/frontend/app/lib/utilities.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ import {
CoreEnabledFeaturesDocument,
GetPresignedS3UrlDocument,
PresignedPutS3UrlDocument,
type User,
UserCollectionsListDocument,
type UserPreferences,
UserPreferencesDocument,
} from "@ryot/generated/graphql/backend/graphql";
import { UserDetailsDocument } from "@ryot/generated/graphql/backend/graphql";
Expand All @@ -26,10 +24,7 @@ import { v4 as randomUUID } from "uuid";
import { type ZodTypeAny, type output, z } from "zod";
import {
AUTH_COOKIE_NAME,
CORE_DETAILS_COOKIE_NAME,
CurrentWorkoutKey,
USER_DETAILS_COOKIE_NAME,
USER_PREFERENCES_COOKIE_NAME,
dayjsLib,
queryClient,
queryFactory,
Expand Down Expand Up @@ -58,16 +53,9 @@ export const getAuthorizationHeader = (request?: Request, token?: string) => {
return { Authorization: `Bearer ${cookie}` };
};

export const getIsAuthenticated = (request: Request) => {
const cookie = getAuthorizationCookie(request);
if (!cookie) return [false, null] as const;
const value = getCookieValue(request, USER_DETAILS_COOKIE_NAME);
return [true, JSON.parse(value) as User] as const;
};

export const redirectIfNotAuthenticatedOrUpdated = async (request: Request) => {
const [isAuthenticated, userDetails] = getIsAuthenticated(request);
if (!isAuthenticated) {
const { userDetails } = await getCachedUserDetails(request);
if (!userDetails || userDetails.__typename === "UserDetailsError") {
const nextUrl = withoutHost(request.url);
throw redirect($path("/auth", { [redirectToQueryParam]: nextUrl }), {
status: 302,
Expand Down Expand Up @@ -138,6 +126,44 @@ export const processSubmission = <Schema extends ZodTypeAny>(
return submission.value;
};

export const getCachedCoreDetails = async () => {
return await queryClient.ensureQueryData({
queryKey: queryFactory.miscellaneous.coreDetails().queryKey,
queryFn: () => serverGqlService.request(CoreDetailsDocument),
staleTime: Number.POSITIVE_INFINITY,
});
};

export const getCachedUserDetails = async (request: Request) => {
const token = getAuthorizationCookie(request);
return await queryClient.ensureQueryData({
queryKey: queryFactory.users.details(token).queryKey,
queryFn: () =>
serverGqlService.request(
UserDetailsDocument,
undefined,
getAuthorizationHeader(request),
),
staleTime: Number.POSITIVE_INFINITY,
});
};

export const getCachedUserPreferences = async (request: Request) => {
const userDetails = await redirectIfNotAuthenticatedOrUpdated(request);
return queryClient.ensureQueryData({
queryKey: queryFactory.users.preferences(userDetails.id).queryKey,
queryFn: () =>
serverGqlService
.request(
UserPreferencesDocument,
undefined,
getAuthorizationHeader(request),
)
.then((data) => data.userPreferences),
staleTime: Number.POSITIVE_INFINITY,
});
};

export const getCachedUserCollectionsList = async (request: Request) => {
const userDetails = await redirectIfNotAuthenticatedOrUpdated(request);
return queryClient.ensureQueryData({
Expand All @@ -150,7 +176,7 @@ export const getCachedUserCollectionsList = async (request: Request) => {
getAuthorizationHeader(request),
)
.then((data) => data.userCollectionsList),
gcTime: dayjsLib.duration(1, "hour").asMilliseconds(),
staleTime: dayjsLib.duration(1, "hour").asMilliseconds(),
});
};

Expand Down Expand Up @@ -229,12 +255,6 @@ export const s3FileUploader = (prefix: string) =>
return undefined;
}, unstable_createMemoryUploadHandler());

export const getUserPreferences = async (request: Request) => {
await redirectIfNotAuthenticatedOrUpdated(request);
const preferences = getCookieValue(request, USER_PREFERENCES_COOKIE_NAME);
return JSON.parse(preferences) as UserPreferences;
};

export const getCoreEnabledFeatures = async () => {
const { coreEnabledFeatures } = await serverGqlService.request(
CoreEnabledFeaturesDocument,
Expand Down Expand Up @@ -310,67 +330,18 @@ export const getToast = async (request: Request) => {
};

export const getCookiesForApplication = async (token: string) => {
const [{ coreDetails }, { userPreferences }, { userDetails }] =
await Promise.all([
serverGqlService.request(CoreDetailsDocument),
serverGqlService.request(
UserPreferencesDocument,
undefined,
getAuthorizationHeader(undefined, token),
),
serverGqlService.request(
UserDetailsDocument,
undefined,
getAuthorizationHeader(undefined, token),
),
]);
const [{ coreDetails }] = await Promise.all([getCachedCoreDetails()]);
const maxAge = coreDetails.tokenValidForDays * 24 * 60 * 60;
const options = { maxAge, path: "/" } satisfies CookieSerializeOptions;
return combineHeaders(
{
"set-cookie": serialize(
CORE_DETAILS_COOKIE_NAME,
JSON.stringify(coreDetails),
options,
),
},
{
"set-cookie": serialize(
USER_PREFERENCES_COOKIE_NAME,
JSON.stringify(userPreferences),
options,
),
},
{
"set-cookie": serialize(
USER_DETAILS_COOKIE_NAME,
JSON.stringify(userDetails),
options,
),
},
{ "set-cookie": serialize(AUTH_COOKIE_NAME, token, options) },
);
return combineHeaders({
"set-cookie": serialize(AUTH_COOKIE_NAME, token, options),
});
};

export const getLogoutCookies = () => {
return combineHeaders(
{ "set-cookie": serialize(AUTH_COOKIE_NAME, "", { expires: new Date(0) }) },
{
"set-cookie": serialize(CORE_DETAILS_COOKIE_NAME, "", {
expires: new Date(0),
}),
},
{
"set-cookie": serialize(USER_PREFERENCES_COOKIE_NAME, "", {
expires: new Date(0),
}),
},
{
"set-cookie": serialize(USER_DETAILS_COOKIE_NAME, "", {
expires: new Date(0),
}),
},
);
return combineHeaders({
"set-cookie": serialize(AUTH_COOKIE_NAME, "", { expires: new Date(0) }),
});
};

export const extendResponseHeaders = (
Expand All @@ -392,7 +363,7 @@ export const redirectUsingEnhancedCookieSearchParams = async (
request: Request,
cookieName: string,
) => {
const preferences = await getUserPreferences(request);
const preferences = await getCachedUserPreferences(request);
const searchParams = new URL(request.url).searchParams;
if (searchParams.size > 0 || !preferences.general.persistQueries) return;
const cookies = parse(request.headers.get("cookie") || "");
Expand Down
4 changes: 2 additions & 2 deletions apps/frontend/app/routes/_dashboard._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import { useMetadataProgressUpdate } from "~/lib/state/media";
import {
getAuthorizationHeader,
getCachedUserCollectionsList,
getUserPreferences,
getCachedUserPreferences,
serverGqlService,
} from "~/lib/utilities.server";

Expand All @@ -67,7 +67,7 @@ const getTake = (preferences: UserPreferences, el: DashboardElementLot) => {
};

export const loader = unstable_defineLoader(async ({ request }) => {
const preferences = await getUserPreferences(request);
const preferences = await getCachedUserPreferences(request);
const takeUpcoming = getTake(preferences, DashboardElementLot.Upcoming);
const takeInProgress = getTake(preferences, DashboardElementLot.InProgress);
const userCollectionsList = await getCachedUserCollectionsList(request);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// biome-ignore lint/style/useNodejsImportProtocol: This is a browser import
import { Buffer } from "buffer";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { DragDropContext, Draggable, Droppable } from "@hello-pangea/dnd";
Expand Down
16 changes: 7 additions & 9 deletions apps/frontend/app/routes/_dashboard.settings.preferences.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,13 @@ import { Fragment, useState } from "react";
import { match } from "ts-pattern";
import { z } from "zod";
import { zx } from "zodix";
import { queryClient, queryFactory } from "~/lib/generals";
import { useUserDetails, useUserPreferences } from "~/lib/hooks";
import {
combineHeaders,
createToastHeaders,
getAuthorizationCookie,
getAuthorizationHeader,
getCookiesForApplication,
isWorkoutActive,
redirectIfNotAuthenticatedOrUpdated,
serverGqlService,
} from "~/lib/utilities.server";
import classes from "~/styles/preferences.module.css";
Expand All @@ -87,6 +86,7 @@ const notificationContent = {
};

export const action = unstable_defineAction(async ({ request }) => {
const userDetails = await redirectIfNotAuthenticatedOrUpdated(request);
const entries = Object.entries(Object.fromEntries(await request.formData()));
const submission = [];
for (let [property, value] of entries) {
Expand All @@ -106,16 +106,14 @@ export const action = unstable_defineAction(async ({ request }) => {
getAuthorizationHeader(request),
);
}
const token = getAuthorizationCookie(request);
const applicationHeaders = await getCookiesForApplication(token);
queryClient.removeQueries({
queryKey: queryFactory.users.preferences(userDetails.id).queryKey,
});
const toastHeaders = await createToastHeaders({
message: "Preferences updated",
type: "success",
});
return Response.json(
{},
{ headers: combineHeaders(applicationHeaders, toastHeaders) },
);
return Response.json({}, { headers: toastHeaders });
});

export default function Page() {
Expand Down
Loading

0 comments on commit c88f758

Please sign in to comment.