aboutsummaryrefslogtreecommitdiffstats
path: root/src/services
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-05-02 12:55:13 +0200
committerArmand Philippot <git@armandphilippot.com>2022-05-02 15:09:36 +0200
commit9308a6dce03bd0c616e0ba6fec227473aaa44b33 (patch)
treea5b9232b6e15b298316383c13d94cd431d078a13 /src/services
parenta208a8f314f697dbd6f85f8be8332bcea0204178 (diff)
refactor: rewrite API fetching method and GraphQL queries
Diffstat (limited to 'src/services')
-rw-r--r--src/services/graphql/api.ts222
-rw-r--r--src/services/graphql/articles.query.ts174
-rw-r--r--src/services/graphql/comments.query.ts21
-rw-r--r--src/services/graphql/thematics.query.ts116
-rw-r--r--src/services/graphql/topics.query.ts117
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
+ }
+ }
+}`;