Skip to content

Commit

Permalink
Merge pull request #393 from NDLANO/arena-csrf-hmm
Browse files Browse the repository at this point in the history
Add mutations to create new topic, and reply to topic
  • Loading branch information
jnatten authored Nov 22, 2023
2 parents 7de8a8a + f7b140c commit 69910dd
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 2 deletions.
64 changes: 64 additions & 0 deletions src/api/arenaApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
GQLArenaPost,
GQLArenaTopic,
GQLArenaUser,
GQLMutationNewArenaTopicArgs,
GQLMutationReplyToTopicArgs,
GQLQueryArenaCategoryArgs,
GQLQueryArenaTopicArgs,
GQLQueryArenaTopicsByUserArgs,
Expand Down Expand Up @@ -68,6 +70,31 @@ const toCategory = (category: any): GQLArenaCategory => {
};
};

export const fetchCsrfTokenForSession = async (
context: Context,
): Promise<{ cookie: string; 'x-csrf-token': string }> => {
const incomingCookie = context.req.headers.cookie;
const incomingCsrfToken = context.req.headers['x-csrf-token'];
if (incomingCookie !== undefined && typeof incomingCsrfToken === 'string') {
return { cookie: incomingCookie, 'x-csrf-token': incomingCsrfToken };
}

const response = await fetch('/groups/api/config', context);
const resolved: any = await resolveJson(response);
const responseCookie = response.headers.get('set-cookie');

if (!responseCookie)
throw new Error(
'Did not get set-cookie header from /groups/api/config endpoint to use together with csrf token.',
);

const token = resolved.csrf_token;
return {
'x-csrf-token': token,
cookie: responseCookie,
};
};

export const fetchArenaUser = async (
{ username }: GQLQueryArenaUserArgs,
context: Context,
Expand Down Expand Up @@ -128,3 +155,40 @@ export const fetchArenaTopicsByUser = async (
const resolved = await resolveJson(response);
return resolved.topics.map(toTopic);
};

export const newTopic = async (
{ title, content, categoryId }: GQLMutationNewArenaTopicArgs,
context: Context,
): Promise<GQLArenaTopic> => {
const csrfHeaders = await fetchCsrfTokenForSession(context);
const response = await fetch(`/groups/api/v3/topics`, context, {
method: 'POST',
headers: { 'content-type': 'application/json', ...csrfHeaders },
body: JSON.stringify({
cid: categoryId,
title,
content,
}),
});
const resolved = await resolveJson(response);
return toTopic(resolved.response);
};

export const replyToTopic = async (
{ topicId, content }: GQLMutationReplyToTopicArgs,
context: Context,
) => {
const csrfHeaders = await fetchCsrfTokenForSession(context);
const response = await fetch(`/groups/api/v3/topics/${topicId}`, context, {
method: 'POST',
headers: {
'content-type': 'application/json',
...csrfHeaders,
},
body: JSON.stringify({
content,
}),
});
const resolved = await resolveJson(response);
return toArenaPost(resolved.response);
};
25 changes: 25 additions & 0 deletions src/resolvers/arenaResolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import {
fetchArenaUser,
fetchArenaTopic,
fetchArenaTopicsByUser,
fetchCsrfTokenForSession,
newTopic,
replyToTopic,
} from '../api/arenaApi';
import {
GQLArenaCategory,
Expand All @@ -23,6 +26,8 @@ import {
GQLQueryArenaTopicArgs,
GQLQueryArenaTopicsByUserArgs,
GQLQueryResolvers,
GQLMutationResolvers,
GQLArenaPost,
} from '../types/schema';

export const Query: Pick<
Expand Down Expand Up @@ -77,3 +82,23 @@ export const Query: Pick<
return fetchArenaTopicsByUser(params, context);
},
};

export const Mutations: Pick<
GQLMutationResolvers,
'newArenaTopic' | 'replyToTopic'
> = {
async newArenaTopic(
_: any,
params,
context: ContextWithLoaders,
): Promise<GQLArenaTopic> {
return newTopic(params, context);
},
async replyToTopic(
_: any,
params,
context: ContextWithLoaders,
): Promise<GQLArenaPost> {
return replyToTopic(params, context);
},
};
6 changes: 5 additions & 1 deletion src/resolvers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ import {
resolvers as ProgrammeResolvers,
} from './programmeResolvers';

import { Query as ArenaQuery } from './arenaResolvers';
import {
Query as ArenaQuery,
Mutations as ArenaMutations,
} from './arenaResolvers';

export const resolvers = {
Query: {
Expand All @@ -93,6 +96,7 @@ export const resolvers = {
Mutation: {
...FolderMutations,
...TransformArticleMutations,
...ArenaMutations,
},
...folderResolvers,
...articleResolvers,
Expand Down
6 changes: 6 additions & 0 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1393,6 +1393,12 @@ export const typeDefs = gql`
draftConcept: Boolean
absoluteUrl: Boolean
): String!
newArenaTopic(
categoryId: Int!
title: String!
content: String!
): ArenaTopic!
replyToTopic(topicId: Int!, content: String!): ArenaPost!
}
`;

Expand Down
19 changes: 18 additions & 1 deletion src/types/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,8 @@ export type GQLMutation = {
deleteFolder: Scalars['String'];
deleteFolderResource: Scalars['String'];
deletePersonalData: Scalars['Boolean'];
newArenaTopic: GQLArenaTopic;
replyToTopic: GQLArenaPost;
sortFolders: GQLSortResult;
sortResources: GQLSortResult;
transformArticleContent: Scalars['String'];
Expand Down Expand Up @@ -920,6 +922,19 @@ export type GQLMutationDeleteFolderResourceArgs = {
};


export type GQLMutationNewArenaTopicArgs = {
categoryId: Scalars['Int'];
content: Scalars['String'];
title: Scalars['String'];
};


export type GQLMutationReplyToTopicArgs = {
content: Scalars['String'];
topicId: Scalars['Int'];
};


export type GQLMutationSortFoldersArgs = {
parentId?: InputMaybe<Scalars['String']>;
sortedIds: Array<Scalars['String']>;
Expand Down Expand Up @@ -2221,7 +2236,7 @@ export type GQLArenaTopicResolvers<ContextType = any, ParentType extends GQLReso

export type GQLArenaUserResolvers<ContextType = any, ParentType extends GQLResolversParentTypes['ArenaUser'] = GQLResolversParentTypes['ArenaUser']> = {
displayName?: Resolver<GQLResolversTypes['String'], ParentType, ContextType>;
groupTitleArray?: Resolver<Maybe<Array<Maybe<GQLResolversTypes['String']>>>, ParentType, ContextType>;
groupTitleArray?: Resolver<Array<GQLResolversTypes['String']>, ParentType, ContextType>;
id?: Resolver<GQLResolversTypes['Int'], ParentType, ContextType>;
profilePicture?: Resolver<Maybe<GQLResolversTypes['String']>, ParentType, ContextType>;
slug?: Resolver<GQLResolversTypes['String'], ParentType, ContextType>;
Expand Down Expand Up @@ -3018,6 +3033,8 @@ export type GQLMutationResolvers<ContextType = any, ParentType extends GQLResolv
deleteFolder?: Resolver<GQLResolversTypes['String'], ParentType, ContextType, RequireFields<GQLMutationDeleteFolderArgs, 'id'>>;
deleteFolderResource?: Resolver<GQLResolversTypes['String'], ParentType, ContextType, RequireFields<GQLMutationDeleteFolderResourceArgs, 'folderId' | 'resourceId'>>;
deletePersonalData?: Resolver<GQLResolversTypes['Boolean'], ParentType, ContextType>;
newArenaTopic?: Resolver<GQLResolversTypes['ArenaTopic'], ParentType, ContextType, RequireFields<GQLMutationNewArenaTopicArgs, 'categoryId' | 'content' | 'title'>>;
replyToTopic?: Resolver<GQLResolversTypes['ArenaPost'], ParentType, ContextType, RequireFields<GQLMutationReplyToTopicArgs, 'content' | 'topicId'>>;
sortFolders?: Resolver<GQLResolversTypes['SortResult'], ParentType, ContextType, RequireFields<GQLMutationSortFoldersArgs, 'sortedIds'>>;
sortResources?: Resolver<GQLResolversTypes['SortResult'], ParentType, ContextType, RequireFields<GQLMutationSortResourcesArgs, 'parentId' | 'sortedIds'>>;
transformArticleContent?: Resolver<GQLResolversTypes['String'], ParentType, ContextType, RequireFields<GQLMutationTransformArticleContentArgs, 'content'>>;
Expand Down

0 comments on commit 69910dd

Please sign in to comment.