summaryrefslogtreecommitdiffstats
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
parenta208a8f314f697dbd6f85f8be8332bcea0204178 (diff)
refactor: rewrite API fetching method and GraphQL queries
-rw-r--r--package.json1
-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
-rw-r--r--yarn.lock23
7 files changed, 651 insertions, 23 deletions
diff --git a/package.json b/package.json
index 87c7596..5cec983 100644
--- a/package.json
+++ b/package.json
@@ -44,7 +44,6 @@
"@next/mdx": "^12.1.5",
"feed": "^4.2.2",
"graphql": "^16.1.0",
- "graphql-request": "^4.2.0",
"modern-normalize": "^1.1.0",
"next": "^12.1.5",
"next-sitemap": "^2.5.20",
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
+ }
+ }
+}`;
diff --git a/yarn.lock b/yarn.lock
index db10eae..31bd5e5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6310,13 +6310,6 @@ create-require@^1.1.0:
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
-cross-fetch@^3.1.5:
- version "3.1.5"
- resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f"
- integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==
- dependencies:
- node-fetch "2.6.7"
-
cross-spawn@^6.0.0:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@@ -7617,11 +7610,6 @@ extglob@^2.0.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
-extract-files@^9.0.0:
- version "9.0.0"
- resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a"
- integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ==
-
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@@ -8330,15 +8318,6 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
-graphql-request@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-4.2.0.tgz#063377bc2dd29cc46aed3fddcc65fe97b805ba81"
- integrity sha512-uFeMyhhl8ss4LFgjlfPeAn2pqYw+CJto+cjj71uaBYIMMK2jPIqgHm5KEFxUk0YDD41A8Bq31a2b4G2WJBlp2Q==
- dependencies:
- cross-fetch "^3.1.5"
- extract-files "^9.0.0"
- form-data "^3.0.0"
-
graphql@^16.1.0:
version "16.3.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.3.0.tgz#a91e24d10babf9e60c706919bb182b53ccdffc05"
@@ -11363,7 +11342,7 @@ node-dir@^0.1.10:
dependencies:
minimatch "^3.0.2"
-node-fetch@2.6.7, node-fetch@^2.6.1:
+node-fetch@^2.6.1:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==