From aafa26ceb087d2f86962ab32a9994ac7b08ba21d Mon Sep 17 00:00:00 2001 From: decobot Date: Fri, 13 Sep 2024 10:00:38 -0300 Subject: [PATCH 01/20] feat(blogreaction): setup started --- .../extensions/BlogposList/reactions.ts | 24 ++++++++++++++++++ blog/loaders/extensions/BlogpostList.ts | 16 ++++++++++++ blog/loaders/extensions/BlogpostListing.ts | 16 ++++++++++++ .../extensions/BlogpostListing/reactions.ts | 25 +++++++++++++++++++ blog/loaders/extensions/BlogpostPage.ts | 16 ++++++++++++ .../extensions/BlogpostPage/reactions.ts | 25 +++++++++++++++++++ blog/manifest.gen.ts | 12 +++++++++ blog/types.ts | 11 +++++++- blog/utils/constants.ts | 12 +++++++++ wake/loaders/productListingPage.ts | 2 +- 10 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 blog/loaders/extensions/BlogposList/reactions.ts create mode 100644 blog/loaders/extensions/BlogpostList.ts create mode 100644 blog/loaders/extensions/BlogpostListing.ts create mode 100644 blog/loaders/extensions/BlogpostListing/reactions.ts create mode 100644 blog/loaders/extensions/BlogpostPage.ts create mode 100644 blog/loaders/extensions/BlogpostPage/reactions.ts diff --git a/blog/loaders/extensions/BlogposList/reactions.ts b/blog/loaders/extensions/BlogposList/reactions.ts new file mode 100644 index 000000000..fc1717ea0 --- /dev/null +++ b/blog/loaders/extensions/BlogposList/reactions.ts @@ -0,0 +1,24 @@ +import { ExtensionOf } from "../../../../website/loaders/extension.ts"; +import { AppContext } from "../../../mod.ts"; +import { BlogPost } from "../../../types.ts"; +import { REACTIONS_MOCK } from "../../../utils/constants.ts"; + +/** @title ExtensionOf BlogPost list: Reactions */ +export default function reactionsExt( + _props: unknown, + _req: Request, + _ctx: AppContext, +): ExtensionOf { + return (posts: BlogPost[] | null) => { + if (posts?.length === 0 || !posts) { + return null; + } + + const extendedPosts = posts?.map((post) => ({ + ...post, + reactions: REACTIONS_MOCK, + })); + + return extendedPosts; + }; +} diff --git a/blog/loaders/extensions/BlogpostList.ts b/blog/loaders/extensions/BlogpostList.ts new file mode 100644 index 000000000..0e013e29a --- /dev/null +++ b/blog/loaders/extensions/BlogpostList.ts @@ -0,0 +1,16 @@ +import { + default as extend, + Props, +} from "../../../website/loaders/extension.ts"; +import { BlogPost } from "../../types.ts"; + +/** + * @title Extend your Blogpost List + */ +export default function ProductDetailsExt( + props: Props, +): Promise { + return extend(props); +} + +export const cache = "no-cache"; diff --git a/blog/loaders/extensions/BlogpostListing.ts b/blog/loaders/extensions/BlogpostListing.ts new file mode 100644 index 000000000..741b0705a --- /dev/null +++ b/blog/loaders/extensions/BlogpostListing.ts @@ -0,0 +1,16 @@ +import { + default as extend, + Props, +} from "../../../website/loaders/extension.ts"; +import { BlogPostListingPage } from "../../types.ts"; + +/** + * @title Extend your Blogpost Listing Page + */ +export default function ProductDetailsExt( + props: Props, +): Promise { + return extend(props); +} + +export const cache = "no-cache"; diff --git a/blog/loaders/extensions/BlogpostListing/reactions.ts b/blog/loaders/extensions/BlogpostListing/reactions.ts new file mode 100644 index 000000000..492d75823 --- /dev/null +++ b/blog/loaders/extensions/BlogpostListing/reactions.ts @@ -0,0 +1,25 @@ +import { ExtensionOf } from "../../../../website/loaders/extension.ts"; +import { AppContext } from "../../../mod.ts"; +import { BlogPostListingPage } from "../../../types.ts"; +import { REACTIONS_MOCK } from "../../../utils/constants.ts"; + +/** @title ExtensionOf BlogPostPage: Reactions */ +export default function reactionsExt( + _props: unknown, + _req: Request, + _ctx: AppContext, +): ExtensionOf { + return (blogpostListingPage: BlogPostListingPage | null) => { + if (!blogpostListingPage) { + return null; + } + + return { + ...blogpostListingPage, + posts: blogpostListingPage.posts.map((post) => ({ + ...post, + reactions: REACTIONS_MOCK, + })), + }; + }; +} diff --git a/blog/loaders/extensions/BlogpostPage.ts b/blog/loaders/extensions/BlogpostPage.ts new file mode 100644 index 000000000..80888014e --- /dev/null +++ b/blog/loaders/extensions/BlogpostPage.ts @@ -0,0 +1,16 @@ +import { + default as extend, + Props, +} from "../../../website/loaders/extension.ts"; +import { BlogPostPage } from "../../types.ts"; + +/** + * @title Extend your Blogpost Page + */ +export default function ProductDetailsExt( + props: Props, +): Promise { + return extend(props); +} + +export const cache = "no-cache"; diff --git a/blog/loaders/extensions/BlogpostPage/reactions.ts b/blog/loaders/extensions/BlogpostPage/reactions.ts new file mode 100644 index 000000000..afb42cc2c --- /dev/null +++ b/blog/loaders/extensions/BlogpostPage/reactions.ts @@ -0,0 +1,25 @@ +import { ExtensionOf } from "../../../../website/loaders/extension.ts"; +import { AppContext } from "../../../mod.ts"; +import { BlogPostPage } from "../../../types.ts"; +import { REACTIONS_MOCK } from "../../../utils/constants.ts"; + +/** @title ExtensionOf BlogPostPage: Reactions */ +export default function reactionsExt( + _props: unknown, + _req: Request, + _ctx: AppContext, +): ExtensionOf { + return (blogpostPage: BlogPostPage | null) => { + if (!blogpostPage) { + return null; + } + + return { + ...blogpostPage, + post: { + ...blogpostPage.post, + reactions: REACTIONS_MOCK, + }, + }; + }; +} diff --git a/blog/manifest.gen.ts b/blog/manifest.gen.ts index 157fa04d7..685200533 100644 --- a/blog/manifest.gen.ts +++ b/blog/manifest.gen.ts @@ -9,6 +9,12 @@ import * as $$$4 from "./loaders/BlogpostList.ts"; import * as $$$5 from "./loaders/BlogpostListing.ts"; import * as $$$2 from "./loaders/BlogPostPage.ts"; import * as $$$6 from "./loaders/Category.ts"; +import * as $$$8 from "./loaders/extensions/BlogposList/reactions.ts"; +import * as $$$9 from "./loaders/extensions/BlogpostList.ts"; +import * as $$$10 from "./loaders/extensions/BlogpostListing.ts"; +import * as $$$11 from "./loaders/extensions/BlogpostListing/reactions.ts"; +import * as $$$12 from "./loaders/extensions/BlogpostPage.ts"; +import * as $$$13 from "./loaders/extensions/BlogpostPage/reactions.ts"; import * as $$$7 from "./loaders/GetCategories.ts"; import * as $$$$$$0 from "./sections/Seo/SeoBlogPost.tsx"; import * as $$$$$$1 from "./sections/Seo/SeoBlogPostListing.tsx"; @@ -23,6 +29,12 @@ const manifest = { "blog/loaders/BlogpostListing.ts": $$$5, "blog/loaders/BlogPostPage.ts": $$$2, "blog/loaders/Category.ts": $$$6, + "blog/loaders/extensions/BlogposList/reactions.ts": $$$8, + "blog/loaders/extensions/BlogpostList.ts": $$$9, + "blog/loaders/extensions/BlogpostListing.ts": $$$10, + "blog/loaders/extensions/BlogpostListing/reactions.ts": $$$11, + "blog/loaders/extensions/BlogpostPage.ts": $$$12, + "blog/loaders/extensions/BlogpostPage/reactions.ts": $$$13, "blog/loaders/GetCategories.ts": $$$7, }, "sections": { diff --git a/blog/types.ts b/blog/types.ts index 22e1ceb31..434267f48 100644 --- a/blog/types.ts +++ b/blog/types.ts @@ -1,5 +1,5 @@ import { ImageWidget } from "../admin/widgets.ts"; -import { PageInfo } from "../commerce/types.ts"; +import { PageInfo, Person } from "../commerce/types.ts"; /** * @titleBy name @@ -54,6 +54,8 @@ export interface BlogPost { * @title Extra Props */ extraProps?: ExtraProps[]; + /** @hide true */ + reactions?: Reaction[]; } export interface ExtraProps { @@ -86,3 +88,10 @@ export interface BlogPostListingPage { pageInfo: PageInfo; seo: Seo; } + +export interface Reaction { + person: Person; + datePublished: string; + dateModified: string; + action: "like" | "deslike"; +} diff --git a/blog/utils/constants.ts b/blog/utils/constants.ts index 20c5ad2ba..c382c1b41 100644 --- a/blog/utils/constants.ts +++ b/blog/utils/constants.ts @@ -1 +1,13 @@ +import { Reaction } from "../types.ts"; + export const VALID_SORT_ORDERS = ["asc", "desc"]; + +export const REACTIONS_MOCK: Reaction[] = [{ + person: { + email: "test@test", + name: "test", + }, + datePublished: "2024-09-13T12:07:29.207Z", + dateModified: "2024-09-13T12:07:46.120Z", + action: "like", +}]; diff --git a/wake/loaders/productListingPage.ts b/wake/loaders/productListingPage.ts index a327a8671..f20499788 100644 --- a/wake/loaders/productListingPage.ts +++ b/wake/loaders/productListingPage.ts @@ -208,7 +208,7 @@ const searchLoader = async ( const hasNextPage = Boolean( (data?.result?.productsByOffset?.totalCount ?? 0) / - (limit) > + limit > (data?.result?.productsByOffset?.page ?? 0), ); From 5b41f6800acb1704ffb7e0851ceeaf864636a9b1 Mon Sep 17 00:00:00 2001 From: decobot Date: Mon, 16 Sep 2024 10:28:40 -0300 Subject: [PATCH 02/20] chore(blogLike): reaction mock changed --- blog/utils/constants.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blog/utils/constants.ts b/blog/utils/constants.ts index c382c1b41..c3b3c11a9 100644 --- a/blog/utils/constants.ts +++ b/blog/utils/constants.ts @@ -4,8 +4,8 @@ export const VALID_SORT_ORDERS = ["asc", "desc"]; export const REACTIONS_MOCK: Reaction[] = [{ person: { - email: "test@test", - name: "test", + email: "elaine.santo@electrolux.com", + name: "Elaine", }, datePublished: "2024-09-13T12:07:29.207Z", dateModified: "2024-09-13T12:07:46.120Z", From c8d9b2d94047927840355b3067433671167662c9 Mon Sep 17 00:00:00 2001 From: decobot Date: Mon, 16 Sep 2024 15:43:07 -0300 Subject: [PATCH 03/20] feat(blogLike): deco records bug fix --- records/scripts/pullProd.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/records/scripts/pullProd.ts b/records/scripts/pullProd.ts index 0e37b44b0..87dc74842 100644 --- a/records/scripts/pullProd.ts +++ b/records/scripts/pullProd.ts @@ -1,5 +1,5 @@ import { existsSync } from "https://deno.land/std@0.201.0/fs/exists.ts"; -import { createClient as createSQLClient } from "../deps.ts"; +import { createClient as createSQLClient, createLocalClient } from "../deps.ts"; import { getLocalDbFilename, getLocalSQLClientConfig } from "../utils.ts"; import { brightGreen, brightYellow } from "std/fmt/colors.ts"; import { getDbCredentials } from "./checkDbCredential.ts"; @@ -77,7 +77,11 @@ async function run() { } } - const sqlClient = createSQLClient( + if (!createLocalClient) { + return "local client not defined!"; + } + + const sqlClient = createLocalClient( getLocalSQLClientConfig(), ); @@ -86,6 +90,8 @@ async function run() { await sqlClient.batch(sliced, "write"); await checkDumpInsertedTables(sqlClient); + + return "sqlite.db updated sucessfully!" } run() From a563bc57e613c2f053caca70565f8d1f5932b6e5 Mon Sep 17 00:00:00 2001 From: decobot Date: Mon, 16 Sep 2024 17:32:41 -0300 Subject: [PATCH 04/20] feat(bloglike): extension loaders ended --- blog/db/schema.ts | 13 +++++++++ .../reactions.ts | 18 ++++++------ blog/loaders/extensions/BlogpostListing.ts | 4 +-- .../extensions/BlogpostListing/reactions.ts | 18 +++++++----- .../extensions/BlogpostPage/reactions.ts | 16 ++++------- blog/manifest.gen.ts | 8 +++--- blog/utils/records.ts | 28 +++++++++++++++++++ records/scripts/pullProd.ts | 2 +- 8 files changed, 74 insertions(+), 33 deletions(-) create mode 100644 blog/db/schema.ts rename blog/loaders/extensions/{BlogposList => BlogpostList}/reactions.ts (52%) diff --git a/blog/db/schema.ts b/blog/db/schema.ts new file mode 100644 index 000000000..604d594f7 --- /dev/null +++ b/blog/db/schema.ts @@ -0,0 +1,13 @@ +import { + sqliteTable, + text, +} from "https://esm.sh/drizzle-orm@0.30.10/sqlite-core"; + +export const reactions = sqliteTable("reactions", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + postSlug: text("postSlug").notNull(), + person: text("person", { mode: "json" }), + datePublished: (text("datePublished")).notNull(), + dateModified: (text("dateModified")).notNull(), + action: (text("action")).notNull(), +}); diff --git a/blog/loaders/extensions/BlogposList/reactions.ts b/blog/loaders/extensions/BlogpostList/reactions.ts similarity index 52% rename from blog/loaders/extensions/BlogposList/reactions.ts rename to blog/loaders/extensions/BlogpostList/reactions.ts index fc1717ea0..bd151a080 100644 --- a/blog/loaders/extensions/BlogposList/reactions.ts +++ b/blog/loaders/extensions/BlogpostList/reactions.ts @@ -1,24 +1,26 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; import { BlogPost } from "../../../types.ts"; -import { REACTIONS_MOCK } from "../../../utils/constants.ts"; +import { getReactions } from "../../../utils/records.ts"; /** @title ExtensionOf BlogPost list: Reactions */ export default function reactionsExt( _props: unknown, _req: Request, - _ctx: AppContext, + ctx: AppContext, ): ExtensionOf { - return (posts: BlogPost[] | null) => { + return async (posts: BlogPost[] | null) => { if (posts?.length === 0 || !posts) { return null; } - const extendedPosts = posts?.map((post) => ({ - ...post, - reactions: REACTIONS_MOCK, - })); + const postsWithReactions = await Promise.all( + posts.map(async (post) => { + const reactions = await getReactions({ post, ctx }); + return { ...post, ...reactions }; + }), + ); - return extendedPosts; + return postsWithReactions; }; } diff --git a/blog/loaders/extensions/BlogpostListing.ts b/blog/loaders/extensions/BlogpostListing.ts index 741b0705a..6b10238d4 100644 --- a/blog/loaders/extensions/BlogpostListing.ts +++ b/blog/loaders/extensions/BlogpostListing.ts @@ -8,8 +8,8 @@ import { BlogPostListingPage } from "../../types.ts"; * @title Extend your Blogpost Listing Page */ export default function ProductDetailsExt( - props: Props, -): Promise { + props: Props, +): Promise { return extend(props); } diff --git a/blog/loaders/extensions/BlogpostListing/reactions.ts b/blog/loaders/extensions/BlogpostListing/reactions.ts index 492d75823..38ba61692 100644 --- a/blog/loaders/extensions/BlogpostListing/reactions.ts +++ b/blog/loaders/extensions/BlogpostListing/reactions.ts @@ -1,25 +1,29 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; import { BlogPostListingPage } from "../../../types.ts"; -import { REACTIONS_MOCK } from "../../../utils/constants.ts"; +import { getReactions } from "../../../utils/records.ts"; /** @title ExtensionOf BlogPostPage: Reactions */ export default function reactionsExt( _props: unknown, _req: Request, - _ctx: AppContext, + ctx: AppContext, ): ExtensionOf { - return (blogpostListingPage: BlogPostListingPage | null) => { + return async (blogpostListingPage: BlogPostListingPage | null) => { if (!blogpostListingPage) { return null; } + const postsWithReactions = await Promise.all( + blogpostListingPage.posts.map(async (post) => { + const reactions = await getReactions({ post, ctx }); + return { ...post, ...reactions }; + }), + ); + return { ...blogpostListingPage, - posts: blogpostListingPage.posts.map((post) => ({ - ...post, - reactions: REACTIONS_MOCK, - })), + posts: postsWithReactions, }; }; } diff --git a/blog/loaders/extensions/BlogpostPage/reactions.ts b/blog/loaders/extensions/BlogpostPage/reactions.ts index afb42cc2c..aaa68fe00 100644 --- a/blog/loaders/extensions/BlogpostPage/reactions.ts +++ b/blog/loaders/extensions/BlogpostPage/reactions.ts @@ -1,25 +1,19 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; import { BlogPostPage } from "../../../types.ts"; -import { REACTIONS_MOCK } from "../../../utils/constants.ts"; +import { getReactions } from "../../../utils/records.ts"; /** @title ExtensionOf BlogPostPage: Reactions */ export default function reactionsExt( _props: unknown, _req: Request, - _ctx: AppContext, + ctx: AppContext, ): ExtensionOf { - return (blogpostPage: BlogPostPage | null) => { + return async (blogpostPage: BlogPostPage | null) => { if (!blogpostPage) { return null; } - - return { - ...blogpostPage, - post: { - ...blogpostPage.post, - reactions: REACTIONS_MOCK, - }, - }; + const extendedPosts = await getReactions({ post: blogpostPage.post, ctx }); + return { ...blogpostPage, post: extendedPosts }; }; } diff --git a/blog/manifest.gen.ts b/blog/manifest.gen.ts index 685200533..f465a50e9 100644 --- a/blog/manifest.gen.ts +++ b/blog/manifest.gen.ts @@ -9,8 +9,8 @@ import * as $$$4 from "./loaders/BlogpostList.ts"; import * as $$$5 from "./loaders/BlogpostListing.ts"; import * as $$$2 from "./loaders/BlogPostPage.ts"; import * as $$$6 from "./loaders/Category.ts"; -import * as $$$8 from "./loaders/extensions/BlogposList/reactions.ts"; -import * as $$$9 from "./loaders/extensions/BlogpostList.ts"; +import * as $$$8 from "./loaders/extensions/BlogpostList.ts"; +import * as $$$9 from "./loaders/extensions/BlogpostList/reactions.ts"; import * as $$$10 from "./loaders/extensions/BlogpostListing.ts"; import * as $$$11 from "./loaders/extensions/BlogpostListing/reactions.ts"; import * as $$$12 from "./loaders/extensions/BlogpostPage.ts"; @@ -29,8 +29,8 @@ const manifest = { "blog/loaders/BlogpostListing.ts": $$$5, "blog/loaders/BlogPostPage.ts": $$$2, "blog/loaders/Category.ts": $$$6, - "blog/loaders/extensions/BlogposList/reactions.ts": $$$8, - "blog/loaders/extensions/BlogpostList.ts": $$$9, + "blog/loaders/extensions/BlogpostList.ts": $$$8, + "blog/loaders/extensions/BlogpostList/reactions.ts": $$$9, "blog/loaders/extensions/BlogpostListing.ts": $$$10, "blog/loaders/extensions/BlogpostListing/reactions.ts": $$$11, "blog/loaders/extensions/BlogpostPage.ts": $$$12, diff --git a/blog/utils/records.ts b/blog/utils/records.ts index 6e19e463b..af0e8a2a7 100644 --- a/blog/utils/records.ts +++ b/blog/utils/records.ts @@ -1,5 +1,9 @@ +import { reactions } from "../db/schema.ts"; import { AppContext } from "../mod.ts"; import { type Resolvable } from "@deco/deco"; +import { eq } from "https://esm.sh/drizzle-orm@0.30.10"; +import { BlogPost } from "../types.ts"; +import { logger } from "@deco/deco/o11y"; export async function getRecordsByPath( ctx: AppContext, path: string, @@ -13,3 +17,27 @@ export async function getRecordsByPath( }); return (current as Record[]).map((item) => item[accessor]); } + +export async function getReactions( + { ctx, post }: { ctx: AppContext; post: BlogPost }, +): Promise { + const records = await ctx.invoke.records.loaders.drizzle(); + try { + const currentReactions = await records.select({ + postSlug: reactions.postSlug, + person: reactions.person, + datePublished: reactions.datePublished, + dateModified: reactions.dateModified, + action: reactions.action, + }) + .from(reactions).where(eq(reactions.postSlug, post.slug)); + + return { + ...post, + reactions: currentReactions, + }; + } catch (e) { + logger.error(e); + return post; + } +} diff --git a/records/scripts/pullProd.ts b/records/scripts/pullProd.ts index 87dc74842..a0a7316dc 100644 --- a/records/scripts/pullProd.ts +++ b/records/scripts/pullProd.ts @@ -91,7 +91,7 @@ async function run() { await checkDumpInsertedTables(sqlClient); - return "sqlite.db updated sucessfully!" + return "sqlite.db updated sucessfully!"; } run() From 640176491d812997c14a9898f4f094f1cea88646 Mon Sep 17 00:00:00 2001 From: decobot Date: Mon, 16 Sep 2024 20:53:47 -0300 Subject: [PATCH 05/20] feat(bloglike): submit extension --- blog/actions/submitReaction.ts | 74 +++++++++++++++++++ blog/db/schema.ts | 5 ++ .../extensions/BlogpostPage/reactions.ts | 4 +- blog/manifest.gen.ts | 4 + blog/utils/records.ts | 31 +++++--- 5 files changed, 106 insertions(+), 12 deletions(-) create mode 100644 blog/actions/submitReaction.ts diff --git a/blog/actions/submitReaction.ts b/blog/actions/submitReaction.ts new file mode 100644 index 000000000..18c164491 --- /dev/null +++ b/blog/actions/submitReaction.ts @@ -0,0 +1,74 @@ +import { and, eq, like, or } from "https://esm.sh/drizzle-orm@0.30.10"; +import { Person } from "../../commerce/types.ts"; +import { reactions, ReactionSchema } from "../db/schema.ts"; +import { AppContext } from "../mod.ts"; +import { logger } from "@deco/deco/o11y"; + +export interface Props { + postSlug: string; + person: Person; + action: "like" | "deslike"; +} + +export interface SubmitResult { + success: boolean; + message?: string; +} + +export default async function submitReaction( + { postSlug, person, action }: Props, + _req: Request, + ctx: AppContext, +): Promise { + const isoDate = new Date().toISOString().split("T")[0]; + const records = await ctx.invoke.records.loaders.drizzle(); + + try { + const currentReaction = await records.select({ + postSlug: reactions.postSlug, + person: reactions.person, + action: reactions.action, + id: reactions.id, + }) + .from(reactions).where( + and( + eq(reactions.postSlug, postSlug), + or( + like(reactions.person, `%"email":"${person.email}"%`), + like(reactions.person, `%"id":"${person["@id"]}"%`), + ), + ), + ) as ReactionSchema[]; + + if (currentReaction.length > 0 && currentReaction[0].id) { + const { id } = currentReaction[0]!; + await records.update(reactions).set({ + action: action, + dateModified: isoDate, + }).where( + eq(reactions.id, id), + ); + return { + success: true, + }; + } + + await records.insert(reactions).values({ + postSlug: postSlug, + person: person, + datePublished: isoDate, + dateModified: isoDate, + action: action, + }); + + return { + success: true, + }; + } catch (e) { + logger.error(e); + return { + success: false, + message: e, + }; + } +} diff --git a/blog/db/schema.ts b/blog/db/schema.ts index 604d594f7..c2cc8091b 100644 --- a/blog/db/schema.ts +++ b/blog/db/schema.ts @@ -2,6 +2,7 @@ import { sqliteTable, text, } from "https://esm.sh/drizzle-orm@0.30.10/sqlite-core"; +import { Reaction } from "../types.ts"; export const reactions = sqliteTable("reactions", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), @@ -11,3 +12,7 @@ export const reactions = sqliteTable("reactions", { dateModified: (text("dateModified")).notNull(), action: (text("action")).notNull(), }); + +export interface ReactionSchema extends Reaction { + id: string; +} diff --git a/blog/loaders/extensions/BlogpostPage/reactions.ts b/blog/loaders/extensions/BlogpostPage/reactions.ts index aaa68fe00..37b317938 100644 --- a/blog/loaders/extensions/BlogpostPage/reactions.ts +++ b/blog/loaders/extensions/BlogpostPage/reactions.ts @@ -13,7 +13,7 @@ export default function reactionsExt( if (!blogpostPage) { return null; } - const extendedPosts = await getReactions({ post: blogpostPage.post, ctx }); - return { ...blogpostPage, post: extendedPosts }; + const post = await getReactions({ post: blogpostPage.post, ctx }); + return { ...blogpostPage, post }; }; } diff --git a/blog/manifest.gen.ts b/blog/manifest.gen.ts index f465a50e9..1df5206a1 100644 --- a/blog/manifest.gen.ts +++ b/blog/manifest.gen.ts @@ -2,6 +2,7 @@ // This file SHOULD be checked into source version control. // This file is automatically updated during development when running `dev.ts`. +import * as $$$$$$$$$0 from "./actions/submitReaction.ts"; import * as $$$0 from "./loaders/Author.ts"; import * as $$$3 from "./loaders/Blogpost.ts"; import * as $$$1 from "./loaders/BlogPostItem.ts"; @@ -42,6 +43,9 @@ const manifest = { "blog/sections/Seo/SeoBlogPostListing.tsx": $$$$$$1, "blog/sections/Template.tsx": $$$$$$2, }, + "actions": { + "blog/actions/submitReaction.ts": $$$$$$$$$0, + }, "name": "blog", "baseUrl": import.meta.url, }; diff --git a/blog/utils/records.ts b/blog/utils/records.ts index af0e8a2a7..7e43b09d0 100644 --- a/blog/utils/records.ts +++ b/blog/utils/records.ts @@ -2,7 +2,7 @@ import { reactions } from "../db/schema.ts"; import { AppContext } from "../mod.ts"; import { type Resolvable } from "@deco/deco"; import { eq } from "https://esm.sh/drizzle-orm@0.30.10"; -import { BlogPost } from "../types.ts"; +import { BlogPost, Reaction } from "../types.ts"; import { logger } from "@deco/deco/o11y"; export async function getRecordsByPath( ctx: AppContext, @@ -18,9 +18,9 @@ export async function getRecordsByPath( return (current as Record[]).map((item) => item[accessor]); } -export async function getReactions( - { ctx, post }: { ctx: AppContext; post: BlogPost }, -): Promise { +export async function getReactionFromSlug( + { ctx, slug }: { ctx: AppContext; slug: string }, +): Promise { const records = await ctx.invoke.records.loaders.drizzle(); try { const currentReactions = await records.select({ @@ -30,14 +30,25 @@ export async function getReactions( dateModified: reactions.dateModified, action: reactions.action, }) - .from(reactions).where(eq(reactions.postSlug, post.slug)); + .from(reactions).where(eq(reactions.postSlug, slug)) as Reaction[]; - return { - ...post, - reactions: currentReactions, - }; + return currentReactions; } catch (e) { logger.error(e); - return post; + return []; } } + +export async function getReactions( + { ctx, post }: { ctx: AppContext; post: BlogPost }, +): Promise { + const currentReactions = await getReactionFromSlug({ + ctx, + slug: post.slug, + }); + + return { + ...post, + reactions: currentReactions, + }; +} From 64a827d43f8d927ce838d7d96f748abc66c52da3 Mon Sep 17 00:00:00 2001 From: decobot Date: Mon, 16 Sep 2024 21:28:25 -0300 Subject: [PATCH 06/20] fix(bloglike): action fix --- blog/actions/submitReaction.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/blog/actions/submitReaction.ts b/blog/actions/submitReaction.ts index 18c164491..be140e1d7 100644 --- a/blog/actions/submitReaction.ts +++ b/blog/actions/submitReaction.ts @@ -3,6 +3,7 @@ import { Person } from "../../commerce/types.ts"; import { reactions, ReactionSchema } from "../db/schema.ts"; import { AppContext } from "../mod.ts"; import { logger } from "@deco/deco/o11y"; +import { Reaction } from "../types.ts"; export interface Props { postSlug: string; @@ -19,7 +20,7 @@ export default async function submitReaction( { postSlug, person, action }: Props, _req: Request, ctx: AppContext, -): Promise { +): Promise { const isoDate = new Date().toISOString().split("T")[0]; const records = await ctx.invoke.records.loaders.drizzle(); @@ -28,6 +29,8 @@ export default async function submitReaction( postSlug: reactions.postSlug, person: reactions.person, action: reactions.action, + dateModified: reactions.dateModified, + datePublished: reactions.datePublished, id: reactions.id, }) .from(reactions).where( @@ -41,15 +44,17 @@ export default async function submitReaction( ) as ReactionSchema[]; if (currentReaction.length > 0 && currentReaction[0].id) { - const { id } = currentReaction[0]!; + const current = currentReaction[0]!; await records.update(reactions).set({ action: action, dateModified: isoDate, }).where( - eq(reactions.id, id), + eq(reactions.id, current.id), ); return { - success: true, + ...current, + action, + dateModified: isoDate, }; } @@ -62,13 +67,13 @@ export default async function submitReaction( }); return { - success: true, + person, + datePublished: isoDate, + dateModified: isoDate, + action, }; } catch (e) { logger.error(e); - return { - success: false, - message: e, - }; + return null; } } From e85fab282a3bf07d040596ab0661450c5faa561d Mon Sep 17 00:00:00 2001 From: decobot Date: Tue, 17 Sep 2024 06:11:47 -0300 Subject: [PATCH 07/20] chore(bloglike): prettify code --- blog/actions/submitReaction.ts | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/blog/actions/submitReaction.ts b/blog/actions/submitReaction.ts index be140e1d7..cf5abf909 100644 --- a/blog/actions/submitReaction.ts +++ b/blog/actions/submitReaction.ts @@ -25,7 +25,7 @@ export default async function submitReaction( const records = await ctx.invoke.records.loaders.drizzle(); try { - const currentReaction = await records.select({ + const storedReaction = await records.select({ postSlug: reactions.postSlug, person: reactions.person, action: reactions.action, @@ -43,10 +43,11 @@ export default async function submitReaction( ), ) as ReactionSchema[]; - if (currentReaction.length > 0 && currentReaction[0].id) { - const current = currentReaction[0]!; + //if has data, then update de table + if (storedReaction.length > 0 && storedReaction?.at(0)?.id) { + const current = storedReaction.at(0)!; await records.update(reactions).set({ - action: action, + action, dateModified: isoDate, }).where( eq(reactions.id, current.id), @@ -58,20 +59,20 @@ export default async function submitReaction( }; } - await records.insert(reactions).values({ - postSlug: postSlug, - person: person, - datePublished: isoDate, - dateModified: isoDate, - action: action, - }); - - return { + //Or insert more data + const insertedData = { person, + action, datePublished: isoDate, dateModified: isoDate, - action, }; + + await records.insert(reactions).values({ + postSlug: postSlug, + ...insertedData, + }); + + return insertedData; } catch (e) { logger.error(e); return null; From d6e7437fbe08a8237a489d09381b4c7bdf0a5376 Mon Sep 17 00:00:00 2001 From: decobot Date: Tue, 17 Sep 2024 15:15:43 -0300 Subject: [PATCH 08/20] feat(blogpost): comment feat started --- blog/db/schema.ts | 19 ++++++++- .../extensions/BlogpostList/comments.ts | 29 ++++++++++++++ .../extensions/BlogpostList/reactions.ts | 5 ++- .../extensions/BlogpostListing/comments.ts | 32 +++++++++++++++ .../extensions/BlogpostListing/reactions.ts | 5 ++- .../extensions/BlogpostPage/comments.ts | 22 ++++++++++ .../extensions/BlogpostPage/reactions.ts | 5 ++- blog/manifest.gen.ts | 26 +++++++----- blog/types.ts | 14 ++++++- blog/utils/records.ts | 40 ++++++++++++++++++- 10 files changed, 179 insertions(+), 18 deletions(-) create mode 100644 blog/loaders/extensions/BlogpostList/comments.ts create mode 100644 blog/loaders/extensions/BlogpostListing/comments.ts create mode 100644 blog/loaders/extensions/BlogpostPage/comments.ts diff --git a/blog/db/schema.ts b/blog/db/schema.ts index c2cc8091b..b4a804e45 100644 --- a/blog/db/schema.ts +++ b/blog/db/schema.ts @@ -1,8 +1,9 @@ import { + integer, sqliteTable, text, } from "https://esm.sh/drizzle-orm@0.30.10/sqlite-core"; -import { Reaction } from "../types.ts"; +import { ArticleComment, Reaction } from "../types.ts"; export const reactions = sqliteTable("reactions", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), @@ -13,6 +14,22 @@ export const reactions = sqliteTable("reactions", { action: (text("action")).notNull(), }); +export const comments = sqliteTable("comments", { + id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), + postSlug: text("postSlug").notNull(), + person: text("person", { mode: "json" }), + datePublished: (text("datePublished")).notNull(), + dateModified: (text("dateModified")).notNull(), + comment: (text("comment")).notNull(), + removed: integer("removed", { mode: "boolean" }).notNull().default(false), +}); + export interface ReactionSchema extends Reaction { id: string; + postSlug: string; +} + +export interface CommentsSchema extends ArticleComment { + id: string; + postSlug: string; } diff --git a/blog/loaders/extensions/BlogpostList/comments.ts b/blog/loaders/extensions/BlogpostList/comments.ts new file mode 100644 index 000000000..770221a0d --- /dev/null +++ b/blog/loaders/extensions/BlogpostList/comments.ts @@ -0,0 +1,29 @@ +import { ExtensionOf } from "../../../../website/loaders/extension.ts"; +import { AppContext } from "../../../mod.ts"; +import { BlogPost } from "../../../types.ts"; +import { getComments } from "../../../utils/records.ts"; + +/** + * @title ExtensionOf BlogPost list: Comments + * @description It can harm performance. Use wisely + */ +export default function reactionsExt( + _props: unknown, + _req: Request, + ctx: AppContext, +): ExtensionOf { + return async (posts: BlogPost[] | null) => { + if (posts?.length === 0 || !posts) { + return null; + } + + const postsWithReactions = await Promise.all( + posts.map(async (post) => { + const reactions = await getComments({ post, ctx }); + return { ...post, ...reactions }; + }), + ); + + return postsWithReactions; + }; +} diff --git a/blog/loaders/extensions/BlogpostList/reactions.ts b/blog/loaders/extensions/BlogpostList/reactions.ts index bd151a080..aa0c9e047 100644 --- a/blog/loaders/extensions/BlogpostList/reactions.ts +++ b/blog/loaders/extensions/BlogpostList/reactions.ts @@ -3,7 +3,10 @@ import { AppContext } from "../../../mod.ts"; import { BlogPost } from "../../../types.ts"; import { getReactions } from "../../../utils/records.ts"; -/** @title ExtensionOf BlogPost list: Reactions */ +/** + * @title ExtensionOf BlogPost list: Reactions + * @description It can harm performance. Use wisely + */ export default function reactionsExt( _props: unknown, _req: Request, diff --git a/blog/loaders/extensions/BlogpostListing/comments.ts b/blog/loaders/extensions/BlogpostListing/comments.ts new file mode 100644 index 000000000..0f8873838 --- /dev/null +++ b/blog/loaders/extensions/BlogpostListing/comments.ts @@ -0,0 +1,32 @@ +import { ExtensionOf } from "../../../../website/loaders/extension.ts"; +import { AppContext } from "../../../mod.ts"; +import { BlogPostListingPage } from "../../../types.ts"; +import { getComments } from "../../../utils/records.ts"; + +/** + * @title ExtensionOf BlogPostPage: Comments + * @description It can harm performance. Use wisely + */ +export default function reactionsExt( + _props: unknown, + _req: Request, + ctx: AppContext, +): ExtensionOf { + return async (blogpostListingPage: BlogPostListingPage | null) => { + if (!blogpostListingPage) { + return null; + } + + const postsWithReactions = await Promise.all( + blogpostListingPage.posts.map(async (post) => { + const reactions = await getComments({ post, ctx }); + return { ...post, ...reactions }; + }), + ); + + return { + ...blogpostListingPage, + posts: postsWithReactions, + }; + }; +} diff --git a/blog/loaders/extensions/BlogpostListing/reactions.ts b/blog/loaders/extensions/BlogpostListing/reactions.ts index 38ba61692..02f5d4e58 100644 --- a/blog/loaders/extensions/BlogpostListing/reactions.ts +++ b/blog/loaders/extensions/BlogpostListing/reactions.ts @@ -3,7 +3,10 @@ import { AppContext } from "../../../mod.ts"; import { BlogPostListingPage } from "../../../types.ts"; import { getReactions } from "../../../utils/records.ts"; -/** @title ExtensionOf BlogPostPage: Reactions */ +/** + * @title ExtensionOf BlogPostPage: Reactions + * @description It can harm performance. Use wisely + */ export default function reactionsExt( _props: unknown, _req: Request, diff --git a/blog/loaders/extensions/BlogpostPage/comments.ts b/blog/loaders/extensions/BlogpostPage/comments.ts new file mode 100644 index 000000000..1377b076b --- /dev/null +++ b/blog/loaders/extensions/BlogpostPage/comments.ts @@ -0,0 +1,22 @@ +import { ExtensionOf } from "../../../../website/loaders/extension.ts"; +import { AppContext } from "../../../mod.ts"; +import { BlogPostPage } from "../../../types.ts"; +import { getComments } from "../../../utils/records.ts"; + +/** + * @title ExtensionOf BlogPostPage: Comments + * @description It can harm performance. Use wisely + */ +export default function reactionsExt( + _props: unknown, + _req: Request, + ctx: AppContext, +): ExtensionOf { + return async (blogpostPage: BlogPostPage | null) => { + if (!blogpostPage) { + return null; + } + const post = await getComments({ post: blogpostPage.post, ctx }); + return { ...blogpostPage, post }; + }; +} diff --git a/blog/loaders/extensions/BlogpostPage/reactions.ts b/blog/loaders/extensions/BlogpostPage/reactions.ts index 37b317938..8d8e5145c 100644 --- a/blog/loaders/extensions/BlogpostPage/reactions.ts +++ b/blog/loaders/extensions/BlogpostPage/reactions.ts @@ -3,7 +3,10 @@ import { AppContext } from "../../../mod.ts"; import { BlogPostPage } from "../../../types.ts"; import { getReactions } from "../../../utils/records.ts"; -/** @title ExtensionOf BlogPostPage: Reactions */ +/** + * @title ExtensionOf BlogPostPage: Reactions + * @description It can harm performance. Use wisely + */ export default function reactionsExt( _props: unknown, _req: Request, diff --git a/blog/manifest.gen.ts b/blog/manifest.gen.ts index 1df5206a1..5ec97c598 100644 --- a/blog/manifest.gen.ts +++ b/blog/manifest.gen.ts @@ -11,11 +11,14 @@ import * as $$$5 from "./loaders/BlogpostListing.ts"; import * as $$$2 from "./loaders/BlogPostPage.ts"; import * as $$$6 from "./loaders/Category.ts"; import * as $$$8 from "./loaders/extensions/BlogpostList.ts"; -import * as $$$9 from "./loaders/extensions/BlogpostList/reactions.ts"; -import * as $$$10 from "./loaders/extensions/BlogpostListing.ts"; -import * as $$$11 from "./loaders/extensions/BlogpostListing/reactions.ts"; -import * as $$$12 from "./loaders/extensions/BlogpostPage.ts"; -import * as $$$13 from "./loaders/extensions/BlogpostPage/reactions.ts"; +import * as $$$9 from "./loaders/extensions/BlogpostList/comments.ts"; +import * as $$$10 from "./loaders/extensions/BlogpostList/reactions.ts"; +import * as $$$11 from "./loaders/extensions/BlogpostListing.ts"; +import * as $$$12 from "./loaders/extensions/BlogpostListing/comments.ts"; +import * as $$$13 from "./loaders/extensions/BlogpostListing/reactions.ts"; +import * as $$$14 from "./loaders/extensions/BlogpostPage.ts"; +import * as $$$15 from "./loaders/extensions/BlogpostPage/comments.ts"; +import * as $$$16 from "./loaders/extensions/BlogpostPage/reactions.ts"; import * as $$$7 from "./loaders/GetCategories.ts"; import * as $$$$$$0 from "./sections/Seo/SeoBlogPost.tsx"; import * as $$$$$$1 from "./sections/Seo/SeoBlogPostListing.tsx"; @@ -31,11 +34,14 @@ const manifest = { "blog/loaders/BlogPostPage.ts": $$$2, "blog/loaders/Category.ts": $$$6, "blog/loaders/extensions/BlogpostList.ts": $$$8, - "blog/loaders/extensions/BlogpostList/reactions.ts": $$$9, - "blog/loaders/extensions/BlogpostListing.ts": $$$10, - "blog/loaders/extensions/BlogpostListing/reactions.ts": $$$11, - "blog/loaders/extensions/BlogpostPage.ts": $$$12, - "blog/loaders/extensions/BlogpostPage/reactions.ts": $$$13, + "blog/loaders/extensions/BlogpostList/comments.ts": $$$9, + "blog/loaders/extensions/BlogpostList/reactions.ts": $$$10, + "blog/loaders/extensions/BlogpostListing.ts": $$$11, + "blog/loaders/extensions/BlogpostListing/comments.ts": $$$12, + "blog/loaders/extensions/BlogpostListing/reactions.ts": $$$13, + "blog/loaders/extensions/BlogpostPage.ts": $$$14, + "blog/loaders/extensions/BlogpostPage/comments.ts": $$$15, + "blog/loaders/extensions/BlogpostPage/reactions.ts": $$$16, "blog/loaders/GetCategories.ts": $$$7, }, "sections": { diff --git a/blog/types.ts b/blog/types.ts index 434267f48..a4443c442 100644 --- a/blog/types.ts +++ b/blog/types.ts @@ -56,6 +56,8 @@ export interface BlogPost { extraProps?: ExtraProps[]; /** @hide true */ reactions?: Reaction[]; + /** @hide true */ + comments?: ArticleComment[]; } export interface ExtraProps { @@ -89,9 +91,17 @@ export interface BlogPostListingPage { seo: Seo; } -export interface Reaction { +export interface Reaction extends PersonAndDate { + action: "like" | "deslike"; +} + +export interface ArticleComment extends PersonAndDate { + comment: string; + removed: boolean; +} + +export interface PersonAndDate { person: Person; datePublished: string; dateModified: string; - action: "like" | "deslike"; } diff --git a/blog/utils/records.ts b/blog/utils/records.ts index 7e43b09d0..fe17b601d 100644 --- a/blog/utils/records.ts +++ b/blog/utils/records.ts @@ -1,8 +1,8 @@ -import { reactions } from "../db/schema.ts"; +import { comments, reactions } from "../db/schema.ts"; import { AppContext } from "../mod.ts"; import { type Resolvable } from "@deco/deco"; import { eq } from "https://esm.sh/drizzle-orm@0.30.10"; -import { BlogPost, Reaction } from "../types.ts"; +import { ArticleComment, BlogPost, Reaction } from "../types.ts"; import { logger } from "@deco/deco/o11y"; export async function getRecordsByPath( ctx: AppContext, @@ -52,3 +52,39 @@ export async function getReactions( reactions: currentReactions, }; } + +export async function getCommentsFromSlug( + { ctx, slug }: { ctx: AppContext; slug: string }, +): Promise { + const records = await ctx.invoke.records.loaders.drizzle(); + try { + const currentComments = await records.select({ + postSlug: comments.postSlug, + person: comments.person, + datePublished: comments.datePublished, + dateModified: comments.dateModified, + comment: comments.comment, + removed: comments.removed, + }) + .from(comments).where(eq(comments.postSlug, slug)) as ArticleComment[]; + + return currentComments; + } catch (e) { + logger.error(e); + return []; + } +} + +export async function getComments( + { ctx, post }: { ctx: AppContext; post: BlogPost }, +): Promise { + const comments = await getCommentsFromSlug({ + ctx, + slug: post.slug, + }); + + return { + ...post, + comments, + }; +} From e348e9f83308c25c2857e9c205e058d62f4cbf5f Mon Sep 17 00:00:00 2001 From: decobot Date: Tue, 17 Sep 2024 16:46:37 -0300 Subject: [PATCH 09/20] feat(blogpost): comment action added --- blog/actions/submitComment.ts | 73 +++++++++++++++++++++++++++++++++++ blog/db/schema.ts | 3 +- blog/manifest.gen.ts | 6 ++- blog/types.ts | 4 +- blog/utils/records.ts | 29 +++++++++++++- 5 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 blog/actions/submitComment.ts diff --git a/blog/actions/submitComment.ts b/blog/actions/submitComment.ts new file mode 100644 index 000000000..98542f71d --- /dev/null +++ b/blog/actions/submitComment.ts @@ -0,0 +1,73 @@ +import { eq } from "https://esm.sh/drizzle-orm@0.30.10"; +import { Person } from "../../commerce/types.ts"; +import { comments } from "../db/schema.ts"; +import { AppContext } from "../mod.ts"; +import { logger } from "@deco/deco/o11y"; +import { ArticleComment } from "../types.ts"; +import { getCommentById } from "../utils/records.ts"; + +export interface Props { + action: "create" | "update"; + postSlug?: string; + person?: Person; + status?: "submited" | "deleted"; + id?: string; + comment?: string; +} + +export interface SubmitResult { + success: boolean; + message?: string; +} + +export default async function submitComment( + { postSlug, person, status, id, comment, action }: Props, + _req: Request, + ctx: AppContext, +): Promise { + const isoDate = new Date().toISOString().split("T")[0]; + const records = await ctx.invoke.records.loaders.drizzle(); + + try { + if (action != "create") { + const storedComment = await getCommentById({ ctx, id }); + if (!storedComment) { + return null; + } + const updateRecord = { + status: status ?? storedComment.status, + comment: comment ?? storedComment.comment, + dateModified: isoDate, + }; + await records.update(comments).set({ + ...updateRecord, + }).where( + eq(comments.id, id!), + ); + + return { + ...updateRecord, + person: person ?? storedComment.person, + datePublished: storedComment.datePublished, + }; + } + + const insertData = { + person: person!, + status: status!, + comment: comment!, + datePublished: isoDate, + dateModified: isoDate, + }; + + await records.insert(comments).values({ + postSlug, + ...insertData, + }); + + return insertData; + } catch (e) { + logger.error(e); + return null; + } +} diff --git a/blog/db/schema.ts b/blog/db/schema.ts index b4a804e45..4af77a88f 100644 --- a/blog/db/schema.ts +++ b/blog/db/schema.ts @@ -1,5 +1,4 @@ import { - integer, sqliteTable, text, } from "https://esm.sh/drizzle-orm@0.30.10/sqlite-core"; @@ -21,7 +20,7 @@ export const comments = sqliteTable("comments", { datePublished: (text("datePublished")).notNull(), dateModified: (text("dateModified")).notNull(), comment: (text("comment")).notNull(), - removed: integer("removed", { mode: "boolean" }).notNull().default(false), + status: (text("status")).notNull(), }); export interface ReactionSchema extends Reaction { diff --git a/blog/manifest.gen.ts b/blog/manifest.gen.ts index 5ec97c598..612d1d156 100644 --- a/blog/manifest.gen.ts +++ b/blog/manifest.gen.ts @@ -2,7 +2,8 @@ // This file SHOULD be checked into source version control. // This file is automatically updated during development when running `dev.ts`. -import * as $$$$$$$$$0 from "./actions/submitReaction.ts"; +import * as $$$$$$$$$0 from "./actions/submitComment.ts"; +import * as $$$$$$$$$1 from "./actions/submitReaction.ts"; import * as $$$0 from "./loaders/Author.ts"; import * as $$$3 from "./loaders/Blogpost.ts"; import * as $$$1 from "./loaders/BlogPostItem.ts"; @@ -50,7 +51,8 @@ const manifest = { "blog/sections/Template.tsx": $$$$$$2, }, "actions": { - "blog/actions/submitReaction.ts": $$$$$$$$$0, + "blog/actions/submitComment.ts": $$$$$$$$$0, + "blog/actions/submitReaction.ts": $$$$$$$$$1, }, "name": "blog", "baseUrl": import.meta.url, diff --git a/blog/types.ts b/blog/types.ts index a4443c442..0a5c09979 100644 --- a/blog/types.ts +++ b/blog/types.ts @@ -92,12 +92,12 @@ export interface BlogPostListingPage { } export interface Reaction extends PersonAndDate { - action: "like" | "deslike"; + action: "like" | "deslike" | "invalid"; } export interface ArticleComment extends PersonAndDate { comment: string; - removed: boolean; + status: "submited" | "aproved" | "declined" | "deleted"; } export interface PersonAndDate { diff --git a/blog/utils/records.ts b/blog/utils/records.ts index fe17b601d..dee047243 100644 --- a/blog/utils/records.ts +++ b/blog/utils/records.ts @@ -1,4 +1,4 @@ -import { comments, reactions } from "../db/schema.ts"; +import { comments, CommentsSchema, reactions } from "../db/schema.ts"; import { AppContext } from "../mod.ts"; import { type Resolvable } from "@deco/deco"; import { eq } from "https://esm.sh/drizzle-orm@0.30.10"; @@ -64,7 +64,7 @@ export async function getCommentsFromSlug( datePublished: comments.datePublished, dateModified: comments.dateModified, comment: comments.comment, - removed: comments.removed, + status: comments.status, }) .from(comments).where(eq(comments.postSlug, slug)) as ArticleComment[]; @@ -88,3 +88,28 @@ export async function getComments( comments, }; } + +export const getCommentById = async ( + { ctx, id }: { ctx: AppContext; id?: string }, +) => { + if (!id) { + return null; + } + const records = await ctx.invoke.records.loaders.drizzle(); + try { + const comment = await records.select({ + postSlug: comments.postSlug, + person: comments.person, + datePublished: comments.datePublished, + dateModified: comments.dateModified, + comment: comments.comment, + status: comments.status, + id: comments.id, + }) + .from(comments).where(eq(comments.id, id)).get() as CommentsSchema; + return comment; + } catch (e) { + logger.error(e); + return null; + } +}; From b26b7f6889f2d28a8a2875d1791781a49d8819c1 Mon Sep 17 00:00:00 2001 From: decobot Date: Wed, 18 Sep 2024 15:03:57 -0300 Subject: [PATCH 10/20] fix(blog): changed schema --- blog/actions/submitComment.ts | 73 --------- blog/actions/submitRating.ts | 75 ++++++++++ blog/actions/submitReaction.ts | 80 ---------- blog/actions/submitReview.ts | 87 +++++++++++ blog/db/schema.ts | 35 ++--- .../BlogpostList/{reactions.ts => ratings.ts} | 14 +- .../BlogpostList/{comments.ts => reviews.ts} | 14 +- .../{comments.ts => ratings.ts} | 14 +- .../{reactions.ts => reviews.ts} | 14 +- .../BlogpostPage/{comments.ts => ratings.ts} | 8 +- .../BlogpostPage/{reactions.ts => reviews.ts} | 8 +- blog/manifest.gen.ts | 32 ++-- blog/types.ts | 61 ++++++-- blog/utils/constants.ts | 12 -- blog/utils/records.ts | 140 +++++++++++------- 15 files changed, 364 insertions(+), 303 deletions(-) delete mode 100644 blog/actions/submitComment.ts create mode 100644 blog/actions/submitRating.ts delete mode 100644 blog/actions/submitReaction.ts create mode 100644 blog/actions/submitReview.ts rename blog/loaders/extensions/BlogpostList/{reactions.ts => ratings.ts} (59%) rename blog/loaders/extensions/BlogpostList/{comments.ts => reviews.ts} (60%) rename blog/loaders/extensions/BlogpostListing/{comments.ts => ratings.ts} (64%) rename blog/loaders/extensions/BlogpostListing/{reactions.ts => reviews.ts} (64%) rename blog/loaders/extensions/BlogpostPage/{comments.ts => ratings.ts} (69%) rename blog/loaders/extensions/BlogpostPage/{reactions.ts => reviews.ts} (68%) diff --git a/blog/actions/submitComment.ts b/blog/actions/submitComment.ts deleted file mode 100644 index 98542f71d..000000000 --- a/blog/actions/submitComment.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { eq } from "https://esm.sh/drizzle-orm@0.30.10"; -import { Person } from "../../commerce/types.ts"; -import { comments } from "../db/schema.ts"; -import { AppContext } from "../mod.ts"; -import { logger } from "@deco/deco/o11y"; -import { ArticleComment } from "../types.ts"; -import { getCommentById } from "../utils/records.ts"; - -export interface Props { - action: "create" | "update"; - postSlug?: string; - person?: Person; - status?: "submited" | "deleted"; - id?: string; - comment?: string; -} - -export interface SubmitResult { - success: boolean; - message?: string; -} - -export default async function submitComment( - { postSlug, person, status, id, comment, action }: Props, - _req: Request, - ctx: AppContext, -): Promise { - const isoDate = new Date().toISOString().split("T")[0]; - const records = await ctx.invoke.records.loaders.drizzle(); - - try { - if (action != "create") { - const storedComment = await getCommentById({ ctx, id }); - if (!storedComment) { - return null; - } - const updateRecord = { - status: status ?? storedComment.status, - comment: comment ?? storedComment.comment, - dateModified: isoDate, - }; - await records.update(comments).set({ - ...updateRecord, - }).where( - eq(comments.id, id!), - ); - - return { - ...updateRecord, - person: person ?? storedComment.person, - datePublished: storedComment.datePublished, - }; - } - - const insertData = { - person: person!, - status: status!, - comment: comment!, - datePublished: isoDate, - dateModified: isoDate, - }; - - await records.insert(comments).values({ - postSlug, - ...insertData, - }); - - return insertData; - } catch (e) { - logger.error(e); - return null; - } -} diff --git a/blog/actions/submitRating.ts b/blog/actions/submitRating.ts new file mode 100644 index 000000000..47d84ade7 --- /dev/null +++ b/blog/actions/submitRating.ts @@ -0,0 +1,75 @@ +import { and, eq, like, or } from "https://esm.sh/drizzle-orm@0.30.10"; +import { Person } from "../../commerce/types.ts"; +import { AppContext } from "../mod.ts"; +import { logger } from "@deco/deco/o11y"; +import { Rating } from "../types.ts"; +import { rating } from "../db/schema.ts"; + +export interface Props { + itemReviewed: string; + author: Person; + ratingValue: number; + additionalType?: string; +} + +export default async function submitRating( + { itemReviewed, author, ratingValue, additionalType }: Props, + _req: Request, + ctx: AppContext, +): Promise { + const records = await ctx.invoke.records.loaders.drizzle(); + + try { + const storedRating = await records.select({ + id: rating.id, + itemReviewed: rating.itemReviewed, + author: rating.author, + ratingValue: rating.ratingValue, + additionalType: rating.additionalType, + }) + .from(rating).where( + and( + eq(rating.itemReviewed, itemReviewed), + or( + like(rating.author, `%"email":"${author.email}"%`), + like(rating.author, `%"id":"${author["@id"]}"%`), + ), + ), + ) as Rating[] | undefined; + + //if has data, then update de table + if (storedRating && storedRating.length > 0 && storedRating?.at(0)?.id) { + const current = storedRating.at(0)!; + await records.update(rating).set({ + ratingValue, + additionalType: additionalType ?? current.additionalType, + }).where( + eq(rating.id, current.id!), + ); + return { + ...current, + ratingValue, + additionalType: additionalType ?? current.additionalType, + }; + } + + const insertedData = { + itemReviewed, + author: author!, + ratingValue: ratingValue!, + additionalType: additionalType, + }; + + await records.insert(rating).values({ + ...insertedData, + }); + + return { + "@type": "Rating", + ...insertedData, + }; + } catch (e) { + logger.error(e); + return null; + } +} diff --git a/blog/actions/submitReaction.ts b/blog/actions/submitReaction.ts deleted file mode 100644 index cf5abf909..000000000 --- a/blog/actions/submitReaction.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { and, eq, like, or } from "https://esm.sh/drizzle-orm@0.30.10"; -import { Person } from "../../commerce/types.ts"; -import { reactions, ReactionSchema } from "../db/schema.ts"; -import { AppContext } from "../mod.ts"; -import { logger } from "@deco/deco/o11y"; -import { Reaction } from "../types.ts"; - -export interface Props { - postSlug: string; - person: Person; - action: "like" | "deslike"; -} - -export interface SubmitResult { - success: boolean; - message?: string; -} - -export default async function submitReaction( - { postSlug, person, action }: Props, - _req: Request, - ctx: AppContext, -): Promise { - const isoDate = new Date().toISOString().split("T")[0]; - const records = await ctx.invoke.records.loaders.drizzle(); - - try { - const storedReaction = await records.select({ - postSlug: reactions.postSlug, - person: reactions.person, - action: reactions.action, - dateModified: reactions.dateModified, - datePublished: reactions.datePublished, - id: reactions.id, - }) - .from(reactions).where( - and( - eq(reactions.postSlug, postSlug), - or( - like(reactions.person, `%"email":"${person.email}"%`), - like(reactions.person, `%"id":"${person["@id"]}"%`), - ), - ), - ) as ReactionSchema[]; - - //if has data, then update de table - if (storedReaction.length > 0 && storedReaction?.at(0)?.id) { - const current = storedReaction.at(0)!; - await records.update(reactions).set({ - action, - dateModified: isoDate, - }).where( - eq(reactions.id, current.id), - ); - return { - ...current, - action, - dateModified: isoDate, - }; - } - - //Or insert more data - const insertedData = { - person, - action, - datePublished: isoDate, - dateModified: isoDate, - }; - - await records.insert(reactions).values({ - postSlug: postSlug, - ...insertedData, - }); - - return insertedData; - } catch (e) { - logger.error(e); - return null; - } -} diff --git a/blog/actions/submitReview.ts b/blog/actions/submitReview.ts new file mode 100644 index 000000000..ee734fad8 --- /dev/null +++ b/blog/actions/submitReview.ts @@ -0,0 +1,87 @@ +import { eq } from "https://esm.sh/drizzle-orm@0.30.10"; +import { Person } from "../../commerce/types.ts"; +import { AppContext } from "../mod.ts"; +import { logger } from "@deco/deco/o11y"; +import { Review } from "../types.ts"; +import { getReviewById } from "../utils/records.ts"; +import { review } from "../db/schema.ts"; + +export interface Props { + action: "create" | "update"; + id?: string; + reviewBody?: string; + reviewHeadline?: string; + itemReviewed?: string; + author?: Person; + /** Review status */ + additionalType?: string; + isAnonymous?: boolean; +} + +export default async function submitReview( + { + reviewBody, + reviewHeadline, + itemReviewed, + id, + author, + action, + additionalType, + isAnonymous, + }: Props, + _req: Request, + ctx: AppContext, +): Promise { + const isoDate = new Date().toISOString(); + const records = await ctx.invoke.records.loaders.drizzle(); + + try { + if (action != "create") { + const storedReview = await getReviewById({ ctx, id }); + if (!storedReview) { + return null; + } + const updateRecord = { + additionalType: additionalType ?? storedReview.additionalType, + reviewHeadline: reviewHeadline ?? storedReview.reviewHeadline, + reviewBody: reviewBody ?? storedReview.reviewBody, + dateModified: isoDate, + }; + await records.update(review).set({ + ...updateRecord, + }).where( + eq(review.id, id!), + ); + + return { + ...updateRecord, + "@type": "Review", + author: author ?? storedReview.author, + datePublished: storedReview.datePublished, + }; + } + + const insertData = { + itemReviewed, + isAnonymous, + author: author!, + additionalType: additionalType, + reviewHeadline: reviewHeadline, + reviewBody: reviewBody!, + datePublished: isoDate, + dateModified: isoDate, + }; + + await records.insert(review).values({ + ...insertData, + }); + + return { + "@type": "Review", + ...insertData, + }; + } catch (e) { + logger.error(e); + return null; + } +} diff --git a/blog/db/schema.ts b/blog/db/schema.ts index 4af77a88f..6c46bf33b 100644 --- a/blog/db/schema.ts +++ b/blog/db/schema.ts @@ -1,34 +1,25 @@ import { + integer, sqliteTable, text, } from "https://esm.sh/drizzle-orm@0.30.10/sqlite-core"; -import { ArticleComment, Reaction } from "../types.ts"; -export const reactions = sqliteTable("reactions", { +export const rating = sqliteTable("rating", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), - postSlug: text("postSlug").notNull(), - person: text("person", { mode: "json" }), - datePublished: (text("datePublished")).notNull(), - dateModified: (text("dateModified")).notNull(), - action: (text("action")).notNull(), + itemReviewed: text("itemReviewed").notNull(), + author: text("author", { mode: "json" }), + ratingValue: (integer("ratingValue")).notNull(), + additionalType: text("additionalType"), }); -export const comments = sqliteTable("comments", { +export const review = sqliteTable("review", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), - postSlug: text("postSlug").notNull(), - person: text("person", { mode: "json" }), + itemReviewed: text("itemReviewed").notNull(), + author: text("author", { mode: "json" }), datePublished: (text("datePublished")).notNull(), dateModified: (text("dateModified")).notNull(), - comment: (text("comment")).notNull(), - status: (text("status")).notNull(), + reviewHeadline: (text("reviewHeadline")), + reviewBody: (text("reviewBody")).notNull(), + additionalType: (text("additionalType")), + isAnonymous: integer("isAnonymous", { mode: "boolean" }), }); - -export interface ReactionSchema extends Reaction { - id: string; - postSlug: string; -} - -export interface CommentsSchema extends ArticleComment { - id: string; - postSlug: string; -} diff --git a/blog/loaders/extensions/BlogpostList/reactions.ts b/blog/loaders/extensions/BlogpostList/ratings.ts similarity index 59% rename from blog/loaders/extensions/BlogpostList/reactions.ts rename to blog/loaders/extensions/BlogpostList/ratings.ts index aa0c9e047..72a9a13ca 100644 --- a/blog/loaders/extensions/BlogpostList/reactions.ts +++ b/blog/loaders/extensions/BlogpostList/ratings.ts @@ -1,13 +1,13 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; import { BlogPost } from "../../../types.ts"; -import { getReactions } from "../../../utils/records.ts"; +import { getRatings } from "../../../utils/records.ts"; /** - * @title ExtensionOf BlogPost list: Reactions + * @title ExtensionOf BlogPost list: Ratings * @description It can harm performance. Use wisely */ -export default function reactionsExt( +export default function ratingsExt( _props: unknown, _req: Request, ctx: AppContext, @@ -17,13 +17,13 @@ export default function reactionsExt( return null; } - const postsWithReactions = await Promise.all( + const postsWithRatings = await Promise.all( posts.map(async (post) => { - const reactions = await getReactions({ post, ctx }); - return { ...post, ...reactions }; + const ratings = await getRatings({ post, ctx }); + return { ...post, ...ratings }; }), ); - return postsWithReactions; + return postsWithRatings; }; } diff --git a/blog/loaders/extensions/BlogpostList/comments.ts b/blog/loaders/extensions/BlogpostList/reviews.ts similarity index 60% rename from blog/loaders/extensions/BlogpostList/comments.ts rename to blog/loaders/extensions/BlogpostList/reviews.ts index 770221a0d..bd3fc46ff 100644 --- a/blog/loaders/extensions/BlogpostList/comments.ts +++ b/blog/loaders/extensions/BlogpostList/reviews.ts @@ -1,13 +1,13 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; import { BlogPost } from "../../../types.ts"; -import { getComments } from "../../../utils/records.ts"; +import { getReviews } from "../../../utils/records.ts"; /** - * @title ExtensionOf BlogPost list: Comments + * @title ExtensionOf BlogPost list: Reviews * @description It can harm performance. Use wisely */ -export default function reactionsExt( +export default function reviewsExt( _props: unknown, _req: Request, ctx: AppContext, @@ -17,13 +17,13 @@ export default function reactionsExt( return null; } - const postsWithReactions = await Promise.all( + const postsWithReviews = await Promise.all( posts.map(async (post) => { - const reactions = await getComments({ post, ctx }); - return { ...post, ...reactions }; + const reviews = await getReviews({ post, ctx }); + return { ...post, ...reviews }; }), ); - return postsWithReactions; + return postsWithReviews; }; } diff --git a/blog/loaders/extensions/BlogpostListing/comments.ts b/blog/loaders/extensions/BlogpostListing/ratings.ts similarity index 64% rename from blog/loaders/extensions/BlogpostListing/comments.ts rename to blog/loaders/extensions/BlogpostListing/ratings.ts index 0f8873838..abe8ddd6d 100644 --- a/blog/loaders/extensions/BlogpostListing/comments.ts +++ b/blog/loaders/extensions/BlogpostListing/ratings.ts @@ -1,13 +1,13 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; import { BlogPostListingPage } from "../../../types.ts"; -import { getComments } from "../../../utils/records.ts"; +import { getRatings } from "../../../utils/records.ts"; /** - * @title ExtensionOf BlogPostPage: Comments + * @title ExtensionOf BlogPostPage: Ratings * @description It can harm performance. Use wisely */ -export default function reactionsExt( +export default function ratingsExt( _props: unknown, _req: Request, ctx: AppContext, @@ -17,16 +17,16 @@ export default function reactionsExt( return null; } - const postsWithReactions = await Promise.all( + const posts = await Promise.all( blogpostListingPage.posts.map(async (post) => { - const reactions = await getComments({ post, ctx }); - return { ...post, ...reactions }; + const ratings = await getRatings({ post, ctx }); + return { ...post, ...ratings }; }), ); return { ...blogpostListingPage, - posts: postsWithReactions, + posts, }; }; } diff --git a/blog/loaders/extensions/BlogpostListing/reactions.ts b/blog/loaders/extensions/BlogpostListing/reviews.ts similarity index 64% rename from blog/loaders/extensions/BlogpostListing/reactions.ts rename to blog/loaders/extensions/BlogpostListing/reviews.ts index 02f5d4e58..ee5919f5d 100644 --- a/blog/loaders/extensions/BlogpostListing/reactions.ts +++ b/blog/loaders/extensions/BlogpostListing/reviews.ts @@ -1,13 +1,13 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; import { BlogPostListingPage } from "../../../types.ts"; -import { getReactions } from "../../../utils/records.ts"; +import { getReviews } from "../../../utils/records.ts"; /** - * @title ExtensionOf BlogPostPage: Reactions + * @title ExtensionOf BlogPostPage: Reviews * @description It can harm performance. Use wisely */ -export default function reactionsExt( +export default function reviewsExt( _props: unknown, _req: Request, ctx: AppContext, @@ -17,16 +17,16 @@ export default function reactionsExt( return null; } - const postsWithReactions = await Promise.all( + const posts = await Promise.all( blogpostListingPage.posts.map(async (post) => { - const reactions = await getReactions({ post, ctx }); - return { ...post, ...reactions }; + const reviews = await getReviews({ post, ctx }); + return { ...post, ...reviews }; }), ); return { ...blogpostListingPage, - posts: postsWithReactions, + posts, }; }; } diff --git a/blog/loaders/extensions/BlogpostPage/comments.ts b/blog/loaders/extensions/BlogpostPage/ratings.ts similarity index 69% rename from blog/loaders/extensions/BlogpostPage/comments.ts rename to blog/loaders/extensions/BlogpostPage/ratings.ts index 1377b076b..bbd6b385b 100644 --- a/blog/loaders/extensions/BlogpostPage/comments.ts +++ b/blog/loaders/extensions/BlogpostPage/ratings.ts @@ -1,13 +1,13 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; import { BlogPostPage } from "../../../types.ts"; -import { getComments } from "../../../utils/records.ts"; +import { getRatings } from "../../../utils/records.ts"; /** - * @title ExtensionOf BlogPostPage: Comments + * @title ExtensionOf BlogPostPage: Ratings * @description It can harm performance. Use wisely */ -export default function reactionsExt( +export default function ratingsExt( _props: unknown, _req: Request, ctx: AppContext, @@ -16,7 +16,7 @@ export default function reactionsExt( if (!blogpostPage) { return null; } - const post = await getComments({ post: blogpostPage.post, ctx }); + const post = await getRatings({ post: blogpostPage.post, ctx }); return { ...blogpostPage, post }; }; } diff --git a/blog/loaders/extensions/BlogpostPage/reactions.ts b/blog/loaders/extensions/BlogpostPage/reviews.ts similarity index 68% rename from blog/loaders/extensions/BlogpostPage/reactions.ts rename to blog/loaders/extensions/BlogpostPage/reviews.ts index 8d8e5145c..ca5481142 100644 --- a/blog/loaders/extensions/BlogpostPage/reactions.ts +++ b/blog/loaders/extensions/BlogpostPage/reviews.ts @@ -1,13 +1,13 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; import { BlogPostPage } from "../../../types.ts"; -import { getReactions } from "../../../utils/records.ts"; +import { getReviews } from "../../../utils/records.ts"; /** - * @title ExtensionOf BlogPostPage: Reactions + * @title ExtensionOf BlogPostPage: Reviews * @description It can harm performance. Use wisely */ -export default function reactionsExt( +export default function reviewsExt( _props: unknown, _req: Request, ctx: AppContext, @@ -16,7 +16,7 @@ export default function reactionsExt( if (!blogpostPage) { return null; } - const post = await getReactions({ post: blogpostPage.post, ctx }); + const post = await getReviews({ post: blogpostPage.post, ctx }); return { ...blogpostPage, post }; }; } diff --git a/blog/manifest.gen.ts b/blog/manifest.gen.ts index 612d1d156..bb0e9f8e2 100644 --- a/blog/manifest.gen.ts +++ b/blog/manifest.gen.ts @@ -2,8 +2,8 @@ // This file SHOULD be checked into source version control. // This file is automatically updated during development when running `dev.ts`. -import * as $$$$$$$$$0 from "./actions/submitComment.ts"; -import * as $$$$$$$$$1 from "./actions/submitReaction.ts"; +import * as $$$$$$$$$0 from "./actions/submitRating.ts"; +import * as $$$$$$$$$1 from "./actions/submitReview.ts"; import * as $$$0 from "./loaders/Author.ts"; import * as $$$3 from "./loaders/Blogpost.ts"; import * as $$$1 from "./loaders/BlogPostItem.ts"; @@ -12,14 +12,14 @@ import * as $$$5 from "./loaders/BlogpostListing.ts"; import * as $$$2 from "./loaders/BlogPostPage.ts"; import * as $$$6 from "./loaders/Category.ts"; import * as $$$8 from "./loaders/extensions/BlogpostList.ts"; -import * as $$$9 from "./loaders/extensions/BlogpostList/comments.ts"; -import * as $$$10 from "./loaders/extensions/BlogpostList/reactions.ts"; +import * as $$$9 from "./loaders/extensions/BlogpostList/ratings.ts"; +import * as $$$10 from "./loaders/extensions/BlogpostList/reviews.ts"; import * as $$$11 from "./loaders/extensions/BlogpostListing.ts"; -import * as $$$12 from "./loaders/extensions/BlogpostListing/comments.ts"; -import * as $$$13 from "./loaders/extensions/BlogpostListing/reactions.ts"; +import * as $$$12 from "./loaders/extensions/BlogpostListing/ratings.ts"; +import * as $$$13 from "./loaders/extensions/BlogpostListing/reviews.ts"; import * as $$$14 from "./loaders/extensions/BlogpostPage.ts"; -import * as $$$15 from "./loaders/extensions/BlogpostPage/comments.ts"; -import * as $$$16 from "./loaders/extensions/BlogpostPage/reactions.ts"; +import * as $$$15 from "./loaders/extensions/BlogpostPage/ratings.ts"; +import * as $$$16 from "./loaders/extensions/BlogpostPage/reviews.ts"; import * as $$$7 from "./loaders/GetCategories.ts"; import * as $$$$$$0 from "./sections/Seo/SeoBlogPost.tsx"; import * as $$$$$$1 from "./sections/Seo/SeoBlogPostListing.tsx"; @@ -35,14 +35,14 @@ const manifest = { "blog/loaders/BlogPostPage.ts": $$$2, "blog/loaders/Category.ts": $$$6, "blog/loaders/extensions/BlogpostList.ts": $$$8, - "blog/loaders/extensions/BlogpostList/comments.ts": $$$9, - "blog/loaders/extensions/BlogpostList/reactions.ts": $$$10, + "blog/loaders/extensions/BlogpostList/ratings.ts": $$$9, + "blog/loaders/extensions/BlogpostList/reviews.ts": $$$10, "blog/loaders/extensions/BlogpostListing.ts": $$$11, - "blog/loaders/extensions/BlogpostListing/comments.ts": $$$12, - "blog/loaders/extensions/BlogpostListing/reactions.ts": $$$13, + "blog/loaders/extensions/BlogpostListing/ratings.ts": $$$12, + "blog/loaders/extensions/BlogpostListing/reviews.ts": $$$13, "blog/loaders/extensions/BlogpostPage.ts": $$$14, - "blog/loaders/extensions/BlogpostPage/comments.ts": $$$15, - "blog/loaders/extensions/BlogpostPage/reactions.ts": $$$16, + "blog/loaders/extensions/BlogpostPage/ratings.ts": $$$15, + "blog/loaders/extensions/BlogpostPage/reviews.ts": $$$16, "blog/loaders/GetCategories.ts": $$$7, }, "sections": { @@ -51,8 +51,8 @@ const manifest = { "blog/sections/Template.tsx": $$$$$$2, }, "actions": { - "blog/actions/submitComment.ts": $$$$$$$$$0, - "blog/actions/submitReaction.ts": $$$$$$$$$1, + "blog/actions/submitRating.ts": $$$$$$$$$0, + "blog/actions/submitReview.ts": $$$$$$$$$1, }, "name": "blog", "baseUrl": import.meta.url, diff --git a/blog/types.ts b/blog/types.ts index 0a5c09979..8311a170f 100644 --- a/blog/types.ts +++ b/blog/types.ts @@ -55,9 +55,11 @@ export interface BlogPost { */ extraProps?: ExtraProps[]; /** @hide true */ - reactions?: Reaction[]; + aggregateRating?: AggregateRating; /** @hide true */ - comments?: ArticleComment[]; + review?: Review[]; + /** @hide true */ + contentRating?: Rating[]; } export interface ExtraProps { @@ -91,17 +93,54 @@ export interface BlogPostListingPage { seo: Seo; } -export interface Reaction extends PersonAndDate { - action: "like" | "deslike" | "invalid"; +export interface Review { + "@type": "Review"; + id?: string; + /** Author of the */ + author?: Person; + /** The date that the review was published, in ISO 8601 date format.*/ + datePublished?: string; + /** The date that the review was modified, in ISO 8601 date format.*/ + dateModified?: string; + /** The item that is being reviewed/rated. */ + itemReviewed?: string; + /** Emphasis part of the review */ + reviewHeadline?: string; + /** The actual body of the review. */ + reviewBody?: string; + /** Review status */ + additionalType?: string; + /** Anonymous comment. Not in Schema.org */ + isAnonymous?: boolean; } -export interface ArticleComment extends PersonAndDate { - comment: string; - status: "submited" | "aproved" | "declined" | "deleted"; +export interface Rating { + "@type": "Rating"; + id?: string; + /** The author of this content or rating. Please note that author is special in that HTML 5 provides a special mechanism for indicating authorship via the rel tag. That is equivalent to this and may be used interchangeably. */ + author?: Person; + /** The item that is being reviewed/rated. */ + itemReviewed?: string; + /** The highest value allowed in this rating system. */ + bestRating?: number; + /** The lowest value allowed in this rating system. */ + worstRating?: number; + /** A short explanation (e.g. one to two sentences) providing background context and other information that led to the conclusion expressed in the rating. This is particularly applicable to ratings associated with "fact check" markup using ClaimReview. */ + ratingValue?: number; + /** Review status */ + additionalType?: string; } -export interface PersonAndDate { - person: Person; - datePublished: string; - dateModified: string; +export interface AggregateRating { + "@type": "AggregateRating"; + /** The count of total number of ratings. */ + ratingCount?: number; + /** The count of total number of reviews. */ + reviewCount?: number; + /** The rating for the content. */ + ratingValue?: number; + /** The highest value allowed in this rating system. */ + bestRating?: number; + /** The lowest value allowed in this rating system. */ + worstRating?: number; } diff --git a/blog/utils/constants.ts b/blog/utils/constants.ts index c3b3c11a9..20c5ad2ba 100644 --- a/blog/utils/constants.ts +++ b/blog/utils/constants.ts @@ -1,13 +1 @@ -import { Reaction } from "../types.ts"; - export const VALID_SORT_ORDERS = ["asc", "desc"]; - -export const REACTIONS_MOCK: Reaction[] = [{ - person: { - email: "elaine.santo@electrolux.com", - name: "Elaine", - }, - datePublished: "2024-09-13T12:07:29.207Z", - dateModified: "2024-09-13T12:07:46.120Z", - action: "like", -}]; diff --git a/blog/utils/records.ts b/blog/utils/records.ts index dee047243..4a3b1340c 100644 --- a/blog/utils/records.ts +++ b/blog/utils/records.ts @@ -1,9 +1,10 @@ -import { comments, CommentsSchema, reactions } from "../db/schema.ts"; +import { rating, review } from "../db/schema.ts"; import { AppContext } from "../mod.ts"; import { type Resolvable } from "@deco/deco"; import { eq } from "https://esm.sh/drizzle-orm@0.30.10"; -import { ArticleComment, BlogPost, Reaction } from "../types.ts"; +import { BlogPost, Rating, Review } from "../types.ts"; import { logger } from "@deco/deco/o11y"; + export async function getRecordsByPath( ctx: AppContext, path: string, @@ -18,98 +19,131 @@ export async function getRecordsByPath( return (current as Record[]).map((item) => item[accessor]); } -export async function getReactionFromSlug( +export async function getRatingsBySlug( { ctx, slug }: { ctx: AppContext; slug: string }, -): Promise { +): Promise { const records = await ctx.invoke.records.loaders.drizzle(); try { - const currentReactions = await records.select({ - postSlug: reactions.postSlug, - person: reactions.person, - datePublished: reactions.datePublished, - dateModified: reactions.dateModified, - action: reactions.action, + const currentRatings = await records.select({ + id: rating.id, + itemReviewed: rating.itemReviewed, + author: rating.author, + ratingValue: rating.ratingValue, + additionalType: rating.additionalType, }) - .from(reactions).where(eq(reactions.postSlug, slug)) as Reaction[]; + .from(rating).where(eq(rating.itemReviewed, slug)) as + | Rating[] + | undefined; - return currentReactions; + return currentRatings?.length === 0 || !currentRatings + ? [] + : currentRatings.map((rating) => ({ + ...rating, + bestRating: 5, + worstRating: 1, + })); } catch (e) { logger.error(e); return []; } } -export async function getReactions( +export async function getRatings( { ctx, post }: { ctx: AppContext; post: BlogPost }, ): Promise { - const currentReactions = await getReactionFromSlug({ + const contentRating = await getRatingsBySlug({ ctx, slug: post.slug, }); + const ratingValue = contentRating.length === 0 ? 0 : contentRating.reduce( + (acc, rating) => acc = acc + rating!.ratingValue!, + 0, + ) / contentRating.length; + return { ...post, - reactions: currentReactions, + contentRating, + aggregateRating: { + ...post.aggregateRating, + "@type": "AggregateRating", + ratingCount: contentRating.length, + bestRating: 5, + worstRating: 1, + ratingValue, + }, }; } -export async function getCommentsFromSlug( +export const getReviewById = async ( + { ctx, id }: { ctx: AppContext; id?: string }, +): Promise => { + if (!id) { + return null; + } + const records = await ctx.invoke.records.loaders.drizzle(); + try { + const targetReview = await records.select({ + itemReviewed: review.itemReviewed, + author: review.author, + datePublished: review.datePublished, + dateModified: review.dateModified, + reviewBody: review.reviewBody, + reviewHeadline: review.reviewHeadline, + isAnonymous: review.isAnonymous, + additionalType: review.additionalType, + id: review.id, + }) + .from(review).where(eq(review.id, id)).get() as Review | undefined; + return targetReview ?? null; + } catch (e) { + logger.error(e); + return null; + } +}; + +export async function getReviewsBySlug( { ctx, slug }: { ctx: AppContext; slug: string }, -): Promise { +): Promise { const records = await ctx.invoke.records.loaders.drizzle(); try { const currentComments = await records.select({ - postSlug: comments.postSlug, - person: comments.person, - datePublished: comments.datePublished, - dateModified: comments.dateModified, - comment: comments.comment, - status: comments.status, + itemReviewed: review.itemReviewed, + author: review.author, + datePublished: review.datePublished, + dateModified: review.dateModified, + reviewBody: review.reviewBody, + reviewHeadline: review.reviewHeadline, + isAnonymous: review.isAnonymous, + additionalType: review.additionalType, + id: review.id, }) - .from(comments).where(eq(comments.postSlug, slug)) as ArticleComment[]; + .from(review).where(eq(review.itemReviewed, slug)) as + | Review[] + | undefined; - return currentComments; + return currentComments ?? []; } catch (e) { logger.error(e); return []; } } -export async function getComments( +export async function getReviews( { ctx, post }: { ctx: AppContext; post: BlogPost }, ): Promise { - const comments = await getCommentsFromSlug({ + const review = await getReviewsBySlug({ ctx, slug: post.slug, }); return { ...post, - comments, + review, + aggregateRating: { + ...post.aggregateRating, + "@type": "AggregateRating", + reviewCount: review.length, + }, }; } - -export const getCommentById = async ( - { ctx, id }: { ctx: AppContext; id?: string }, -) => { - if (!id) { - return null; - } - const records = await ctx.invoke.records.loaders.drizzle(); - try { - const comment = await records.select({ - postSlug: comments.postSlug, - person: comments.person, - datePublished: comments.datePublished, - dateModified: comments.dateModified, - comment: comments.comment, - status: comments.status, - id: comments.id, - }) - .from(comments).where(eq(comments.id, id)).get() as CommentsSchema; - return comment; - } catch (e) { - logger.error(e); - return null; - } -}; From e270c56f1a80dc8d6a7c8124312f6144ee32e1c8 Mon Sep 17 00:00:00 2001 From: decobot Date: Wed, 18 Sep 2024 19:09:42 -0300 Subject: [PATCH 11/20] feat(blog): extension props --- .../extensions/BlogpostList/ratings.ts | 22 +++++- .../extensions/BlogpostList/reviews.ts | 17 ++++- .../extensions/BlogpostListing/ratings.ts | 24 +++++-- .../extensions/BlogpostListing/reviews.ts | 19 +++-- .../extensions/BlogpostPage/ratings.ts | 17 ++++- .../extensions/BlogpostPage/reviews.ts | 22 +++++- blog/sections/Template.tsx | 8 ++- blog/types.ts | 11 +++ blog/utils/records.ts | 71 +++++++++++++++---- 9 files changed, 176 insertions(+), 35 deletions(-) diff --git a/blog/loaders/extensions/BlogpostList/ratings.ts b/blog/loaders/extensions/BlogpostList/ratings.ts index 72a9a13ca..bfb5a3731 100644 --- a/blog/loaders/extensions/BlogpostList/ratings.ts +++ b/blog/loaders/extensions/BlogpostList/ratings.ts @@ -1,14 +1,25 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; -import { BlogPost } from "../../../types.ts"; +import { BlogPost, Ignore } from "../../../types.ts"; import { getRatings } from "../../../utils/records.ts"; +interface Props { + /** + * @description Ignore ratings in the aggregateRating calc + */ + ignoreRatings?: Ignore; + /** + * @description Return only aggregate rating object + */ + onlyAggregate?: boolean; +} + /** * @title ExtensionOf BlogPost list: Ratings * @description It can harm performance. Use wisely */ export default function ratingsExt( - _props: unknown, + { ignoreRatings, onlyAggregate }: Props, _req: Request, ctx: AppContext, ): ExtensionOf { @@ -19,7 +30,12 @@ export default function ratingsExt( const postsWithRatings = await Promise.all( posts.map(async (post) => { - const ratings = await getRatings({ post, ctx }); + const ratings = await getRatings({ + post, + ctx, + ignoreRatings, + onlyAggregate, + }); return { ...post, ...ratings }; }), ); diff --git a/blog/loaders/extensions/BlogpostList/reviews.ts b/blog/loaders/extensions/BlogpostList/reviews.ts index bd3fc46ff..83df367fe 100644 --- a/blog/loaders/extensions/BlogpostList/reviews.ts +++ b/blog/loaders/extensions/BlogpostList/reviews.ts @@ -1,14 +1,25 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; -import { BlogPost } from "../../../types.ts"; +import { BlogPost, Ignore } from "../../../types.ts"; import { getReviews } from "../../../utils/records.ts"; +interface Props { + /** + * @description Ignore specific reviews + */ + ignoreReviews?: Ignore; + /** + * @description Order By + */ + orderBy?: "date_asc" | "date_desc"; +} + /** * @title ExtensionOf BlogPost list: Reviews * @description It can harm performance. Use wisely */ export default function reviewsExt( - _props: unknown, + { ignoreReviews, orderBy }: Props, _req: Request, ctx: AppContext, ): ExtensionOf { @@ -19,7 +30,7 @@ export default function reviewsExt( const postsWithReviews = await Promise.all( posts.map(async (post) => { - const reviews = await getReviews({ post, ctx }); + const reviews = await getReviews({ post, ctx, ignoreReviews, orderBy }); return { ...post, ...reviews }; }), ); diff --git a/blog/loaders/extensions/BlogpostListing/ratings.ts b/blog/loaders/extensions/BlogpostListing/ratings.ts index abe8ddd6d..2d33e7e21 100644 --- a/blog/loaders/extensions/BlogpostListing/ratings.ts +++ b/blog/loaders/extensions/BlogpostListing/ratings.ts @@ -1,14 +1,25 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; -import { BlogPostListingPage } from "../../../types.ts"; +import { BlogPostListingPage, Ignore } from "../../../types.ts"; import { getRatings } from "../../../utils/records.ts"; +interface Props { + /** + * @description Ignore ratings in the aggregateRating calc + */ + ignoreRatings?: Ignore; + /** + * @description Return only aggregate rating object + */ + onlyAggregate?: boolean; +} + /** - * @title ExtensionOf BlogPostPage: Ratings + * @title ExtensionOf BlogPostListing: Ratings * @description It can harm performance. Use wisely */ export default function ratingsExt( - _props: unknown, + { ignoreRatings, onlyAggregate }: Props, _req: Request, ctx: AppContext, ): ExtensionOf { @@ -19,7 +30,12 @@ export default function ratingsExt( const posts = await Promise.all( blogpostListingPage.posts.map(async (post) => { - const ratings = await getRatings({ post, ctx }); + const ratings = await getRatings({ + post, + ctx, + onlyAggregate, + ignoreRatings, + }); return { ...post, ...ratings }; }), ); diff --git a/blog/loaders/extensions/BlogpostListing/reviews.ts b/blog/loaders/extensions/BlogpostListing/reviews.ts index ee5919f5d..f91a84b4b 100644 --- a/blog/loaders/extensions/BlogpostListing/reviews.ts +++ b/blog/loaders/extensions/BlogpostListing/reviews.ts @@ -1,14 +1,25 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; -import { BlogPostListingPage } from "../../../types.ts"; +import { BlogPostListingPage, Ignore } from "../../../types.ts"; import { getReviews } from "../../../utils/records.ts"; +interface Props { + /** + * @description Ignore specific reviews + */ + ignoreReviews?: Ignore; + /** + * @description Order By + */ + orderBy?: "date_asc" | "date_desc"; +} + /** - * @title ExtensionOf BlogPostPage: Reviews + * @title ExtensionOf BlogPostListing: Reviews * @description It can harm performance. Use wisely */ export default function reviewsExt( - _props: unknown, + { ignoreReviews, orderBy }: Props, _req: Request, ctx: AppContext, ): ExtensionOf { @@ -19,7 +30,7 @@ export default function reviewsExt( const posts = await Promise.all( blogpostListingPage.posts.map(async (post) => { - const reviews = await getReviews({ post, ctx }); + const reviews = await getReviews({ post, ctx, ignoreReviews, orderBy }); return { ...post, ...reviews }; }), ); diff --git a/blog/loaders/extensions/BlogpostPage/ratings.ts b/blog/loaders/extensions/BlogpostPage/ratings.ts index bbd6b385b..bf4f66083 100644 --- a/blog/loaders/extensions/BlogpostPage/ratings.ts +++ b/blog/loaders/extensions/BlogpostPage/ratings.ts @@ -1,14 +1,21 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; -import { BlogPostPage } from "../../../types.ts"; +import { BlogPostPage, Ignore } from "../../../types.ts"; import { getRatings } from "../../../utils/records.ts"; +interface Props { + /** + * @description Ignore ratings in the aggregateRating calc + */ + ignoreRatings?: Ignore; +} + /** * @title ExtensionOf BlogPostPage: Ratings * @description It can harm performance. Use wisely */ export default function ratingsExt( - _props: unknown, + { ignoreRatings }: Props, _req: Request, ctx: AppContext, ): ExtensionOf { @@ -16,7 +23,11 @@ export default function ratingsExt( if (!blogpostPage) { return null; } - const post = await getRatings({ post: blogpostPage.post, ctx }); + const post = await getRatings({ + post: blogpostPage.post, + ctx, + ignoreRatings, + }); return { ...blogpostPage, post }; }; } diff --git a/blog/loaders/extensions/BlogpostPage/reviews.ts b/blog/loaders/extensions/BlogpostPage/reviews.ts index ca5481142..6b8f08c20 100644 --- a/blog/loaders/extensions/BlogpostPage/reviews.ts +++ b/blog/loaders/extensions/BlogpostPage/reviews.ts @@ -1,14 +1,25 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; -import { BlogPostPage } from "../../../types.ts"; +import { BlogPostPage, Ignore } from "../../../types.ts"; import { getReviews } from "../../../utils/records.ts"; +interface Props { + /** + * @description Ignore specific reviews + */ + ignoreReviews?: Ignore; + /** + * @description Order By + */ + orderBy?: "date_asc" | "date_desc"; +} + /** * @title ExtensionOf BlogPostPage: Reviews * @description It can harm performance. Use wisely */ export default function reviewsExt( - _props: unknown, + { ignoreReviews, orderBy }: Props, _req: Request, ctx: AppContext, ): ExtensionOf { @@ -16,7 +27,12 @@ export default function reviewsExt( if (!blogpostPage) { return null; } - const post = await getReviews({ post: blogpostPage.post, ctx }); + const post = await getReviews({ + post: blogpostPage.post, + ctx, + ignoreReviews, + orderBy, + }); return { ...blogpostPage, post }; }; } diff --git a/blog/sections/Template.tsx b/blog/sections/Template.tsx index 8dcb278fe..4ee094d3b 100644 --- a/blog/sections/Template.tsx +++ b/blog/sections/Template.tsx @@ -14,7 +14,7 @@ export default function Template({ post }: Props) { excerpt = "Excerpt", date, image, - alt + alt, } = post; return ( @@ -33,7 +33,11 @@ export default function Template({ post }: Props) { : ""}

{image && ( - {alt + {alt )}
diff --git a/blog/types.ts b/blog/types.ts index 491c0d036..97730a1e1 100644 --- a/blog/types.ts +++ b/blog/types.ts @@ -148,3 +148,14 @@ export interface AggregateRating { /** The lowest value allowed in this rating system. */ worstRating?: number; } + +export interface Ignore { + /** + * @title Active + */ + active?: boolean; + /** + * @title When additionalType is marked with: + */ + markedAs?: string[]; +} diff --git a/blog/utils/records.ts b/blog/utils/records.ts index 4a3b1340c..d22bbab79 100644 --- a/blog/utils/records.ts +++ b/blog/utils/records.ts @@ -1,8 +1,14 @@ import { rating, review } from "../db/schema.ts"; import { AppContext } from "../mod.ts"; import { type Resolvable } from "@deco/deco"; -import { eq } from "https://esm.sh/drizzle-orm@0.30.10"; -import { BlogPost, Rating, Review } from "../types.ts"; +import { + and, + asc, + desc, + eq, + notInArray, +} from "https://esm.sh/drizzle-orm@0.30.10"; +import { BlogPost, Ignore, Rating, Review } from "../types.ts"; import { logger } from "@deco/deco/o11y"; export async function getRecordsByPath( @@ -49,28 +55,44 @@ export async function getRatingsBySlug( } export async function getRatings( - { ctx, post }: { ctx: AppContext; post: BlogPost }, + { ctx, post, ignoreRatings, onlyAggregate }: { + ctx: AppContext; + post: BlogPost; + ignoreRatings?: Ignore; + onlyAggregate?: boolean; + }, ): Promise { const contentRating = await getRatingsBySlug({ ctx, slug: post.slug, }); - const ratingValue = contentRating.length === 0 ? 0 : contentRating.reduce( - (acc, rating) => acc = acc + rating!.ratingValue!, - 0, - ) / contentRating.length; + const { ratingCount, ratingTotal } = contentRating.length === 0 + ? { ratingCount: 0, ratingTotal: 0 } + : contentRating.reduce( + (acc, { ratingValue, additionalType }) => + ignoreRatings?.active && additionalType && + ignoreRatings.markedAs?.includes(additionalType) + ? acc + : { + ratingCount: acc.ratingCount + 1, + ratingTotal: acc.ratingTotal + (ratingValue ?? 0), + }, + { ratingCount: 0, ratingTotal: 0 }, + ); + + const ratingValue = ratingTotal / ratingCount; return { ...post, - contentRating, + contentRating: onlyAggregate ? undefined : contentRating, aggregateRating: { ...post.aggregateRating, "@type": "AggregateRating", - ratingCount: contentRating.length, + ratingCount, + ratingValue: isNaN(ratingValue) ? 0 : ratingValue, bestRating: 5, worstRating: 1, - ratingValue, }, }; } @@ -103,9 +125,26 @@ export const getReviewById = async ( }; export async function getReviewsBySlug( - { ctx, slug }: { ctx: AppContext; slug: string }, + { ctx, slug, ignoreReviews, orderBy = "date_desc" }: { + ctx: AppContext; + slug: string; + ignoreReviews?: Ignore; + orderBy?: "date_asc" | "date_desc"; + }, ): Promise { const records = await ctx.invoke.records.loaders.drizzle(); + + const whereClause = ignoreReviews?.active && ignoreReviews?.markedAs && + ignoreReviews?.markedAs?.length > 0 + ? and( + eq(review.itemReviewed, slug), + notInArray(review.additionalType, ignoreReviews.markedAs), + ) + : eq(review.itemReviewed, slug); + const orderClause = orderBy.endsWith("desc") + ? desc(review.datePublished) + : asc(review.datePublished); + try { const currentComments = await records.select({ itemReviewed: review.itemReviewed, @@ -118,7 +157,7 @@ export async function getReviewsBySlug( additionalType: review.additionalType, id: review.id, }) - .from(review).where(eq(review.itemReviewed, slug)) as + .from(review).where(whereClause).orderBy(orderClause) as | Review[] | undefined; @@ -130,11 +169,17 @@ export async function getReviewsBySlug( } export async function getReviews( - { ctx, post }: { ctx: AppContext; post: BlogPost }, + { ctx, post, ...rest }: { + ctx: AppContext; + post: BlogPost; + ignoreReviews?: Ignore; + orderBy?: "date_asc" | "date_desc"; + }, ): Promise { const review = await getReviewsBySlug({ ctx, slug: post.slug, + ...rest, }); return { From 88851719ce9928bd78b551a4a12b0d997e94b48b Mon Sep 17 00:00:00 2001 From: decobot Date: Mon, 23 Sep 2024 15:02:42 -0300 Subject: [PATCH 12/20] feat(blogpost): new types for carousel --- admin/widgets.ts | 1 - blog/types.ts | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/admin/widgets.ts b/admin/widgets.ts index dda7b3c32..6ed9942ee 100644 --- a/admin/widgets.ts +++ b/admin/widgets.ts @@ -69,4 +69,3 @@ export type CSVWidget = string; * @accept application/pdf */ export type PDFWidget = string; - diff --git a/blog/types.ts b/blog/types.ts index 97730a1e1..f420dbbbc 100644 --- a/blog/types.ts +++ b/blog/types.ts @@ -21,6 +21,9 @@ export interface Category { export interface BlogPost { title: string; excerpt: string; + /** + * @title Main image + */ image?: ImageWidget; /** * @title Alt text for the image @@ -46,6 +49,11 @@ export interface BlogPost { * @format rich-text */ content: string; + /** + * @title Carousel in post content + * @description add a carousel in the middle of the post. Must be implemented in frontEnd + */ + imageCarousel?: ImageCarousel; /** * @title SEO */ @@ -97,6 +105,11 @@ export interface BlogPostListingPage { seo: Seo; } +export interface ImageCarousel { + banners?: Banner[]; + description?: string; +} + export interface Review { "@type": "Review"; id?: string; @@ -159,3 +172,22 @@ export interface Ignore { */ markedAs?: string[]; } + +export interface BannerItem { + image?: ImageWidget; + width?: number; + height?: number; +} + +export interface Banner { + /** @description desktop otimized image */ + desktop: BannerItem; + /** @description mobile otimized image */ + mobile: BannerItem; + /** @description Image's alt text */ + alt: string; + action?: { + /** @description when user clicks on the image, go to this link */ + href: string; + }; +} From 6627bf487a8719615b59a01932e3279692506861 Mon Sep 17 00:00:00 2001 From: decobot Date: Mon, 7 Oct 2024 11:24:56 -0300 Subject: [PATCH 13/20] feat(blogextensions): update database script --- blog/scripts/updateDatabase.ts | 23 +++++++++++++++++++ .../storefront/storefront.graphql.gen.ts | 17 ++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 blog/scripts/updateDatabase.ts diff --git a/blog/scripts/updateDatabase.ts b/blog/scripts/updateDatabase.ts new file mode 100644 index 000000000..d8cd56201 --- /dev/null +++ b/blog/scripts/updateDatabase.ts @@ -0,0 +1,23 @@ +import { dirname, fromFileUrl, resolve } from "std/path/mod.ts"; +import { copy } from "std/fs/copy.ts"; + +// Script location +const __dirname = dirname(fromFileUrl(import.meta.url)); + +//Database location +const sourceFilePath = resolve(__dirname, "../db/schema.ts"); + +//Destiny +const destinationDir = resolve(Deno.cwd(), "db"); + +// Destiny full path +const destinationFilePath = resolve(destinationDir, "schema.ts"); + +await Deno.mkdir(destinationDir, { recursive: true }); + +try { + await copy(sourceFilePath, destinationFilePath); + console.log(`Database successfully copied to: ${destinationFilePath}`); +} catch (error) { + console.error("Error in file copy:", error); +} diff --git a/shopify/utils/storefront/storefront.graphql.gen.ts b/shopify/utils/storefront/storefront.graphql.gen.ts index 286430b30..f4038d73e 100644 --- a/shopify/utils/storefront/storefront.graphql.gen.ts +++ b/shopify/utils/storefront/storefront.graphql.gen.ts @@ -7704,6 +7704,8 @@ export type FilterFragment = { id: string, label: string, type: FilterType, valu export type CartFragment = { id: string, checkoutUrl: any, totalQuantity: number, lines: { nodes: Array<{ id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string }, price: { amount: any, currencyCode: CurrencyCode } }, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string }, price: { amount: any, currencyCode: CurrencyCode } }, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } }> }, cost: { subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array<{ discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } }> }; +export type CustomerFragment = { id: string, email?: string | null, firstName?: string | null, lastName?: string | null }; + export type CreateCartMutationVariables = Exact<{ [key: string]: never; }>; @@ -7767,6 +7769,13 @@ export type ProductRecommendationsQueryVariables = Exact<{ export type ProductRecommendationsQuery = { productRecommendations?: Array<{ availableForSale: boolean, createdAt: any, description: string, descriptionHtml: any, handle: string, id: string, isGiftCard: boolean, onlineStoreUrl?: any | null, productType: string, publishedAt: any, requiresSellingPlan: boolean, tags: Array, title: string, totalInventory?: number | null, updatedAt: any, vendor: string, featuredImage?: { altText?: string | null, url: any } | null, images: { nodes: Array<{ altText?: string | null, url: any }> }, media: { nodes: Array<{ alt?: string | null, mediaContentType: MediaContentType, previewImage?: { altText?: string | null, url: any } | null } | { alt?: string | null, mediaContentType: MediaContentType, previewImage?: { altText?: string | null, url: any } | null } | { alt?: string | null, mediaContentType: MediaContentType, previewImage?: { altText?: string | null, url: any } | null } | { alt?: string | null, mediaContentType: MediaContentType, previewImage?: { altText?: string | null, url: any } | null }> }, options: Array<{ name: string, values: Array }>, priceRange: { minVariantPrice: { amount: any, currencyCode: CurrencyCode }, maxVariantPrice: { amount: any, currencyCode: CurrencyCode } }, seo: { title?: string | null, description?: string | null }, variants: { nodes: Array<{ availableForSale: boolean, barcode?: string | null, currentlyNotInStock: boolean, id: string, quantityAvailable?: number | null, requiresShipping: boolean, sku?: string | null, title: string, weight?: number | null, weightUnit: WeightUnit, compareAtPrice?: { amount: any, currencyCode: CurrencyCode } | null, image?: { altText?: string | null, url: any } | null, price: { amount: any, currencyCode: CurrencyCode }, selectedOptions: Array<{ name: string, value: string }>, unitPrice?: { amount: any, currencyCode: CurrencyCode } | null, unitPriceMeasurement?: { measuredType?: UnitPriceMeasurementMeasuredType | null, quantityValue: number, referenceUnit?: UnitPriceMeasurementMeasuredUnit | null, quantityUnit?: UnitPriceMeasurementMeasuredUnit | null } | null }> }, collections: { nodes: Array<{ description: string, descriptionHtml: any, handle: string, id: string, title: string, updatedAt: any, image?: { altText?: string | null, url: any } | null }> } }> | null }; +export type FetchCustomerInfoQueryVariables = Exact<{ + customerAccessToken: Scalars['String']['input']; +}>; + + +export type FetchCustomerInfoQuery = { customer?: { id: string, email?: string | null, firstName?: string | null, lastName?: string | null } | null }; + export type AddItemToCartMutationVariables = Exact<{ cartId: Scalars['ID']['input']; lines: Array | CartLineInput; @@ -7790,3 +7799,11 @@ export type UpdateItemsMutationVariables = Exact<{ export type UpdateItemsMutation = { payload?: { cart?: { id: string, checkoutUrl: any, totalQuantity: number, lines: { nodes: Array<{ id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string }, price: { amount: any, currencyCode: CurrencyCode } }, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } } | { id: string, quantity: number, merchandise: { id: string, title: string, image?: { url: any, altText?: string | null } | null, product: { title: string }, price: { amount: any, currencyCode: CurrencyCode } }, cost: { totalAmount: { amount: any, currencyCode: CurrencyCode }, subtotalAmount: { amount: any, currencyCode: CurrencyCode }, amountPerQuantity: { amount: any, currencyCode: CurrencyCode }, compareAtAmountPerQuantity?: { amount: any, currencyCode: CurrencyCode } | null } }> }, cost: { subtotalAmount: { amount: any, currencyCode: CurrencyCode }, totalAmount: { amount: any, currencyCode: CurrencyCode }, checkoutChargeAmount: { amount: any, currencyCode: CurrencyCode } }, discountCodes: Array<{ code: string, applicable: boolean }>, discountAllocations: Array<{ discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } } | { discountedAmount: { amount: any, currencyCode: CurrencyCode } }> } | null } | null }; + +export type SignInWithEmailAndPasswordMutationVariables = Exact<{ + email: Scalars['String']['input']; + password: Scalars['String']['input']; +}>; + + +export type SignInWithEmailAndPasswordMutation = { customerAccessTokenCreate?: { customerAccessToken?: { accessToken: string, expiresAt: any } | null, customerUserErrors: Array<{ code?: CustomerErrorCode | null, message: string }> } | null }; From af2a9552b348cab59b9b094bc35b207fd34fc101 Mon Sep 17 00:00:00 2001 From: decobot Date: Mon, 7 Oct 2024 12:09:53 -0300 Subject: [PATCH 14/20] feat(blogextensions): script workaround --- blog/scripts/updateDatabase.ts | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 blog/scripts/updateDatabase.ts diff --git a/blog/scripts/updateDatabase.ts b/blog/scripts/updateDatabase.ts deleted file mode 100644 index d8cd56201..000000000 --- a/blog/scripts/updateDatabase.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { dirname, fromFileUrl, resolve } from "std/path/mod.ts"; -import { copy } from "std/fs/copy.ts"; - -// Script location -const __dirname = dirname(fromFileUrl(import.meta.url)); - -//Database location -const sourceFilePath = resolve(__dirname, "../db/schema.ts"); - -//Destiny -const destinationDir = resolve(Deno.cwd(), "db"); - -// Destiny full path -const destinationFilePath = resolve(destinationDir, "schema.ts"); - -await Deno.mkdir(destinationDir, { recursive: true }); - -try { - await copy(sourceFilePath, destinationFilePath); - console.log(`Database successfully copied to: ${destinationFilePath}`); -} catch (error) { - console.error("Error in file copy:", error); -} From e5ebddef3ca9e323f6111dc070edb55a40ba6989 Mon Sep 17 00:00:00 2001 From: decobot Date: Wed, 30 Oct 2024 11:40:06 -0300 Subject: [PATCH 15/20] feat(most-viewed): order by most viewed started --- blog/db/schema.ts | 5 +++++ blog/types.ts | 10 +++++++++- blog/utils/handlePosts.ts | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/blog/db/schema.ts b/blog/db/schema.ts index 6c46bf33b..33b48daa7 100644 --- a/blog/db/schema.ts +++ b/blog/db/schema.ts @@ -23,3 +23,8 @@ export const review = sqliteTable("review", { additionalType: (text("additionalType")), isAnonymous: integer("isAnonymous", { mode: "boolean" }), }); + +export const postViews = sqliteTable("postViews", { + id: text("id").primaryKey(), + userInteractionCount: integer("userInteractionCount").notNull(), +}); diff --git a/blog/types.ts b/blog/types.ts index f420dbbbc..1cbd24c8b 100644 --- a/blog/types.ts +++ b/blog/types.ts @@ -1,5 +1,5 @@ import { ImageWidget } from "../admin/widgets.ts"; -import { PageInfo, Person } from "../commerce/types.ts"; +import { PageInfo, Person, Thing } from "../commerce/types.ts"; /** * @titleBy name @@ -72,6 +72,8 @@ export interface BlogPost { review?: Review[]; /** @hide true */ contentRating?: Rating[]; + /** @hide true */ + interactionStatistic?: InteractionCounter; } export interface ExtraProps { @@ -162,6 +164,12 @@ export interface AggregateRating { worstRating?: number; } +export interface InteractionCounter extends Omit { + "@type": "InteractionCounter"; + /** The number of interactions or views */ + userInteractionCount?: number; +} + export interface Ignore { /** * @title Active diff --git a/blog/utils/handlePosts.ts b/blog/utils/handlePosts.ts index 405fa325b..ab564d802 100644 --- a/blog/utils/handlePosts.ts +++ b/blog/utils/handlePosts.ts @@ -8,6 +8,10 @@ import { VALID_SORT_ORDERS } from "./constants.ts"; * @param sortBy Sort option (must be: "date_desc" | "date_asc" | "title_asc" | "title_desc" ) */ export const sortPosts = (blogPosts: BlogPost[], sortBy: SortBy) => { + //Sort by most viewed. + //Is drizzle installed? + //Make the req to views database and do semething like an extension. + const splittedSort = sortBy.split("_"); const sortMethod = splittedSort[0] in blogPosts[0] From 6ccb9444135f639319002bd25297efa5e498113c Mon Sep 17 00:00:00 2001 From: decobot Date: Wed, 30 Oct 2024 20:25:43 -0300 Subject: [PATCH 16/20] feat(most-viewed): loader and orderBy finished --- blog/loaders/BlogpostList.ts | 18 +++++--- blog/loaders/BlogpostListing.ts | 38 ++++++++++------- blog/types.ts | 9 +++- blog/utils/handlePosts.ts | 76 ++++++++++++++++++++++++++++----- 4 files changed, 108 insertions(+), 33 deletions(-) diff --git a/blog/loaders/BlogpostList.ts b/blog/loaders/BlogpostList.ts index aca007475..a6d86ed62 100644 --- a/blog/loaders/BlogpostList.ts +++ b/blog/loaders/BlogpostList.ts @@ -6,6 +6,7 @@ * @param ctx - The application context. * @returns A promise that resolves to an array of blog posts. */ +import { logger } from "@deco/deco/o11y"; import { RequestURLParam } from "../../website/functions/requestToParam.ts"; import { AppContext } from "../mod.ts"; import { BlogPost, SortBy } from "../types.ts"; @@ -63,13 +64,18 @@ export default async function BlogPostList( ACCESSOR, ); - const handledPosts = handlePosts(posts, pageSort, slug); + try { + const handledPosts = await handlePosts(posts, pageSort, ctx, slug); - if (!handledPosts) { - return null; - } + if (!handledPosts) { + return null; + } - const slicedPosts = slicePosts(handledPosts, pageNumber, postsPerPage); + const slicedPosts = slicePosts(handledPosts, pageNumber, postsPerPage); - return slicedPosts.length > 0 ? slicedPosts : null; + return slicedPosts.length > 0 ? slicedPosts : null; + } catch (e) { + logger.error(e); + return null; + } } diff --git a/blog/loaders/BlogpostListing.ts b/blog/loaders/BlogpostListing.ts index 926469463..00a536ae7 100644 --- a/blog/loaders/BlogpostListing.ts +++ b/blog/loaders/BlogpostListing.ts @@ -6,6 +6,7 @@ * @param ctx - The application context. * @returns A promise that resolves to an array of blog posts. */ +import { logger } from "@deco/deco/o11y"; import { PageInfo } from "../../commerce/types.ts"; import { RequestURLParam } from "../../website/functions/requestToParam.ts"; import { AppContext } from "../mod.ts"; @@ -65,27 +66,32 @@ export default async function BlogPostList( ACCESSOR, ); - const handledPosts = handlePosts(posts, pageSort, slug); + try { + const handledPosts = await handlePosts(posts, pageSort, ctx, slug); - if (!handledPosts) { - return null; - } + if (!handledPosts) { + return null; + } + + const slicedPosts = slicePosts(handledPosts, pageNumber, postsPerPage); - const slicedPosts = slicePosts(handledPosts, pageNumber, postsPerPage); + if (slicedPosts.length === 0) { + return null; + } - if (slicedPosts.length === 0) { + const category = slicedPosts[0].categories.find((c) => c.slug === slug); + return { + posts: slicedPosts, + pageInfo: toPageInfo(handledPosts, postsPerPage, pageNumber, params), + seo: { + title: category?.name ?? "", + canonical: new URL(url.pathname, url.origin).href, + }, + }; + } catch (e) { + logger.error(e); return null; } - - const category = slicedPosts[0].categories.find((c) => c.slug === slug); - return { - posts: slicedPosts, - pageInfo: toPageInfo(handledPosts, postsPerPage, pageNumber, params), - seo: { - title: category?.name ?? "", - canonical: new URL(url.pathname, url.origin).href, - }, - }; } const toPageInfo = ( diff --git a/blog/types.ts b/blog/types.ts index 1cbd24c8b..0fa27b671 100644 --- a/blog/types.ts +++ b/blog/types.ts @@ -99,7 +99,9 @@ export type SortBy = | "date_desc" | "date_asc" | "title_asc" - | "title_desc"; + | "title_desc" + | "view_asc" + | "view_desc"; export interface BlogPostListingPage { posts: BlogPost[]; @@ -170,6 +172,11 @@ export interface InteractionCounter extends Omit { userInteractionCount?: number; } +export interface ViewFromDatabase { + id: string; + userInteractionCount?: number; +} + export interface Ignore { /** * @title Active diff --git a/blog/utils/handlePosts.ts b/blog/utils/handlePosts.ts index ab564d802..beb02528f 100644 --- a/blog/utils/handlePosts.ts +++ b/blog/utils/handlePosts.ts @@ -1,19 +1,73 @@ -import { BlogPost, SortBy } from "../types.ts"; +import { postViews } from "../db/schema.ts"; +import { AppContext } from "../mod.ts"; +import { BlogPost, SortBy, ViewFromDatabase } from "../types.ts"; import { VALID_SORT_ORDERS } from "./constants.ts"; /** * Returns an sorted BlogPost list * * @param posts Posts to be sorted - * @param sortBy Sort option (must be: "date_desc" | "date_asc" | "title_asc" | "title_desc" ) + * @param sortBy Sort option (must be: "date_desc" | "date_asc" | "title_asc" | "title_desc" | "view_asc" | "view_desc" ) */ -export const sortPosts = (blogPosts: BlogPost[], sortBy: SortBy) => { - //Sort by most viewed. - //Is drizzle installed? - //Make the req to views database and do semething like an extension. - +export const sortPosts = async ( + blogPosts: BlogPost[], + sortBy: SortBy, + ctx: AppContext, +) => { const splittedSort = sortBy.split("_"); + if (splittedSort[0] === "view") { + //If sort is "view_asc" or "view_desc" + + const records = await ctx.invoke.records.loaders.drizzle(); + //Deco records not installed + if (records.__resolveType) { + throw new Error("Deco Records not installed!"); + } + + //Get views from database + const views = await records.select({ + id: postViews.id, + userInteractionCount: postViews.userInteractionCount, + }).from(postViews) as ViewFromDatabase[] | null; + + if (!views) { + return blogPosts; + } + + //Act like a real extension + for (let i = 0; i < views.length; i++) { + const view = views[i]; + const post = blogPosts.findIndex(({ slug }) => slug === view.id); + + if (blogPosts[post]) { + blogPosts[post].interactionStatistic = { + "@type": "InteractionCounter", + userInteractionCount: view.userInteractionCount, + }; + } + } + + const sortOrder = VALID_SORT_ORDERS.includes(splittedSort[1]) + ? splittedSort[1] + : "desc"; + + //Sort and return + return blogPosts.toSorted((a, b) => { + const countOfA = a?.interactionStatistic?.userInteractionCount; + const countOfB = b?.interactionStatistic?.userInteractionCount; + if ( + !countOfA && + !countOfB + ) { + return 0; + } + + const comparison = (countOfA ?? 0) - (countOfB ?? 0); + return sortOrder === "desc" ? comparison : -comparison; + }); + } + const sortMethod = splittedSort[0] in blogPosts[0] ? splittedSort[0] as keyof BlogPost : "date"; @@ -73,12 +127,14 @@ export const slicePosts = ( * Returns an filtered and sorted BlogPost list. It dont slice * * @param posts Posts to be handled - * @param sortBy Sort option (must be: "date_desc" | "date_asc" | "title_asc" | "title_desc" ) + * @param sortBy Sort option (must be: "date_desc" | "date_asc" | "title_asc" | "title_desc") + * @param ctx AppContext * @param slug Category slug to be filter */ -export default function handlePosts( +export default async function handlePosts( posts: BlogPost[], sortBy: SortBy, + ctx: AppContext, slug?: string, ) { const filteredPosts = filterPostsByCategory(posts, slug); @@ -87,5 +143,5 @@ export default function handlePosts( return null; } - return sortPosts(filteredPosts, sortBy); + return await sortPosts(filteredPosts, sortBy, ctx); } From 5d4660975049b26e1ed1bdef39bc749f5446c407 Mon Sep 17 00:00:00 2001 From: decobot Date: Wed, 30 Oct 2024 20:56:54 -0300 Subject: [PATCH 17/20] feat(most-viewed): action finished --- blog/actions/submitView.ts | 38 ++++++++++++++++++++++++++++++++++++++ blog/manifest.gen.ts | 2 ++ 2 files changed, 40 insertions(+) create mode 100644 blog/actions/submitView.ts diff --git a/blog/actions/submitView.ts b/blog/actions/submitView.ts new file mode 100644 index 000000000..f6cbdf83c --- /dev/null +++ b/blog/actions/submitView.ts @@ -0,0 +1,38 @@ +import { eq } from "https://esm.sh/v135/drizzle-orm@0.30.10"; +import { postViews } from "../db/schema.ts"; +import { AppContext } from "../mod.ts"; +import { ViewFromDatabase } from "../types.ts"; + +export interface Props { + id: string; +} + +export default async function action( + { id }: Props, + _req: Request, + ctx: AppContext, +): Promise<{ count: number }> { + const records = await ctx.invoke.records.loaders.drizzle(); + + const existingRecord = await records.select() + .from(postViews) + .where(eq(postViews.id, id)) + .get() as ViewFromDatabase | null; + + if (!existingRecord) { + await records.insert(postViews).values({ + id, + userInteractionCount: 1, + }); + + return { count: 1 }; + } + + const newCount = existingRecord.userInteractionCount! + 1; + + await records.update(postViews) + .set({ userInteractionCount: newCount }) + .where(eq(postViews.id, id)); + + return { count: newCount }; +} diff --git a/blog/manifest.gen.ts b/blog/manifest.gen.ts index bb0e9f8e2..a4d90dfea 100644 --- a/blog/manifest.gen.ts +++ b/blog/manifest.gen.ts @@ -4,6 +4,7 @@ import * as $$$$$$$$$0 from "./actions/submitRating.ts"; import * as $$$$$$$$$1 from "./actions/submitReview.ts"; +import * as $$$$$$$$$2 from "./actions/submitView.ts"; import * as $$$0 from "./loaders/Author.ts"; import * as $$$3 from "./loaders/Blogpost.ts"; import * as $$$1 from "./loaders/BlogPostItem.ts"; @@ -53,6 +54,7 @@ const manifest = { "actions": { "blog/actions/submitRating.ts": $$$$$$$$$0, "blog/actions/submitReview.ts": $$$$$$$$$1, + "blog/actions/submitView.ts": $$$$$$$$$2, }, "name": "blog", "baseUrl": import.meta.url, From ff7a1d047703525afeb2ac3def6c60d527defc06 Mon Sep 17 00:00:00 2001 From: decobot Date: Wed, 30 Oct 2024 21:04:14 -0300 Subject: [PATCH 18/20] chore(blog): blogposting core logic archives --- blog/actions/submitReview.ts | 2 +- blog/{utils => core}/handlePosts.ts | 2 +- blog/{utils => core}/records.ts | 0 blog/loaders/BlogPostItem.ts | 2 +- blog/loaders/BlogPostPage.ts | 2 +- blog/loaders/BlogpostList.ts | 4 ++-- blog/loaders/BlogpostListing.ts | 4 ++-- blog/loaders/GetCategories.ts | 2 +- blog/loaders/extensions/BlogpostList/ratings.ts | 2 +- blog/loaders/extensions/BlogpostList/reviews.ts | 2 +- blog/loaders/extensions/BlogpostListing/ratings.ts | 2 +- blog/loaders/extensions/BlogpostListing/reviews.ts | 2 +- blog/loaders/extensions/BlogpostPage/ratings.ts | 2 +- blog/loaders/extensions/BlogpostPage/reviews.ts | 2 +- 14 files changed, 15 insertions(+), 15 deletions(-) rename blog/{utils => core}/handlePosts.ts (98%) rename blog/{utils => core}/records.ts (100%) diff --git a/blog/actions/submitReview.ts b/blog/actions/submitReview.ts index ee734fad8..b817018d9 100644 --- a/blog/actions/submitReview.ts +++ b/blog/actions/submitReview.ts @@ -3,7 +3,7 @@ import { Person } from "../../commerce/types.ts"; import { AppContext } from "../mod.ts"; import { logger } from "@deco/deco/o11y"; import { Review } from "../types.ts"; -import { getReviewById } from "../utils/records.ts"; +import { getReviewById } from "../core/records.ts"; import { review } from "../db/schema.ts"; export interface Props { diff --git a/blog/utils/handlePosts.ts b/blog/core/handlePosts.ts similarity index 98% rename from blog/utils/handlePosts.ts rename to blog/core/handlePosts.ts index beb02528f..24604c10d 100644 --- a/blog/utils/handlePosts.ts +++ b/blog/core/handlePosts.ts @@ -1,7 +1,7 @@ import { postViews } from "../db/schema.ts"; import { AppContext } from "../mod.ts"; import { BlogPost, SortBy, ViewFromDatabase } from "../types.ts"; -import { VALID_SORT_ORDERS } from "./constants.ts"; +import { VALID_SORT_ORDERS } from "../utils/constants.ts"; /** * Returns an sorted BlogPost list diff --git a/blog/utils/records.ts b/blog/core/records.ts similarity index 100% rename from blog/utils/records.ts rename to blog/core/records.ts diff --git a/blog/loaders/BlogPostItem.ts b/blog/loaders/BlogPostItem.ts index 1e2680174..69161e918 100644 --- a/blog/loaders/BlogPostItem.ts +++ b/blog/loaders/BlogPostItem.ts @@ -1,6 +1,6 @@ import { AppContext } from "../mod.ts"; import { BlogPost } from "../types.ts"; -import { getRecordsByPath } from "../utils/records.ts"; +import { getRecordsByPath } from "../core/records.ts"; import type { RequestURLParam } from "../../website/functions/requestToParam.ts"; const COLLECTION_PATH = "collections/blog/posts"; diff --git a/blog/loaders/BlogPostPage.ts b/blog/loaders/BlogPostPage.ts index b4b206ab3..f12e002b5 100644 --- a/blog/loaders/BlogPostPage.ts +++ b/blog/loaders/BlogPostPage.ts @@ -1,6 +1,6 @@ import { AppContext } from "../mod.ts"; import { BlogPost, BlogPostPage } from "../types.ts"; -import { getRecordsByPath } from "../utils/records.ts"; +import { getRecordsByPath } from "../core/records.ts"; import type { RequestURLParam } from "../../website/functions/requestToParam.ts"; const COLLECTION_PATH = "collections/blog/posts"; diff --git a/blog/loaders/BlogpostList.ts b/blog/loaders/BlogpostList.ts index a6d86ed62..d55d3e4d8 100644 --- a/blog/loaders/BlogpostList.ts +++ b/blog/loaders/BlogpostList.ts @@ -10,8 +10,8 @@ import { logger } from "@deco/deco/o11y"; import { RequestURLParam } from "../../website/functions/requestToParam.ts"; import { AppContext } from "../mod.ts"; import { BlogPost, SortBy } from "../types.ts"; -import handlePosts, { slicePosts } from "../utils/handlePosts.ts"; -import { getRecordsByPath } from "../utils/records.ts"; +import handlePosts, { slicePosts } from "../core/handlePosts.ts"; +import { getRecordsByPath } from "../core/records.ts"; const COLLECTION_PATH = "collections/blog/posts"; const ACCESSOR = "post"; diff --git a/blog/loaders/BlogpostListing.ts b/blog/loaders/BlogpostListing.ts index 00a536ae7..86b37c4d6 100644 --- a/blog/loaders/BlogpostListing.ts +++ b/blog/loaders/BlogpostListing.ts @@ -11,8 +11,8 @@ import { PageInfo } from "../../commerce/types.ts"; import { RequestURLParam } from "../../website/functions/requestToParam.ts"; import { AppContext } from "../mod.ts"; import { BlogPost, BlogPostListingPage, SortBy } from "../types.ts"; -import handlePosts, { slicePosts } from "../utils/handlePosts.ts"; -import { getRecordsByPath } from "../utils/records.ts"; +import handlePosts, { slicePosts } from "../core/handlePosts.ts"; +import { getRecordsByPath } from "../core/records.ts"; const COLLECTION_PATH = "collections/blog/posts"; const ACCESSOR = "post"; diff --git a/blog/loaders/GetCategories.ts b/blog/loaders/GetCategories.ts index c2a2080b7..c64317dc6 100644 --- a/blog/loaders/GetCategories.ts +++ b/blog/loaders/GetCategories.ts @@ -9,7 +9,7 @@ import { RequestURLParam } from "../../website/functions/requestToParam.ts"; import { AppContext } from "../mod.ts"; import { Category } from "../types.ts"; -import { getRecordsByPath } from "../utils/records.ts"; +import { getRecordsByPath } from "../core/records.ts"; const COLLECTION_PATH = "collections/blog/categories"; const ACCESSOR = "category"; diff --git a/blog/loaders/extensions/BlogpostList/ratings.ts b/blog/loaders/extensions/BlogpostList/ratings.ts index bfb5a3731..fb1647c3c 100644 --- a/blog/loaders/extensions/BlogpostList/ratings.ts +++ b/blog/loaders/extensions/BlogpostList/ratings.ts @@ -1,7 +1,7 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; import { BlogPost, Ignore } from "../../../types.ts"; -import { getRatings } from "../../../utils/records.ts"; +import { getRatings } from "../../../core/records.ts"; interface Props { /** diff --git a/blog/loaders/extensions/BlogpostList/reviews.ts b/blog/loaders/extensions/BlogpostList/reviews.ts index 83df367fe..911fd860f 100644 --- a/blog/loaders/extensions/BlogpostList/reviews.ts +++ b/blog/loaders/extensions/BlogpostList/reviews.ts @@ -1,7 +1,7 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; import { BlogPost, Ignore } from "../../../types.ts"; -import { getReviews } from "../../../utils/records.ts"; +import { getReviews } from "../../../core/records.ts"; interface Props { /** diff --git a/blog/loaders/extensions/BlogpostListing/ratings.ts b/blog/loaders/extensions/BlogpostListing/ratings.ts index 2d33e7e21..700f2774e 100644 --- a/blog/loaders/extensions/BlogpostListing/ratings.ts +++ b/blog/loaders/extensions/BlogpostListing/ratings.ts @@ -1,7 +1,7 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; import { BlogPostListingPage, Ignore } from "../../../types.ts"; -import { getRatings } from "../../../utils/records.ts"; +import { getRatings } from "../../../core/records.ts"; interface Props { /** diff --git a/blog/loaders/extensions/BlogpostListing/reviews.ts b/blog/loaders/extensions/BlogpostListing/reviews.ts index f91a84b4b..5549fab7e 100644 --- a/blog/loaders/extensions/BlogpostListing/reviews.ts +++ b/blog/loaders/extensions/BlogpostListing/reviews.ts @@ -1,7 +1,7 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; import { BlogPostListingPage, Ignore } from "../../../types.ts"; -import { getReviews } from "../../../utils/records.ts"; +import { getReviews } from "../../../core/records.ts"; interface Props { /** diff --git a/blog/loaders/extensions/BlogpostPage/ratings.ts b/blog/loaders/extensions/BlogpostPage/ratings.ts index bf4f66083..37703aa53 100644 --- a/blog/loaders/extensions/BlogpostPage/ratings.ts +++ b/blog/loaders/extensions/BlogpostPage/ratings.ts @@ -1,7 +1,7 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; import { BlogPostPage, Ignore } from "../../../types.ts"; -import { getRatings } from "../../../utils/records.ts"; +import { getRatings } from "../../../core/records.ts"; interface Props { /** diff --git a/blog/loaders/extensions/BlogpostPage/reviews.ts b/blog/loaders/extensions/BlogpostPage/reviews.ts index 6b8f08c20..6e2a48f49 100644 --- a/blog/loaders/extensions/BlogpostPage/reviews.ts +++ b/blog/loaders/extensions/BlogpostPage/reviews.ts @@ -1,7 +1,7 @@ import { ExtensionOf } from "../../../../website/loaders/extension.ts"; import { AppContext } from "../../../mod.ts"; import { BlogPostPage, Ignore } from "../../../types.ts"; -import { getReviews } from "../../../utils/records.ts"; +import { getReviews } from "../../../core/records.ts"; interface Props { /** From 863cc25acc4458dbc233d24c1a0f6d1bb6353aca Mon Sep 17 00:00:00 2001 From: decobot Date: Wed, 27 Nov 2024 09:25:34 -0300 Subject: [PATCH 19/20] feat(blogpost): listing now supports slugs lists --- blog/core/handlePosts.ts | 14 +++++++++++++- blog/loaders/BlogpostList.ts | 15 +++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/blog/core/handlePosts.ts b/blog/core/handlePosts.ts index 24604c10d..a384e2c9b 100644 --- a/blog/core/handlePosts.ts +++ b/blog/core/handlePosts.ts @@ -106,6 +106,15 @@ export const filterPostsByCategory = (posts: BlogPost[], slug?: string) => ? posts.filter(({ categories }) => categories.find((c) => c.slug === slug)) : posts; +/** + * Returns an filtered BlogPost list by specific slugs + * + * @param posts Posts to be handled + * @param postSlugs Specific slugs to be filter + */ +export const filterPostsBySlugs = (posts: BlogPost[], postSlugs: string[]) => + posts.filter(({ slug }) => postSlugs.includes(slug)); + /** * Returns an filtered and sorted BlogPost list * @@ -136,8 +145,11 @@ export default async function handlePosts( sortBy: SortBy, ctx: AppContext, slug?: string, + postSlugs?: string[], ) { - const filteredPosts = filterPostsByCategory(posts, slug); + const filteredPosts = postSlugs && postSlugs.length > 0 + ? filterPostsBySlugs(posts, postSlugs) + : filterPostsByCategory(posts, slug); if (!filteredPosts || filteredPosts.length === 0) { return null; diff --git a/blog/loaders/BlogpostList.ts b/blog/loaders/BlogpostList.ts index d55d3e4d8..5b12f9369 100644 --- a/blog/loaders/BlogpostList.ts +++ b/blog/loaders/BlogpostList.ts @@ -32,6 +32,11 @@ export interface Props { * @description Filter by a specific category slug. */ slug?: RequestURLParam; + /** + * @title Specific post slugs + * @description Filter by specific post slugs. + */ + postSlugs?: string[]; /** * @title Page sorting parameter * @description The sorting option. Default is "date_desc" @@ -49,7 +54,7 @@ export interface Props { * @returns A promise that resolves to an array of blog posts. */ export default async function BlogPostList( - { page, count, slug, sortBy }: Props, + { page, count, slug, sortBy, postSlugs }: Props, req: Request, ctx: AppContext, ): Promise { @@ -65,7 +70,13 @@ export default async function BlogPostList( ); try { - const handledPosts = await handlePosts(posts, pageSort, ctx, slug); + const handledPosts = await handlePosts( + posts, + pageSort, + ctx, + slug, + postSlugs, + ); if (!handledPosts) { return null; From caf5c58d39055ceb4153c4496f07248b03648f0f Mon Sep 17 00:00:00 2001 From: decobot Date: Tue, 14 Jan 2025 17:03:46 -0300 Subject: [PATCH 20/20] fix(blog): deploy error --- blog/actions/submitRating.ts | 2 +- blog/actions/submitReview.ts | 2 +- blog/actions/submitView.ts | 2 +- blog/core/records.ts | 8 +------- blog/db/schema.ts | 2 +- shopify/utils/storefront/storefront.graphql.gen.ts | 7 ------- verified-reviews/utils/client.ts | 12 ++++++++++-- 7 files changed, 15 insertions(+), 20 deletions(-) diff --git a/blog/actions/submitRating.ts b/blog/actions/submitRating.ts index 47d84ade7..9d4c7cec2 100644 --- a/blog/actions/submitRating.ts +++ b/blog/actions/submitRating.ts @@ -1,4 +1,4 @@ -import { and, eq, like, or } from "https://esm.sh/drizzle-orm@0.30.10"; +import { and, eq, like, or } from "npm:drizzle-orm@0.30.10"; import { Person } from "../../commerce/types.ts"; import { AppContext } from "../mod.ts"; import { logger } from "@deco/deco/o11y"; diff --git a/blog/actions/submitReview.ts b/blog/actions/submitReview.ts index b817018d9..f199bf39f 100644 --- a/blog/actions/submitReview.ts +++ b/blog/actions/submitReview.ts @@ -1,4 +1,4 @@ -import { eq } from "https://esm.sh/drizzle-orm@0.30.10"; +import { eq } from "npm:drizzle-orm@0.30.10"; import { Person } from "../../commerce/types.ts"; import { AppContext } from "../mod.ts"; import { logger } from "@deco/deco/o11y"; diff --git a/blog/actions/submitView.ts b/blog/actions/submitView.ts index f6cbdf83c..4bb2a76a0 100644 --- a/blog/actions/submitView.ts +++ b/blog/actions/submitView.ts @@ -1,4 +1,4 @@ -import { eq } from "https://esm.sh/v135/drizzle-orm@0.30.10"; +import { eq } from "npm:drizzle-orm@0.30.10"; import { postViews } from "../db/schema.ts"; import { AppContext } from "../mod.ts"; import { ViewFromDatabase } from "../types.ts"; diff --git a/blog/core/records.ts b/blog/core/records.ts index 974ab2ebc..1750e0901 100644 --- a/blog/core/records.ts +++ b/blog/core/records.ts @@ -1,13 +1,7 @@ import { rating, review } from "../db/schema.ts"; import { AppContext } from "../mod.ts"; import { type Resolvable } from "@deco/deco"; -import { - and, - asc, - desc, - eq, - notInArray, -} from "https://esm.sh/drizzle-orm@0.30.10"; +import { and, asc, desc, eq, notInArray } from "npm:drizzle-orm@0.30.10"; import { BlogPost, Ignore, Rating, Review } from "../types.ts"; import { logger } from "@deco/deco/o11y"; diff --git a/blog/db/schema.ts b/blog/db/schema.ts index 33b48daa7..a37608ed6 100644 --- a/blog/db/schema.ts +++ b/blog/db/schema.ts @@ -2,7 +2,7 @@ import { integer, sqliteTable, text, -} from "https://esm.sh/drizzle-orm@0.30.10/sqlite-core"; +} from "npm:drizzle-orm@0.30.10/sqlite-core"; export const rating = sqliteTable("rating", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), diff --git a/shopify/utils/storefront/storefront.graphql.gen.ts b/shopify/utils/storefront/storefront.graphql.gen.ts index b3ea06d6c..86f5fad6b 100644 --- a/shopify/utils/storefront/storefront.graphql.gen.ts +++ b/shopify/utils/storefront/storefront.graphql.gen.ts @@ -7779,13 +7779,6 @@ export type FetchCustomerInfoQueryVariables = Exact<{ }>; -export type FetchCustomerInfoQuery = { customer?: { id: string, email?: string | null, firstName?: string | null, lastName?: string | null } | null }; - -export type FetchCustomerInfoQueryVariables = Exact<{ - customerAccessToken: Scalars['String']['input']; -}>; - - export type FetchCustomerInfoQuery = { customer?: { id: string, email?: string | null, firstName?: string | null, lastName?: string | null } | null }; export type AddItemToCartMutationVariables = Exact<{ diff --git a/verified-reviews/utils/client.ts b/verified-reviews/utils/client.ts index 4f4cd49c4..8ff1b5d67 100644 --- a/verified-reviews/utils/client.ts +++ b/verified-reviews/utils/client.ts @@ -102,13 +102,21 @@ export const createClient = (params: ConfigVerifiedReviews | undefined) => { }; /** @description https://documenter.getpostman.com/view/2336519/SVzw6MK5#daf51360-c79e-451a-b627-33bdd0ef66b8 */ const reviews = ( - { productId, count = 5, offset = 0, order: _order = "date_desc", customizeOrder = false }: + { + productId, + count = 5, + offset = 0, + order: _order = "date_desc", + customizeOrder = false, + }: & PaginationOptions & { productId: string | string[]; }, ) => { - const order = customizeOrder ? _order : orderMap[_order as keyof typeof orderMap]; + const order = customizeOrder + ? _order + : orderMap[_order as keyof typeof orderMap]; const payload = { query: "reviews",