diff --git a/apis/api-gateway/schema.graphql b/apis/api-gateway/schema.graphql index 9674113db0a..a0a91fcaa46 100644 --- a/apis/api-gateway/schema.graphql +++ b/apis/api-gateway/schema.graphql @@ -2199,6 +2199,18 @@ type UserRole @join__type(graph: API_JOURNEYS, key: "id") @join__type(graph: AP roles: [Role!] } +type AuthenticatedUser @join__type(graph: API_JOURNEYS, key: "id", extension: true) @join__type(graph: API_JOURNEYS_MODERN, key: "id", extension: true) @join__type(graph: API_LANGUAGES, key: "id", extension: true) @join__type(graph: API_MEDIA, key: "id", extension: true) @join__type(graph: API_USERS, key: "id") { + id: ID! + languageUserRoles: [LanguageRole!]! @join__field(graph: API_LANGUAGES) + mediaUserRoles: [MediaRole!]! @join__field(graph: API_MEDIA) + firstName: String! @join__field(graph: API_USERS) + lastName: String @join__field(graph: API_USERS) + email: String! @join__field(graph: API_USERS) + imageUrl: String @join__field(graph: API_USERS) + superAdmin: Boolean @join__field(graph: API_USERS) + emailVerified: Boolean! @join__field(graph: API_USERS) +} + type UserTeamInvite @join__type(graph: API_JOURNEYS) @join__type(graph: API_JOURNEYS_MODERN) { id: ID! teamId: ID! @@ -3257,16 +3269,6 @@ type ZodFieldError @join__type(graph: API_MEDIA) { path: [String!]! } -type AuthenticatedUser @join__type(graph: API_USERS, key: "id") { - id: ID! - firstName: String! - lastName: String - email: String! - imageUrl: String - superAdmin: Boolean - emailVerified: Boolean! -} - interface BaseError @join__type(graph: API_ANALYTICS) @join__type(graph: API_MEDIA) { message: String } @@ -3738,21 +3740,25 @@ enum PlausibleEvent @join__type(graph: API_JOURNEYS_MODERN) { journeyResponses @join__enumValue(graph: API_JOURNEYS_MODERN) } +enum LanguageRole @join__type(graph: API_LANGUAGES) { + publisher @join__enumValue(graph: API_LANGUAGES) +} + enum LanguageIdType @join__type(graph: API_LANGUAGES) { databaseId @join__enumValue(graph: API_LANGUAGES) bcp47 @join__enumValue(graph: API_LANGUAGES) } -enum LanguageRole @join__type(graph: API_LANGUAGES) { - publisher @join__enumValue(graph: API_LANGUAGES) -} - enum DefaultPlatform @join__type(graph: API_MEDIA) { ios @join__enumValue(graph: API_MEDIA) android @join__enumValue(graph: API_MEDIA) web @join__enumValue(graph: API_MEDIA) } +enum MediaRole @join__type(graph: API_MEDIA) { + publisher @join__enumValue(graph: API_MEDIA) +} + enum ImageAspectRatio @join__type(graph: API_MEDIA) { hd @join__enumValue(graph: API_MEDIA) banner @join__enumValue(graph: API_MEDIA) @@ -3764,10 +3770,6 @@ enum MaxResolutionTier @join__type(graph: API_MEDIA) { uhd @join__enumValue(graph: API_MEDIA) } -enum MediaRole @join__type(graph: API_MEDIA) { - publisher @join__enumValue(graph: API_MEDIA) -} - enum SegmindModel @join__type(graph: API_MEDIA) { sdxl1__0_txt2img @join__enumValue(graph: API_MEDIA) kandinsky2__2_txt2img @join__enumValue(graph: API_MEDIA) diff --git a/apis/api-journeys-modern/schema.graphql b/apis/api-journeys-modern/schema.graphql index cdefbabbfdb..0ee985a166e 100644 --- a/apis/api-journeys-modern/schema.graphql +++ b/apis/api-journeys-modern/schema.graphql @@ -7,6 +7,13 @@ interface Action { parentBlock: Block! } +type AuthenticatedUser + @key(fields: "id") + @extends +{ + id: ID! @external +} + interface Block { id: ID! journeyId: ID! diff --git a/apis/api-journeys-modern/src/schema/user/index.ts b/apis/api-journeys-modern/src/schema/user/index.ts index ddac8a72157..51b52301de8 100644 --- a/apis/api-journeys-modern/src/schema/user/index.ts +++ b/apis/api-journeys-modern/src/schema/user/index.ts @@ -1,3 +1,3 @@ import './user' -export { UserRef } from './user' +export { AuthenticatedUserRef, UserRef } from './user' diff --git a/apis/api-journeys-modern/src/schema/user/user.ts b/apis/api-journeys-modern/src/schema/user/user.ts index 30b98bee598..c8645a8c4dc 100644 --- a/apis/api-journeys-modern/src/schema/user/user.ts +++ b/apis/api-journeys-modern/src/schema/user/user.ts @@ -13,3 +13,17 @@ UserRef.implement({ // No additional fields needed - this is just the external reference }) }) + +// Define the federated AuthenticatedUser type reference - this should only be defined once +export const AuthenticatedUserRef = builder.externalRef( + 'AuthenticatedUser', + builder.selection<{ id: string }>('id') +) + +// Implement the external fields for the AuthenticatedUser type +AuthenticatedUserRef.implement({ + externalFields: (t) => ({ id: t.id({ nullable: false }) }), + fields: (t) => ({ + // No additional fields needed - this is just the external reference + }) +}) diff --git a/apis/api-journeys/schema.graphql b/apis/api-journeys/schema.graphql index d86f741d92c..245663c9b46 100644 --- a/apis/api-journeys/schema.graphql +++ b/apis/api-journeys/schema.graphql @@ -2739,6 +2739,12 @@ enum Role { publisher } +extend type AuthenticatedUser + @key(fields: "id") +{ + id: ID! @external +} + enum UserTeamRole { manager member @@ -3048,4 +3054,4 @@ type _Service { sdl: String } -union _Entity = Journey | JourneyProfile | JourneyVisitor | Language | ShortLink | Tag | Team | User | UserInvite | UserJourney | UserRole | UserTeam | Video | VideoBlock | Visitor +union _Entity = AuthenticatedUser | Journey | JourneyProfile | JourneyVisitor | Language | ShortLink | Tag | Team | User | UserInvite | UserJourney | UserRole | UserTeam | Video | VideoBlock | Visitor diff --git a/apis/api-journeys/src/__generated__/graphql.ts b/apis/api-journeys/src/__generated__/graphql.ts index 6ae8342f590..fce78ace97e 100644 --- a/apis/api-journeys/src/__generated__/graphql.ts +++ b/apis/api-journeys/src/__generated__/graphql.ts @@ -52,6 +52,17 @@ export type AudioPreview = { value: Scalars['String']['output']; }; +export type AuthenticatedUser = { + __typename?: 'AuthenticatedUser'; + email: Scalars['String']['output']; + emailVerified: Scalars['Boolean']['output']; + firstName: Scalars['String']['output']; + id: Scalars['ID']['output']; + imageUrl?: Maybe; + lastName?: Maybe; + superAdmin?: Maybe; +}; + export type BaseError = { message?: Maybe; }; @@ -1817,6 +1828,8 @@ export type Mutation = { deleteMuxVideo: Scalars['Boolean']['output']; enableMuxDownload?: Maybe; fixVideoLanguages: Scalars['Boolean']['output']; + /** Triggers a backfill of the Google Sheets sync. Clears existing data and re-exports all events. */ + googleSheetsSyncBackfill: GoogleSheetsSync; googleSheetsSyncCreate: GoogleSheetsSync; googleSheetsSyncDelete: GoogleSheetsSync; hostCreate: Host; @@ -2290,6 +2303,11 @@ export type MutationFixVideoLanguagesArgs = { }; +export type MutationGoogleSheetsSyncBackfillArgs = { + id: Scalars['ID']['input']; +}; + + export type MutationGoogleSheetsSyncCreateArgs = { input: CreateGoogleSheetsSyncInput; }; diff --git a/apis/api-journeys/src/app/__generated__/graphql.ts b/apis/api-journeys/src/app/__generated__/graphql.ts index 3a791603c5b..cb6a8c22013 100644 --- a/apis/api-journeys/src/app/__generated__/graphql.ts +++ b/apis/api-journeys/src/app/__generated__/graphql.ts @@ -2030,6 +2030,10 @@ export class User { id: string; } +export class AuthenticatedUser { + id: string; +} + export type DateTime = String; export type Json = any; diff --git a/apis/api-journeys/src/app/modules/userTeam/userTeam.graphql b/apis/api-journeys/src/app/modules/userTeam/userTeam.graphql index 296542f6398..698c1512a7c 100644 --- a/apis/api-journeys/src/app/modules/userTeam/userTeam.graphql +++ b/apis/api-journeys/src/app/modules/userTeam/userTeam.graphql @@ -2,6 +2,10 @@ extend type User @key(fields: "id") { id: ID! @external } +extend type AuthenticatedUser @key(fields: "id") { + id: ID! @external +} + enum UserTeamRole { manager member diff --git a/apis/api-languages/schema.graphql b/apis/api-languages/schema.graphql index cff25a5040e..f2a3bca05b6 100644 --- a/apis/api-languages/schema.graphql +++ b/apis/api-languages/schema.graphql @@ -13,6 +13,14 @@ type AudioPreview codec: String! } +type AuthenticatedUser + @key(fields: "id") + @extends +{ + id: ID! @external + languageUserRoles: [LanguageRole!]! +} + type Continent { id: ID! name(languageId: ID, primary: Boolean): [ContinentName!]! diff --git a/apis/api-languages/src/schema/user/user.ts b/apis/api-languages/src/schema/user/user.ts index 642944b581f..15647bc8580 100644 --- a/apis/api-languages/src/schema/user/user.ts +++ b/apis/api-languages/src/schema/user/user.ts @@ -24,3 +24,26 @@ builder.externalRef('User', builder.selection<{ id: string }>('id')).implement({ }) }) }) + +builder + .externalRef('AuthenticatedUser', builder.selection<{ id: string }>('id')) + .implement({ + externalFields: (t) => ({ + id: t.id({ nullable: false }) + }), + fields: (t) => ({ + languageUserRoles: t.field({ + type: [LanguageRole], + nullable: false, + resolve: async (data) => { + return ( + ( + await prisma.userLanguageRole.findUnique({ + where: { userId: data.id } + }) + )?.roles ?? [] + ) + } + }) + }) + }) diff --git a/apis/api-media/schema.graphql b/apis/api-media/schema.graphql index 5111a0c6174..9a25cb13871 100644 --- a/apis/api-media/schema.graphql +++ b/apis/api-media/schema.graphql @@ -9,6 +9,14 @@ type ArclightApiKey defaultPlatform: DefaultPlatform! } +type AuthenticatedUser + @key(fields: "id") + @extends +{ + id: ID! @external + mediaUserRoles: [MediaRole!]! +} + interface BaseError { message: String } diff --git a/apis/api-media/src/schema/user/index.ts b/apis/api-media/src/schema/user/index.ts index cacb05ea5af..f39da98322e 100644 --- a/apis/api-media/src/schema/user/index.ts +++ b/apis/api-media/src/schema/user/index.ts @@ -1,4 +1,4 @@ import './enums' import './user' -export { UserRef } from './user' +export { AuthenticatedUserRef, UserRef } from './user' diff --git a/apis/api-media/src/schema/user/user.ts b/apis/api-media/src/schema/user/user.ts index f27b1297abc..4722942b26a 100644 --- a/apis/api-media/src/schema/user/user.ts +++ b/apis/api-media/src/schema/user/user.ts @@ -29,3 +29,29 @@ UserRef.implement({ }) }) }) + +export const AuthenticatedUserRef = builder.externalRef( + 'AuthenticatedUser', + builder.selection<{ id: string }>('id') +) + +AuthenticatedUserRef.implement({ + externalFields: (t) => ({ + id: t.id({ nullable: false }) + }), + fields: (t) => ({ + mediaUserRoles: t.field({ + type: [MediaRole], + nullable: false, + resolve: async (data) => { + return ( + ( + await prisma.userMediaRole.findUnique({ + where: { id: data.id } + }) + )?.roles ?? [] + ) + } + }) + }) +}) diff --git a/libs/shared/gql/src/__generated__/graphql-env.d.ts b/libs/shared/gql/src/__generated__/graphql-env.d.ts index d25910967a2..e150d21351b 100644 --- a/libs/shared/gql/src/__generated__/graphql-env.d.ts +++ b/libs/shared/gql/src/__generated__/graphql-env.d.ts @@ -5,7 +5,7 @@ export type introspection_types = { 'Action': { kind: 'INTERFACE'; name: 'Action'; fields: { 'gtmEventName': { name: 'gtmEventName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'parentBlock': { name: 'parentBlock'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INTERFACE'; name: 'Block'; ofType: null; }; } }; 'parentBlockId': { name: 'parentBlockId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; }; possibleTypes: 'ChatAction' | 'EmailAction' | 'LinkAction' | 'NavigateToBlockAction' | 'PhoneAction'; }; 'ArclightApiKey': { kind: 'OBJECT'; name: 'ArclightApiKey'; fields: { 'defaultPlatform': { name: 'defaultPlatform'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'DefaultPlatform'; ofType: null; }; } }; 'desc': { name: 'desc'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'key': { name: 'key'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; 'AudioPreview': { kind: 'OBJECT'; name: 'AudioPreview'; fields: { 'bitrate': { name: 'bitrate'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'codec': { name: 'codec'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'duration': { name: 'duration'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'language': { name: 'language'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Language'; ofType: null; }; } }; 'languageId': { name: 'languageId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'size': { name: 'size'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'value': { name: 'value'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'AuthenticatedUser': { kind: 'OBJECT'; name: 'AuthenticatedUser'; fields: { 'email': { name: 'email'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'emailVerified': { name: 'emailVerified'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'firstName': { name: 'firstName'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'imageUrl': { name: 'imageUrl'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'lastName': { name: 'lastName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'superAdmin': { name: 'superAdmin'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; }; }; + 'AuthenticatedUser': { kind: 'OBJECT'; name: 'AuthenticatedUser'; fields: { 'email': { name: 'email'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'emailVerified': { name: 'emailVerified'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'firstName': { name: 'firstName'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'imageUrl': { name: 'imageUrl'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'languageUserRoles': { name: 'languageUserRoles'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'LanguageRole'; ofType: null; }; }; }; } }; 'lastName': { name: 'lastName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'mediaUserRoles': { name: 'mediaUserRoles'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'MediaRole'; ofType: null; }; }; }; } }; 'superAdmin': { name: 'superAdmin'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; }; }; 'BaseError': { kind: 'INTERFACE'; name: 'BaseError'; fields: { 'message': { name: 'message'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; possibleTypes: 'Error' | 'ForeignKeyConstraintError' | 'NotFoundError' | 'NotUniqueError' | 'ZodError'; }; 'BibleBook': { kind: 'OBJECT'; name: 'BibleBook'; fields: { 'alternateName': { name: 'alternateName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'isNewTestament': { name: 'isNewTestament'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BibleBookName'; ofType: null; }; }; }; } }; 'order': { name: 'order'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'osisId': { name: 'osisId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'paratextAbbreviation': { name: 'paratextAbbreviation'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; 'BibleBookName': { kind: 'OBJECT'; name: 'BibleBookName'; fields: { 'language': { name: 'language'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Language'; ofType: null; }; } }; 'primary': { name: 'primary'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'value': { name: 'value'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; };