diff options
| author | Armand Philippot <git@armandphilippot.com> | 2022-05-02 12:55:13 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2022-05-02 15:09:36 +0200 |
| commit | 9308a6dce03bd0c616e0ba6fec227473aaa44b33 (patch) | |
| tree | a5b9232b6e15b298316383c13d94cd431d078a13 /src/services | |
| parent | a208a8f314f697dbd6f85f8be8332bcea0204178 (diff) | |
refactor: rewrite API fetching method and GraphQL queries
Diffstat (limited to 'src/services')
| -rw-r--r-- | src/services/graphql/api.ts | 222 | ||||
| -rw-r--r-- | src/services/graphql/articles.query.ts | 174 | ||||
| -rw-r--r-- | src/services/graphql/comments.query.ts | 21 | ||||
| -rw-r--r-- | src/services/graphql/thematics.query.ts | 116 | ||||
| -rw-r--r-- | src/services/graphql/topics.query.ts | 117 |
5 files changed, 650 insertions, 0 deletions
diff --git a/src/services/graphql/api.ts b/src/services/graphql/api.ts new file mode 100644 index 0000000..b0e8d3a --- /dev/null +++ b/src/services/graphql/api.ts @@ -0,0 +1,222 @@ +import { settings } from '@utils/config'; +import { + articleBySlugQuery, + articlesCardQuery, + articlesQuery, + articlesSlugQuery, + totalArticlesQuery, +} from './articles.query'; +import { commentsQuery } from './comments.query'; +import { + thematicBySlugQuery, + thematicsListQuery, + thematicsSlugQuery, +} from './thematics.query'; +import { + topicBySlugQuery, + topicsListQuery, + topicsSlugQuery, +} from './topics.query'; + +export type Queries = + | typeof articlesQuery + | typeof articleBySlugQuery + | typeof articlesCardQuery + | typeof articlesSlugQuery + | typeof commentsQuery + | typeof thematicBySlugQuery + | typeof thematicsListQuery + | typeof thematicsSlugQuery + | typeof topicBySlugQuery + | typeof topicsListQuery + | typeof topicsSlugQuery + | typeof totalArticlesQuery; + +export type ArticleResponse<T> = { + post: T; +}; + +export type ArticlesResponse<T> = { + posts: T; +}; + +export type CommentsResponse<T> = { + comments: T[]; +}; + +export type ThematicResponse<T> = { + thematic: T; +}; + +export type ThematicsResponse<T> = { + thematics: T; +}; + +export type TopicResponse<T> = { + topic: T; +}; + +export type TopicsResponse<T> = { + topics: T; +}; + +export type PageInfo = { + endCursor: string; + hasNextPage: boolean; + total: number; +}; + +export type Edges<T> = { + cursor: string; + node: T; +}; + +export type EdgesResponse<T> = { + edges: Edges<T>[]; + pageInfo: PageInfo; +}; + +export type NodeResponse<T> = { + node: T; +}; + +export type NodesResponse<T> = { + nodes: T[]; +}; + +export type ResponseMap<T> = { + [articleBySlugQuery]: ArticleResponse<NodesResponse<T>>; + [articlesCardQuery]: ArticlesResponse<NodesResponse<T>>; + [articlesQuery]: ArticlesResponse<EdgesResponse<T>>; + [articlesSlugQuery]: ArticlesResponse<EdgesResponse<T>>; + [commentsQuery]: CommentsResponse<NodesResponse<T>>; + [thematicBySlugQuery]: ThematicResponse<NodesResponse<T>>; + [thematicsListQuery]: ThematicsResponse<EdgesResponse<T>>; + [thematicsSlugQuery]: ThematicsResponse<EdgesResponse<T>>; + [topicBySlugQuery]: TopicResponse<NodesResponse<T>>; + [topicsListQuery]: TopicsResponse<EdgesResponse<T>>; + [topicsSlugQuery]: TopicsResponse<EdgesResponse<T>>; + [totalArticlesQuery]: ArticlesResponse<T>; +}; + +export type GraphQLResponse< + T extends keyof ResponseMap<U>, + U +> = ResponseMap<U>[T]; + +export type BySlugVar = { + /** + * A slug. + */ + slug: string; +}; + +export type EdgesVars = { + /** + * A cursor. + */ + after?: string; + /** + * The number of items to return. + */ + first: number; + /** + * A search query. + */ + search?: string; +}; + +export type ByContentIdVar = { + /** + * An article id. + */ + contentId: number; +}; + +export type VariablesMap = { + [articleBySlugQuery]: BySlugVar; + [articlesCardQuery]: EdgesVars; + [articlesQuery]: EdgesVars; + [articlesSlugQuery]: EdgesVars; + [commentsQuery]: ByContentIdVar; + [thematicBySlugQuery]: BySlugVar; + [thematicsListQuery]: EdgesVars; + [thematicsSlugQuery]: EdgesVars; + [topicBySlugQuery]: BySlugVar; + [topicsListQuery]: EdgesVars; + [topicsSlugQuery]: EdgesVars; + [totalArticlesQuery]: null; +}; + +export type FetchAPIProps<T extends Queries> = { + /** + * A GraphQL API URL. + */ + api: string; + /** + * A GraphQL query. + */ + query: T; + /** + * (Optional) The query variables. + */ + variables?: VariablesMap[T]; +}; + +/** + * Fetch a GraphQL API. + * @param {object} obj - An object. + * @param {string} obj.api - A GraphQL API URL. + * @param {Queries} obj.query - A GraphQL query. + * @param {object} [obj.variables] - The query variables. + */ +export async function fetchAPI<T, U extends Queries>({ + api, + query, + variables, +}: FetchAPIProps<U>): Promise<GraphQLResponse<U, T>> { + const response = await fetch(api, { + method: 'POST', + headers: { + 'content-type': 'application/json;charset=UTF-8', + }, + body: JSON.stringify({ + query, + variables, + }), + }); + + type JSONResponse = { + data?: GraphQLResponse<U, T>; + errors?: Array<{ message: string }>; + }; + + const { data, errors }: JSONResponse = await response.json(); + + if (response.ok) { + if (!data) return Promise.reject(new Error(`No data found"`)); + + return data; + } else { + console.error('Failed to fetch API'); + const error = new Error( + errors?.map((e) => e.message).join('\n') ?? 'unknown' + ); + return Promise.reject(error); + } +} + +/** + * Retrieve the API url from settings. + * + * @returns {string} The API url. + */ +export const getAPIUrl = (): string => { + const { url } = settings.api; + + if (!url) { + throw new Error('API url is not defined.'); + } + + return url; +}; diff --git a/src/services/graphql/articles.query.ts b/src/services/graphql/articles.query.ts new file mode 100644 index 0000000..e384aba --- /dev/null +++ b/src/services/graphql/articles.query.ts @@ -0,0 +1,174 @@ +/** + * Query the full article data using its slug. + */ +export const articleBySlugQuery = `query PostBy($slug: ID!) { + post(id: $slug, idType: SLUG) { + acfPosts { + postsInThematic { + ... on Thematic { + databaseId + slug + title + } + } + postsInTopic { + ... on Topic { + databaseId + slug + title + } + } + } + author { + node { + gravatarUrl + name + url + } + } + commentCount + contentParts { + afterMore + beforeMore + } + databaseId + date + featuredImage { + node { + altText + mediaDetails { + height + width + } + sourceUrl + title + } + } + info { + readingTime + wordsCount + } + modified + seo { + metaDesc + title + } + slug + title + } +}`; + +/** + * Query an array of partial articles. + */ +export const articlesQuery = `query Articles($after: String = "", $first: Int = 10, $search: String = "") { + posts( + after: $after + first: $first + where: {orderby: {field: DATE, order: DESC}, search: $search, status: PUBLISH} + ) { + edges { + cursor + node { + acfPosts { + postsInThematic { + ... on Thematic { + databaseId + slug + title + } + } + } + author { + node { + name + } + } + commentCount + contentParts { + beforeMore + } + databaseId + featuredImage { + node { + altText + mediaDetails { + height + width + } + sourceUrl + title + } + } + info { + readingTime + wordsCount + } + modified + slug + title + } + } + pageInfo { + endCursor + hasNextPage + total + } + } +}`; + +/** + * Query an array of articles with only the minimal data. + */ +export const articlesCardQuery = `query ArticlesCard($first: Int = 10) { + posts( + first: $first + where: {orderby: {field: DATE, order: DESC}, status: PUBLISH} + ) { + nodes { + databaseId + date + featuredImage { + node { + altText + mediaDetails { + height + width + } + sourceUrl + title + } + } + slug + title + } + } +}`; + +/** + * Query an array of articles slug. + */ +export const articlesSlugQuery = `query ArticlesSlug($first: Int = 10, $after: String = "") { + posts(after: $after, first: $first) { + edges { + cursor + node { + slug + } + } + pageInfo { + total + } + } +}`; + +/** + * Query the total number of articles. + */ +export const totalArticlesQuery = `query PostsTotal { + posts { + pageInfo { + total + } + } +}`; diff --git a/src/services/graphql/comments.query.ts b/src/services/graphql/comments.query.ts new file mode 100644 index 0000000..ef93e89 --- /dev/null +++ b/src/services/graphql/comments.query.ts @@ -0,0 +1,21 @@ +/** + * Query the comments data by post id. + */ +export const commentsQuery = `query CommentsByPostId($contentId: ID!) { + comments(where: {contentId: $contentId, order: ASC, orderby: COMMENT_DATE}) { + nodes { + approved + author { + node { + gravatarUrl + name + url + } + } + content + databaseId + date + parentDatabaseId + } + } +}`; diff --git a/src/services/graphql/thematics.query.ts b/src/services/graphql/thematics.query.ts new file mode 100644 index 0000000..76949ad --- /dev/null +++ b/src/services/graphql/thematics.query.ts @@ -0,0 +1,116 @@ +/** + * Query the full thematic data using its slug. + */ +export const thematicBySlugQuery = `query ThematicBy($slug: ID!) { + thematic(id: $slug, idType: SLUG) { + acfThematics { + postsInThematic { + ... on Post { + acfPosts { + postsInTopic { + ... on Topic { + databaseId + slug + title + } + } + } + commentCount + contentParts { + beforeMore + } + databaseId + date + featuredImage { + node { + altText + mediaDetails { + height + width + } + sourceUrl + title + } + } + info { + readingTime + wordsCount + } + modified + slug + title + } + } + } + contentParts { + afterMore + beforeMore + } + databaseId + date + featuredImage { + node { + altText + mediaDetails { + height + width + } + sourceUrl + title + } + } + info { + readingTime + wordsCount + } + modified + seo { + metaDesc + title + } + slug + title + } +}`; + +/** + * Query an array of partial thematics. + */ +export const thematicsListQuery = `query ThematicsList($after: String = "", $first: Int = 10) { + thematics( + after: $after + first: $first + where: {orderby: {field: TITLE, order: ASC}, status: PUBLISH} + ) { + edges { + cursor + node { + databaseId + slug + title + } + } + pageInfo { + endCursor + hasNextPage + total + } + } +}`; + +/** + * Query an array of thematics slug. + */ +export const thematicsSlugQuery = `query ThematicsSlug($first: Int = 10, $after: String = "") { + thematics(after: $after, first: $first) { + edges { + cursor + node { + slug + } + } + pageInfo { + total + } + } +}`; diff --git a/src/services/graphql/topics.query.ts b/src/services/graphql/topics.query.ts new file mode 100644 index 0000000..8783799 --- /dev/null +++ b/src/services/graphql/topics.query.ts @@ -0,0 +1,117 @@ +/** + * Query the full topic data using its slug. + */ +export const topicBySlugQuery = `query TopicBy($slug: ID!) { + topic(id: $slug, idType: SLUG) { + acfTopics { + officialWebsite + postsInTopic { + ... on Post { + acfPosts { + postsInThematic { + ... on Thematic { + databaseId + slug + title + } + } + } + commentCount + contentParts { + beforeMore + } + databaseId + date + featuredImage { + node { + altText + mediaDetails { + height + width + } + sourceUrl + title + } + } + info { + readingTime + wordsCount + } + modified + slug + title + } + } + } + contentParts { + afterMore + beforeMore + } + databaseId + date + featuredImage { + node { + altText + mediaDetails { + height + width + } + sourceUrl + title + } + } + info { + readingTime + wordsCount + } + modified + seo { + metaDesc + title + } + slug + title + } +}`; + +/** + * Query an array of partial topics. + */ +export const topicsListQuery = `query TopicsList($after: String = "", $first: Int = 10) { + topics( + after: $after + first: $first + where: {orderby: {field: TITLE, order: ASC}, status: PUBLISH} + ) { + edges { + cursor + node { + databaseId + slug + title + } + } + pageInfo { + endCursor + hasNextPage + total + } + } +}`; + +/** + * Query an array of topics slug. + */ +export const topicsSlugQuery = `query TopicsSlug($first: Int = 10, $after: String = "") { + topics(after: $after, first: $first) { + edges { + cursor + node { + slug + } + } + pageInfo { + total + } + } +}`; |
