diff --git a/apps/backend/src/api/custom-post/controllers/custom-post.js b/apps/backend/src/api/custom-post/controllers/custom-post.js deleted file mode 100644 index b820de442..000000000 --- a/apps/backend/src/api/custom-post/controllers/custom-post.js +++ /dev/null @@ -1,54 +0,0 @@ -"use strict"; - -/** - * A set of functions called "actions" for `custom-post` - */ - -// Allow query parameter to be "authors" to comply with the old API and convert -// it to "author" to use for populate option. Allow only "authors" and "tags" -const sanitizePopulate = (includeQuery) => { - const expectedValues = ["authors", "tags"]; - const input = includeQuery?.split(",") || []; - const sanitizedInput = input - .filter((item) => expectedValues.includes(item)) - .map((item) => (item === "authors" ? "author" : item)); - - // remove duplicates - return [...new Set(sanitizedInput)]; -}; - -module.exports = { - find: async (ctx) => { - try { - const populate = sanitizePopulate(ctx.request.query.include); - const response = await strapi - .service("api::custom-post.custom-post") - .find(populate); - ctx.body = response; - } catch (err) { - ctx.body = err; - } - }, - findOne: async (ctx) => { - try { - const populate = sanitizePopulate(ctx.request.query.include); - const response = await strapi - .service("api::custom-post.custom-post") - .findOne(ctx.request.params.id, populate); - ctx.body = response; - } catch (err) { - ctx.body = err; - } - }, - findOneBySlug: async (ctx) => { - try { - const populate = sanitizePopulate(ctx.request.query.include); - const response = await strapi - .service("api::custom-post.custom-post") - .findOneBySlug(ctx.request.params.slug, populate); - ctx.body = response; - } catch (err) { - ctx.body = err; - } - }, -}; diff --git a/apps/backend/src/api/custom-post/routes/custom-post.js b/apps/backend/src/api/custom-post/routes/custom-post.js deleted file mode 100644 index bbcb7cab7..000000000 --- a/apps/backend/src/api/custom-post/routes/custom-post.js +++ /dev/null @@ -1,31 +0,0 @@ -module.exports = { - routes: [ - { - method: "GET", - path: "/content/posts", - handler: "custom-post.find", - config: { - policies: [], - middlewares: [], - }, - }, - { - method: "GET", - path: "/content/posts/:id", - handler: "custom-post.findOne", - config: { - policies: [], - middlewares: [], - }, - }, - { - method: "GET", - path: "/content/posts/slug/:slug", - handler: "custom-post.findOneBySlug", - config: { - policies: [], - middlewares: [], - }, - }, - ], -}; diff --git a/apps/backend/src/api/custom-post/services/custom-post.js b/apps/backend/src/api/custom-post/services/custom-post.js deleted file mode 100644 index 069619169..000000000 --- a/apps/backend/src/api/custom-post/services/custom-post.js +++ /dev/null @@ -1,113 +0,0 @@ -const { createCoreService } = require("@strapi/strapi").factories; - -// format data structure to match the old API -// and remove attributes we don't want to include in the response -const formatAuthor = (authorObj = {}) => { - const { - createdAt, - updatedAt, - // Exclude the attributes below from the response - /* eslint-disable no-unused-vars */ - username, - email, - provider, - password, - resetPasswordToken, - confirmationToken, - confirmed, - blocked, - /* eslint-enable no-unused-vars */ - // Exclude the attributes above from the response - ...rest - } = authorObj; - - return { - ...rest, - created_at: createdAt, - updated_at: updatedAt, - }; -}; - -// format data structure to match the old API -const formatTag = (tagObj = {}) => { - const { createdAt, updatedAt, ...rest } = tagObj; - - return { - ...rest, - created_at: createdAt, - updated_at: updatedAt, - }; -}; - -// format data structure to match the old API -const formatPost = (postObj) => { - const { createdAt, updatedAt, publishedAt, author, tags, ...rest } = postObj; - - let formattedPost = { - ...rest, - created_at: createdAt, - updated_at: updatedAt, - published_at: publishedAt, - }; - - if (author) { - const formattedAuthor = formatAuthor(author); - // Note: with current model relation, there is only 1 author for a post - // but to comply with the format of the old API we are putting it in an array - formattedPost.authors = [formattedAuthor]; - } - - if (tags) { - const formattedTags = tags.map((tag) => formatTag(tag)); - formattedPost.tags = formattedTags; - } - - return formattedPost; -}; - -module.exports = createCoreService("api::post.post", ({ strapi }) => ({ - async find(populate = []) { - const posts = await strapi.entityService.findMany("api::post.post", { - publicationState: "live", // only published posts - sort: { publishedAt: "desc" }, - populate: populate, - }); - - // rename object keys - const formattedPosts = posts.map((post) => formatPost(post)); - - // format data into desired structure - return { posts: formattedPosts }; - }, - - async findOne(id, populate = []) { - const post = await strapi.entityService.findOne("api::post.post", id, { - publicationState: "live", // only published posts - populate: populate, - }); - - // rename object keys - const formattedPost = formatPost(post); - - // format data into desired structure - const response = { posts: [formattedPost] }; - return response; - }, - - async findOneBySlug(slug, populate = []) { - const posts = await strapi.entityService.findMany("api::post.post", { - publicationState: "live", // only published posts - filters: { - slug: slug, - }, - populate: populate, - }); - - // rename object keys - const formattedPosts = posts.map((post) => formatPost(post)); - - // format data into desired structure - const response = { posts: formattedPosts }; - return response; - }, -})); diff --git a/apps/backend/tests/app.test.js b/apps/backend/tests/app.test.js index b4f93de0c..19a67239f 100644 --- a/apps/backend/tests/app.test.js +++ b/apps/backend/tests/app.test.js @@ -27,5 +27,4 @@ it("strapi is defined", () => { require("./user"); require("./auth"); -require("./custom-post"); require("./post"); diff --git a/apps/backend/tests/custom-post/index.js b/apps/backend/tests/custom-post/index.js deleted file mode 100644 index 9e0d7c5a3..000000000 --- a/apps/backend/tests/custom-post/index.js +++ /dev/null @@ -1,242 +0,0 @@ -const request = require("supertest"); - -const expectedPost = { - id: 1, - title: "Test Title", - slug: "test-slug", - body: "
test body
", - excerpt: null, - // TODO: should we have this? - // locale: "en", - published_at: "2023-08-30T00:00:00.000Z", - created_at: expect.any(String), - updated_at: expect.any(String), - codeinjection_head: null, - codeinjection_foot: null, - ghost_id: null, - scheduled_at: null, - slug_id: expect.stringMatching(/[a-z0-9]{8}/), -}; - -const expectedAuthor = { - slug: "contributor-user", - name: "Contributor User", - bio: null, - website: null, - location: null, - facebook: null, - twitter: null, - status: "active", - last_seen: null, - created_at: expect.any(String), - updated_at: expect.any(String), -}; - -// Author properties that should not be in the API response -const hiddenAuthorKeys = [ - "username", - "email", - "provider", - "password", - "resetPasswordToken", - "confirmationToken", - "confirmed", - "blocked", -]; - -const expectedTag = { - slug: "html", - name: "HTML", - visibility: "public", - created_at: expect.any(String), - updated_at: expect.any(String), -}; - -const findPostByTitle = (posts, title) => { - return posts.find((post) => post.title === title); -}; - -// Configure API token for test environment -let token; -const getAPIToken = async () => { - const tokenService = strapi.service("admin::api-token"); - - const attributes = { - name: `test token`, - description: "", - type: "custom", - lifespan: null, - permissions: [ - "api::custom-post.custom-post.find", - "api::custom-post.custom-post.findOne", - "api::custom-post.custom-post.findOneBySlug", - ], - }; - - const apiToken = await tokenService.create(attributes); - return apiToken.accessKey; -}; - -describe("custom-post", () => { - beforeAll(async () => { - token = await getAPIToken(); - }); - - describe("GET /content/posts", () => { - it("should return posts", async () => { - const response = await request(strapi.server.httpServer) - .get("/api/content/posts") - .set("accept", "application/json") - .set("Authorization", `Bearer ${token}`); - - // Should return all posts - expect(response.status).toBe(200); - expect(response.body.posts.length).toBe(2); - - // Check post data structure - const responsePost = findPostByTitle( - response.body.posts, - expectedPost.title, - ); - expect(responsePost).toEqual(expect.objectContaining(expectedPost)); - - // Should not include authors and tags - expect(responsePost).not.toHaveProperty("authors"); - expect(responsePost).not.toHaveProperty("tags"); - }); - - it("should return posts with author and tags", async () => { - const response = await request(strapi.server.httpServer) - .get("/api/content/posts?include=authors,tags") - .set("accept", "application/json") - .set("Authorization", `Bearer ${token}`); - - // Should return all posts - expect(response.status).toBe(200); - expect(response.body.posts.length).toBe(2); - - // Check post data structure - const responsePost = findPostByTitle( - response.body.posts, - expectedPost.title, - ); - expect(responsePost).toEqual(expect.objectContaining(expectedPost)); - - // Check author and tag data structure - expect(responsePost.authors[0]).toEqual( - expect.objectContaining(expectedAuthor), - ); - expect(responsePost.authors[0]).not.toHaveProperty(hiddenAuthorKeys); - expect(responsePost.tags[0]).toEqual( - expect.objectContaining(expectedTag), - ); - }); - }); - - describe("GET /content/posts/:id", () => { - it("should return post", async () => { - const response = await request(strapi.server.httpServer) - .get(`/api/content/posts/${expectedPost.id}`) - .set("accept", "application/json") - .set("Authorization", `Bearer ${token}`); - - // Should return one single post - expect(response.status).toBe(200); - expect(response.body.posts.length).toBe(1); - - // Check post data structure - const responsePost = response.body.posts[0]; - expect(responsePost).toEqual(expect.objectContaining(expectedPost)); - - // Should not include authors and tags - expect(responsePost).not.toHaveProperty("authors"); - expect(responsePost).not.toHaveProperty("tags"); - }); - - it("should return post with authors and tags", async () => { - const response = await request(strapi.server.httpServer) - .get(`/api/content/posts/${expectedPost.id}?include=authors,tags`) - .set("accept", "application/json") - .set("Authorization", `Bearer ${token}`); - - // Should return one single post - expect(response.status).toBe(200); - expect(response.body.posts.length).toBe(1); - - // Check post data structure - const responsePost = response.body.posts[0]; - expect(responsePost).toEqual(expect.objectContaining(expectedPost)); - - // Check author and tag data structure - expect(responsePost.authors[0]).toEqual( - expect.objectContaining(expectedAuthor), - ); - expect(responsePost.authors[0]).not.toHaveProperty(hiddenAuthorKeys); - expect(responsePost.tags[0]).toEqual( - expect.objectContaining(expectedTag), - ); - }); - }); - - describe("GET /content/posts/slug/:slug", () => { - it("should return post", async () => { - const response = await request(strapi.server.httpServer) - .get(`/api/content/posts/slug/${expectedPost.slug}`) - .set("accept", "application/json") - .set("Authorization", `Bearer ${token}`); - - // Should return one single post - expect(response.status).toBe(200); - expect(response.body.posts.length).toBe(1); - - // Check post data structure - const responsePost = response.body.posts[0]; - expect(responsePost).toEqual(expect.objectContaining(expectedPost)); - - // Should not include authors and tags - expect(responsePost).not.toHaveProperty("authors"); - expect(responsePost).not.toHaveProperty("tags"); - }); - - it("should return post with authors and tags", async () => { - const response = await request(strapi.server.httpServer) - .get( - `/api/content/posts/slug/${expectedPost.slug}?include=authors,tags`, - ) - .set("accept", "application/json") - .set("Authorization", `Bearer ${token}`); - - // Should return one single post - expect(response.status).toBe(200); - expect(response.body.posts.length).toBe(1); - - // Check post data structure - const responsePost = response.body.posts[0]; - expect(responsePost).toEqual(expect.objectContaining(expectedPost)); - - // Check author and tag data structure - expect(responsePost.authors[0]).toEqual( - expect.objectContaining(expectedAuthor), - ); - expect(responsePost.authors[0]).not.toHaveProperty(hiddenAuthorKeys); - expect(responsePost.tags[0]).toEqual( - expect.objectContaining(expectedTag), - ); - }); - - it("should escape unsafe include value", async () => { - // Strapi's default `*` query should not be effective here - const response = await request(strapi.server.httpServer) - .get(`/api/content/posts/slug/${expectedPost.slug}?include=*`) - .set("accept", "application/json") - .set("Authorization", `Bearer ${token}`); - - expect(response.status).toBe(200); - const responsePost = response.body.posts[0]; - expect(responsePost).toEqual(expect.objectContaining(expectedPost)); - - // include=* should not return any relations - expect(responsePost).not.toHaveProperty("authors"); - }); - }); -});