diff --git a/packages/backend/server/src/core/config/resolver.ts b/packages/backend/server/src/core/config/resolver.ts index ed6d6c3ba..a1f5d66b6 100644 --- a/packages/backend/server/src/core/config/resolver.ts +++ b/packages/backend/server/src/core/config/resolver.ts @@ -1,3 +1,4 @@ +import { Logger } from '@nestjs/common'; import { Args, Field, @@ -39,6 +40,27 @@ export class CredentialsRequirementType { registerEnumType(RuntimeConfigType, { name: 'RuntimeConfigType', }); + +@ObjectType() +export class ReleaseVersionType { + @Field() + version!: string; + + @Field() + url!: string; + + @Field(() => GraphQLISODateTime) + publishedAt!: Date; + + @Field() + changelog!: string; +} + +const RELEASE_CHANNEL_MAP = new Map([ + ['dev', 'canary'], + ['beta', 'beta'], + ['production', 'stable'], +]); @ObjectType() export class ServerRuntimeConfigType implements Partial { @Field() @@ -74,6 +96,8 @@ export class ServerFlagsType implements ServerFlags { @Resolver(() => ServerConfigType) export class ServerConfigResolver { + private readonly logger = new Logger(ServerConfigResolver.name); + constructor( private readonly config: Config, private readonly runtime: Runtime, @@ -135,6 +159,53 @@ export class ServerConfigResolver { async initialized() { return this.server.initialized(); } + + @ResolveField(() => ReleaseVersionType, { + description: 'fetch latest available upgradable release of server', + }) + async availableUpgrade(): Promise { + const channel = RELEASE_CHANNEL_MAP.get(this.config.AFFINE_ENV) ?? 'stable'; + const url = `https://affine.pro/api/worker/releases?channel=${channel}`; + + try { + const response = await fetch(url, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Cache-Control': 'no-cache', + }, + }); + + if (!response.ok) { + this.logger.error( + 'failed to fetch affine releases', + await response.text() + ); + return null; + } + const releases = (await response.json()) as Array<{ + name: string; + url: string; + body: string; + published_at: string; + }>; + + const latest = releases.at(0); + if (!latest || latest.name === this.config.version) { + return null; + } + + return { + version: latest.name, + url: latest.url, + changelog: latest.body, + publishedAt: new Date(latest.published_at), + }; + } catch (e) { + this.logger.error('failed to fetch affine releases', e); + return null; + } + } } @Resolver(() => ServerConfigType) diff --git a/packages/backend/server/src/schema.gql b/packages/backend/server/src/schema.gql index 14e3c826c..57b0c723e 100644 --- a/packages/backend/server/src/schema.gql +++ b/packages/backend/server/src/schema.gql @@ -1091,6 +1091,13 @@ type QueryTooLongDataType { max: Int! } +type ReleaseVersionType { + changelog: String! + publishedAt: DateTime! + url: String! + version: String! +} + type RemoveAvatar { success: Boolean! } @@ -1128,6 +1135,9 @@ type SameSubscriptionRecurringDataType { } type ServerConfigType { + """fetch latest available upgradable release of server""" + availableUpgrade: ReleaseVersionType! + """Features for user that can be configured""" availableUserFeatures: [FeatureType!]!