From c88f758ab9da00e5b6f7216be986d1115efbf2e2 Mon Sep 17 00:00:00 2001 From: Diptesh Choudhuri Date: Sun, 7 Jul 2024 23:11:24 +0530 Subject: [PATCH] Display version in footer (#905) * 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 --- apps/backend/src/main.rs | 7 +- apps/backend/src/miscellaneous/resolver.rs | 14 +- apps/frontend/app/lib/generals.ts | 20 ++- apps/frontend/app/lib/utilities.server.ts | 127 +++++++----------- .../frontend/app/routes/_dashboard._index.tsx | 4 +- .../_dashboard.fitness.workouts.current.tsx | 1 - .../_dashboard.settings.preferences.tsx | 16 +-- .../routes/_dashboard.settings.profile.tsx | 14 +- apps/frontend/app/routes/_dashboard.tsx | 21 ++- apps/frontend/app/routes/auth.tsx | 4 +- docs/includes/backend-config-schema.yaml | 4 - libs/config/src/lib.rs | 4 - libs/generated/src/graphql/backend/gql.ts | 4 +- libs/generated/src/graphql/backend/graphql.ts | 5 +- .../src/backend/queries/CoreDetails.gql | 1 + 15 files changed, 111 insertions(+), 135 deletions(-) diff --git a/apps/backend/src/main.rs b/apps/backend/src/main.rs index 62255b8414..d905d8bbba 100644 --- a/apps/backend/src/main.rs +++ b/apps/backend/src/main.rs @@ -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())) diff --git a/apps/backend/src/miscellaneous/resolver.rs b/apps/backend/src/miscellaneous/resolver.rs index ad4606d744..b4073b8b77 100644 --- a/apps/backend/src/miscellaneous/resolver.rs +++ b/apps/backend/src/miscellaneous/resolver.rs @@ -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, }, }; @@ -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, @@ -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 { + async fn core_details(&self, gql_ctx: &Context<'_>) -> CoreDetails { let service = gql_ctx.data_unchecked::>(); service.core_details().await } @@ -1441,9 +1442,10 @@ impl MiscellaneousService { type EntityBeingMonitoredByMap = HashMap>; impl MiscellaneousService { - async fn core_details(&self) -> Result { - 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(), @@ -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 { diff --git a/apps/frontend/app/lib/generals.ts b/apps/frontend/app/lib/generals.ts index 64f6317508..63d69dc620 100644 --- a/apps/frontend/app/lib/generals.ts +++ b/apps/frontend/app/lib/generals.ts @@ -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) => @@ -231,6 +228,15 @@ export const getSurroundingElements = ( 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], @@ -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 = ( diff --git a/apps/frontend/app/lib/utilities.server.ts b/apps/frontend/app/lib/utilities.server.ts index 0978749397..08d88b3cfa 100644 --- a/apps/frontend/app/lib/utilities.server.ts +++ b/apps/frontend/app/lib/utilities.server.ts @@ -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"; @@ -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, @@ -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, @@ -138,6 +126,44 @@ export const processSubmission = ( 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({ @@ -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(), }); }; @@ -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, @@ -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 = ( @@ -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") || ""); diff --git a/apps/frontend/app/routes/_dashboard._index.tsx b/apps/frontend/app/routes/_dashboard._index.tsx index 1b8291c5a0..064bb3a977 100644 --- a/apps/frontend/app/routes/_dashboard._index.tsx +++ b/apps/frontend/app/routes/_dashboard._index.tsx @@ -54,7 +54,7 @@ import { useMetadataProgressUpdate } from "~/lib/state/media"; import { getAuthorizationHeader, getCachedUserCollectionsList, - getUserPreferences, + getCachedUserPreferences, serverGqlService, } from "~/lib/utilities.server"; @@ -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); diff --git a/apps/frontend/app/routes/_dashboard.fitness.workouts.current.tsx b/apps/frontend/app/routes/_dashboard.fitness.workouts.current.tsx index 54077823f6..2c77c3f78f 100644 --- a/apps/frontend/app/routes/_dashboard.fitness.workouts.current.tsx +++ b/apps/frontend/app/routes/_dashboard.fitness.workouts.current.tsx @@ -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"; diff --git a/apps/frontend/app/routes/_dashboard.settings.preferences.tsx b/apps/frontend/app/routes/_dashboard.settings.preferences.tsx index 940aa24acd..61e09dbd27 100644 --- a/apps/frontend/app/routes/_dashboard.settings.preferences.tsx +++ b/apps/frontend/app/routes/_dashboard.settings.preferences.tsx @@ -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"; @@ -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) { @@ -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() { diff --git a/apps/frontend/app/routes/_dashboard.settings.profile.tsx b/apps/frontend/app/routes/_dashboard.settings.profile.tsx index 9a4a28c496..8f7ca3ee37 100644 --- a/apps/frontend/app/routes/_dashboard.settings.profile.tsx +++ b/apps/frontend/app/routes/_dashboard.settings.profile.tsx @@ -12,13 +12,12 @@ import { UpdateUserDocument } from "@ryot/generated/graphql/backend/graphql"; import { useRef } from "react"; import { z } from "zod"; import { confirmWrapper } from "~/components/confirmation"; +import { queryClient, queryFactory } from "~/lib/generals"; import { useUserDetails } from "~/lib/hooks"; import { - combineHeaders, createToastHeaders, getAuthorizationCookie, getAuthorizationHeader, - getCookiesForApplication, serverGqlService, } from "~/lib/utilities.server"; import { processSubmission } from "~/lib/utilities.server"; @@ -28,6 +27,7 @@ export const meta = (_args: MetaArgs_SingleFetch) => { }; export const action = unstable_defineAction(async ({ request }) => { + const token = getAuthorizationCookie(request); const formData = await request.formData(); const submission = processSubmission(formData, updateProfileFormSchema); await serverGqlService.request( @@ -35,13 +35,13 @@ export const action = unstable_defineAction(async ({ request }) => { { input: submission }, getAuthorizationHeader(request), ); - const token = getAuthorizationCookie(request); - const applicationHeaders = await getCookiesForApplication(token); - const toastHeaders = await createToastHeaders({ - message: "Profile updated. Please login again for changes to take effect.", + queryClient.removeQueries({ + queryKey: queryFactory.users.details(token).queryKey, }); return Response.json({ status: "success", submission } as const, { - headers: combineHeaders(toastHeaders, applicationHeaders), + headers: await createToastHeaders({ + message: "Profile updated successfully", + }), }); }); diff --git a/apps/frontend/app/routes/_dashboard.tsx b/apps/frontend/app/routes/_dashboard.tsx index 06b110f681..395871c641 100644 --- a/apps/frontend/app/routes/_dashboard.tsx +++ b/apps/frontend/app/routes/_dashboard.tsx @@ -57,7 +57,6 @@ import { } from "@remix-run/react"; import { CollectionExtraInformationLot, - type CoreDetails, EntityLot, MediaLot, type MetadataDetailsQuery, @@ -101,7 +100,6 @@ import { joinURL, withQuery } from "ufo"; import { HiddenLocationInput } from "~/components/common"; import events from "~/lib/events"; import { - CORE_DETAILS_COOKIE_NAME, LOGO_IMAGE_URL, Verb, getLot, @@ -125,9 +123,9 @@ import { } from "~/lib/state/media"; import { serverVariables as envData, + getCachedCoreDetails, getCachedUserCollectionsList, - getCookieValue, - getUserPreferences, + getCachedUserPreferences, isWorkoutActive, redirectIfNotAuthenticatedOrUpdated, } from "~/lib/utilities.server"; @@ -137,12 +135,13 @@ import classes from "~/styles/dashboard.module.css"; export const loader = unstable_defineLoader(async ({ request }) => { const userDetails = await redirectIfNotAuthenticatedOrUpdated(request); - const [userPreferences, userCollections] = await Promise.all([ - getUserPreferences(request), - getCachedUserCollectionsList(request), - ]); - const details = getCookieValue(request, CORE_DETAILS_COOKIE_NAME); - const coreDetails = JSON.parse(details) as CoreDetails; + const [userPreferences, userCollections, { coreDetails }] = await Promise.all( + [ + getCachedUserPreferences(request), + getCachedUserCollectionsList(request), + getCachedCoreDetails(), + ], + ); const mediaLinks = [ ...(Object.entries(userPreferences.featuresEnabled.media || {}) @@ -651,7 +650,7 @@ const Footer = () => { - {loaderData.coreDetails.timezone} + {loaderData.coreDetails.version} diff --git a/apps/frontend/app/routes/auth.tsx b/apps/frontend/app/routes/auth.tsx index 855b4e3abb..b6da3c4361 100644 --- a/apps/frontend/app/routes/auth.tsx +++ b/apps/frontend/app/routes/auth.tsx @@ -42,9 +42,9 @@ import { zx } from "zodix"; import { redirectToQueryParam } from "~/lib/generals"; import { createToastHeaders, + getAuthorizationCookie, getCookiesForApplication, getCoreEnabledFeatures, - getIsAuthenticated, processSubmission, redirectWithToast, serverGqlService, @@ -59,7 +59,7 @@ export type SearchParams = z.infer & export const loader = unstable_defineLoader(async ({ request }) => { const query = zx.parseQuery(request, searchParamsSchema); - const [isAuthenticated, _] = await getIsAuthenticated(request); + const isAuthenticated = !!getAuthorizationCookie(request); if (isAuthenticated) throw await redirectWithToast($path("/"), { message: "You were already logged in", diff --git a/docs/includes/backend-config-schema.yaml b/docs/includes/backend-config-schema.yaml index 5c93312f3b..e951b5a5e7 100644 --- a/docs/includes/backend-config-schema.yaml +++ b/docs/includes/backend-config-schema.yaml @@ -145,10 +145,6 @@ scheduler: # Settings related to server. server: - # The path where the config file will be written once the server boots up. - # @envvar SERVER_CONFIG_DUMP_PATH - config_dump_path: "" - # An array of URLs for CORS. # @envvar SERVER_CORS_ORIGINS cors_origins: [] diff --git a/libs/config/src/lib.rs b/libs/config/src/lib.rs index 0134357fff..96729288a7 100644 --- a/libs/config/src/lib.rs +++ b/libs/config/src/lib.rs @@ -335,9 +335,6 @@ pub struct ServerConfig { /// The OIDC related settings. #[setting(nested)] pub oidc: OidcConfig, - /// The path where the config file will be written once the server boots up. - #[setting(default = format!("tmp/{}-config.json", PROJECT_NAME))] - pub config_dump_path: String, /// An array of URLs for CORS. #[setting(default = vec![], parse_env = schematic::env::split_comma)] pub cors_origins: Vec, @@ -454,7 +451,6 @@ impl AppConfig { cl.podcasts.listennotes.api_token = gt(); cl.video_games.twitch.client_id = gt(); cl.video_games.twitch.client_secret = gt(); - cl.server.config_dump_path = gt(); cl.server.cors_origins = vec![gt()]; cl.users.jwt_secret = gt(); cl.server.smtp.server = gt(); diff --git a/libs/generated/src/graphql/backend/gql.ts b/libs/generated/src/graphql/backend/gql.ts index 571c08f076..ab33d8d879 100644 --- a/libs/generated/src/graphql/backend/gql.ts +++ b/libs/generated/src/graphql/backend/gql.ts @@ -17,7 +17,7 @@ const documents = { "mutation RegisterUser($input: AuthUserInput!) {\n registerUser(input: $input) {\n __typename\n ... on RegisterError {\n error\n }\n ... on StringIdObject {\n id\n }\n }\n}": types.RegisterUserDocument, "mutation AddEntityToCollection($input: ChangeCollectionToEntityInput!) {\n addEntityToCollection(input: $input)\n}\n\nmutation CommitMetadata($input: CommitMediaInput!) {\n commitMetadata(input: $input) {\n id\n }\n}\n\nmutation CommitMetadataGroup($input: CommitMediaInput!) {\n commitMetadataGroup(input: $input) {\n id\n }\n}\n\nmutation CommitPerson($input: CommitPersonInput!) {\n commitPerson(input: $input) {\n id\n }\n}\n\nmutation CreateCustomExercise($input: ExerciseInput!) {\n createCustomExercise(input: $input)\n}\n\nmutation EditCustomExercise($input: EditCustomExerciseInput!) {\n editCustomExercise(input: $input)\n}\n\nmutation CreateCustomMetadata($input: CreateCustomMetadataInput!) {\n createCustomMetadata(input: $input) {\n id\n }\n}\n\nmutation CreateOrUpdateCollection($input: CreateOrUpdateCollectionInput!) {\n createOrUpdateCollection(input: $input) {\n id\n }\n}\n\nmutation CreateReviewComment($input: CreateReviewCommentInput!) {\n createReviewComment(input: $input)\n}\n\nmutation CreateUserMeasurement($input: UserMeasurementInput!) {\n createUserMeasurement(input: $input)\n}\n\nmutation CreateUserNotificationPlatform($input: CreateUserNotificationPlatformInput!) {\n createUserNotificationPlatform(input: $input)\n}\n\nmutation CreateUserIntegration($input: CreateIntegrationInput!) {\n createUserIntegration(input: $input) {\n id\n }\n}\n\nmutation CreateUserWorkout($input: UserWorkoutInput!) {\n createUserWorkout(input: $input)\n}\n\nmutation DeleteCollection($collectionName: String!) {\n deleteCollection(collectionName: $collectionName)\n}\n\nmutation DeleteReview($reviewId: String!) {\n deleteReview(reviewId: $reviewId)\n}\n\nmutation DeleteS3Object($key: String!) {\n deleteS3Object(key: $key)\n}\n\nmutation DeleteSeenItem($seenId: String!) {\n deleteSeenItem(seenId: $seenId) {\n id\n }\n}\n\nmutation DeleteUser($toDeleteUserId: String!) {\n deleteUser(toDeleteUserId: $toDeleteUserId)\n}\n\nmutation DeleteUserIntegration($integrationId: String!) {\n deleteUserIntegration(integrationId: $integrationId)\n}\n\nmutation DeleteUserMeasurement($timestamp: DateTime!) {\n deleteUserMeasurement(timestamp: $timestamp)\n}\n\nmutation DeleteUserNotificationPlatform($notificationId: Int!) {\n deleteUserNotificationPlatform(notificationId: $notificationId)\n}\n\nmutation DeleteUserWorkout($workoutId: String!) {\n deleteUserWorkout(workoutId: $workoutId)\n}\n\nmutation DeployBackgroundJob($jobName: BackgroundJob!) {\n deployBackgroundJob(jobName: $jobName)\n}\n\nmutation DeployBulkProgressUpdate($input: [ProgressUpdateInput!]!) {\n deployBulkProgressUpdate(input: $input)\n}\n\nmutation DeployExportJob($toExport: [ExportItem!]!) {\n deployExportJob(toExport: $toExport)\n}\n\nmutation DeployImportJob($input: DeployImportJobInput!) {\n deployImportJob(input: $input)\n}\n\nmutation DeployUpdateMetadataJob($metadataId: String!) {\n deployUpdateMetadataJob(metadataId: $metadataId)\n}\n\nmutation DeployUpdatePersonJob($personId: String!) {\n deployUpdatePersonJob(personId: $personId)\n}\n\nmutation EditSeenItem($input: EditSeenItemInput!) {\n editSeenItem(input: $input)\n}\n\nmutation EditUserWorkout($input: EditUserWorkoutInput!) {\n editUserWorkout(input: $input)\n}\n\nmutation GenerateAuthToken {\n generateAuthToken\n}\n\nmutation MergeMetadata($mergeFrom: String!, $mergeInto: String!) {\n mergeMetadata(mergeFrom: $mergeFrom, mergeInto: $mergeInto)\n}\n\nmutation PostReview($input: PostReviewInput!) {\n postReview(input: $input) {\n id\n }\n}\n\nmutation PresignedPutS3Url($input: PresignedPutUrlInput!) {\n presignedPutS3Url(input: $input) {\n key\n uploadUrl\n }\n}\n\nmutation RemoveEntityFromCollection($input: ChangeCollectionToEntityInput!) {\n removeEntityFromCollection(input: $input) {\n id\n }\n}\n\nmutation TestUserNotificationPlatforms {\n testUserNotificationPlatforms\n}\n\nmutation UpdateUser($input: UpdateUserInput!) {\n updateUser(input: $input) {\n id\n }\n}\n\nmutation UpdateUserPreference($input: UpdateUserPreferenceInput!) {\n updateUserPreference(input: $input)\n}": types.AddEntityToCollectionDocument, "query CollectionContents($input: CollectionContentsInput!) {\n collectionContents(input: $input) {\n user {\n id\n name\n }\n reviews {\n ...ReviewItemPart\n }\n results {\n details {\n total\n nextPage\n }\n items {\n metadataLot\n entityLot\n details {\n ...MetadataSearchItemPart\n }\n }\n }\n details {\n name\n description\n createdOn\n }\n }\n}": types.CollectionContentsDocument, - "query CoreDetails {\n coreDetails {\n isPro\n timezone\n docsLink\n pageLimit\n websiteUrl\n authorName\n oidcEnabled\n repositoryLink\n localAuthDisabled\n tokenValidForDays\n }\n}": types.CoreDetailsDocument, + "query CoreDetails {\n coreDetails {\n isPro\n version\n timezone\n docsLink\n pageLimit\n websiteUrl\n authorName\n oidcEnabled\n repositoryLink\n localAuthDisabled\n tokenValidForDays\n }\n}": types.CoreDetailsDocument, "query ExerciseDetails($exerciseId: String!) {\n exerciseDetails(exerciseId: $exerciseId) {\n id\n lot\n source\n level\n force\n mechanic\n equipment\n muscles\n createdByUserId\n attributes {\n instructions\n images\n }\n }\n}": types.ExerciseDetailsDocument, "query ExerciseParameters {\n exerciseParameters {\n filters {\n type\n level\n force\n mechanic\n equipment\n muscle\n }\n downloadRequired\n }\n}": types.ExerciseParametersDocument, "query ExercisesList($input: ExercisesListInput!) {\n exercisesList(input: $input) {\n details {\n total\n nextPage\n }\n items {\n id\n lot\n image\n muscle\n numTimesInteracted\n lastUpdatedOn\n }\n }\n}": types.ExercisesListDocument, @@ -80,7 +80,7 @@ export function graphql(source: "query CollectionContents($input: CollectionCont /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query CoreDetails {\n coreDetails {\n isPro\n timezone\n docsLink\n pageLimit\n websiteUrl\n authorName\n oidcEnabled\n repositoryLink\n localAuthDisabled\n tokenValidForDays\n }\n}"): (typeof documents)["query CoreDetails {\n coreDetails {\n isPro\n timezone\n docsLink\n pageLimit\n websiteUrl\n authorName\n oidcEnabled\n repositoryLink\n localAuthDisabled\n tokenValidForDays\n }\n}"]; +export function graphql(source: "query CoreDetails {\n coreDetails {\n isPro\n version\n timezone\n docsLink\n pageLimit\n websiteUrl\n authorName\n oidcEnabled\n repositoryLink\n localAuthDisabled\n tokenValidForDays\n }\n}"): (typeof documents)["query CoreDetails {\n coreDetails {\n isPro\n version\n timezone\n docsLink\n pageLimit\n websiteUrl\n authorName\n oidcEnabled\n repositoryLink\n localAuthDisabled\n tokenValidForDays\n }\n}"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/libs/generated/src/graphql/backend/graphql.ts b/libs/generated/src/graphql/backend/graphql.ts index cd99df39ef..4484c1959b 100644 --- a/libs/generated/src/graphql/backend/graphql.ts +++ b/libs/generated/src/graphql/backend/graphql.ts @@ -192,6 +192,7 @@ export type CoreDetails = { repositoryLink: Scalars['String']['output']; timezone: Scalars['String']['output']; tokenValidForDays: Scalars['Int']['output']; + version: Scalars['String']['output']; websiteUrl: Scalars['String']['output']; }; @@ -2716,7 +2717,7 @@ export type CollectionContentsQuery = { collectionContents: { user: { id: string export type CoreDetailsQueryVariables = Exact<{ [key: string]: never; }>; -export type CoreDetailsQuery = { coreDetails: { isPro: boolean, timezone: string, docsLink: string, pageLimit: number, websiteUrl: string, authorName: string, oidcEnabled: boolean, repositoryLink: string, localAuthDisabled: boolean, tokenValidForDays: number } }; +export type CoreDetailsQuery = { coreDetails: { isPro: boolean, version: string, timezone: string, docsLink: string, pageLimit: number, websiteUrl: string, authorName: string, oidcEnabled: boolean, repositoryLink: string, localAuthDisabled: boolean, tokenValidForDays: number } }; export type ExerciseDetailsQueryVariables = Exact<{ exerciseId: Scalars['String']['input']; @@ -3038,7 +3039,7 @@ export const TestUserNotificationPlatformsDocument = {"kind":"Document","definit export const UpdateUserDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateUser"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateUserInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateUser"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; export const UpdateUserPreferenceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateUserPreference"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateUserPreferenceInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateUserPreference"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; export const CollectionContentsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CollectionContents"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CollectionContentsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"collectionContents"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"reviews"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ReviewItemPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"results"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"nextPage"}}]}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"metadataLot"}},{"kind":"Field","name":{"kind":"Name","value":"entityLot"}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MetadataSearchItemPart"}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenShowExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenShowExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}},{"kind":"Field","name":{"kind":"Name","value":"season"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenPodcastExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenAnimeExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"episode"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SeenMangaExtraInformation"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"chapter"}},{"kind":"Field","name":{"kind":"Name","value":"volume"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ReviewItemPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ReviewItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"rating"}},{"kind":"Field","name":{"kind":"Name","value":"textOriginal"}},{"kind":"Field","name":{"kind":"Name","value":"textRendered"}},{"kind":"Field","name":{"kind":"Name","value":"isSpoiler"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"postedOn"}},{"kind":"Field","name":{"kind":"Name","value":"postedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"comments"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"text"}},{"kind":"Field","name":{"kind":"Name","value":"createdOn"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"likedBy"}}]}},{"kind":"Field","name":{"kind":"Name","value":"showExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenShowExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"podcastExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenPodcastExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"animeExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenAnimeExtraInformationPart"}}]}},{"kind":"Field","name":{"kind":"Name","value":"mangaExtraInformation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SeenMangaExtraInformationPart"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MetadataSearchItemPart"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MetadataSearchItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"identifier"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"publishYear"}}]}}]} as unknown as DocumentNode; -export const CoreDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CoreDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"coreDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"isPro"}},{"kind":"Field","name":{"kind":"Name","value":"timezone"}},{"kind":"Field","name":{"kind":"Name","value":"docsLink"}},{"kind":"Field","name":{"kind":"Name","value":"pageLimit"}},{"kind":"Field","name":{"kind":"Name","value":"websiteUrl"}},{"kind":"Field","name":{"kind":"Name","value":"authorName"}},{"kind":"Field","name":{"kind":"Name","value":"oidcEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"repositoryLink"}},{"kind":"Field","name":{"kind":"Name","value":"localAuthDisabled"}},{"kind":"Field","name":{"kind":"Name","value":"tokenValidForDays"}}]}}]}}]} as unknown as DocumentNode; +export const CoreDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CoreDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"coreDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"isPro"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"timezone"}},{"kind":"Field","name":{"kind":"Name","value":"docsLink"}},{"kind":"Field","name":{"kind":"Name","value":"pageLimit"}},{"kind":"Field","name":{"kind":"Name","value":"websiteUrl"}},{"kind":"Field","name":{"kind":"Name","value":"authorName"}},{"kind":"Field","name":{"kind":"Name","value":"oidcEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"repositoryLink"}},{"kind":"Field","name":{"kind":"Name","value":"localAuthDisabled"}},{"kind":"Field","name":{"kind":"Name","value":"tokenValidForDays"}}]}}]}}]} as unknown as DocumentNode; export const ExerciseDetailsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ExerciseDetails"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"exerciseId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"exerciseDetails"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"exerciseId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"exerciseId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"level"}},{"kind":"Field","name":{"kind":"Name","value":"force"}},{"kind":"Field","name":{"kind":"Name","value":"mechanic"}},{"kind":"Field","name":{"kind":"Name","value":"equipment"}},{"kind":"Field","name":{"kind":"Name","value":"muscles"}},{"kind":"Field","name":{"kind":"Name","value":"createdByUserId"}},{"kind":"Field","name":{"kind":"Name","value":"attributes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"instructions"}},{"kind":"Field","name":{"kind":"Name","value":"images"}}]}}]}}]}}]} as unknown as DocumentNode; export const ExerciseParametersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ExerciseParameters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"exerciseParameters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"filters"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"level"}},{"kind":"Field","name":{"kind":"Name","value":"force"}},{"kind":"Field","name":{"kind":"Name","value":"mechanic"}},{"kind":"Field","name":{"kind":"Name","value":"equipment"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}}]}},{"kind":"Field","name":{"kind":"Name","value":"downloadRequired"}}]}}]}}]} as unknown as DocumentNode; export const ExercisesListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ExercisesList"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ExercisesListInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"exercisesList"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"details"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}},{"kind":"Field","name":{"kind":"Name","value":"nextPage"}}]}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"lot"}},{"kind":"Field","name":{"kind":"Name","value":"image"}},{"kind":"Field","name":{"kind":"Name","value":"muscle"}},{"kind":"Field","name":{"kind":"Name","value":"numTimesInteracted"}},{"kind":"Field","name":{"kind":"Name","value":"lastUpdatedOn"}}]}}]}}]}}]} as unknown as DocumentNode; diff --git a/libs/graphql/src/backend/queries/CoreDetails.gql b/libs/graphql/src/backend/queries/CoreDetails.gql index 3d213c00c0..f94333aadd 100644 --- a/libs/graphql/src/backend/queries/CoreDetails.gql +++ b/libs/graphql/src/backend/queries/CoreDetails.gql @@ -1,6 +1,7 @@ query CoreDetails { coreDetails { isPro + version timezone docsLink pageLimit