Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP - Blog extensions: Reviews and Ratings #861

Open
wants to merge 34 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
aafa26c
feat(blogreaction): setup started
Sep 13, 2024
eab7642
Merge branch 'main' of github.com:deco-cx/apps into feat-blog-like
Sep 13, 2024
5b41f68
chore(blogLike): reaction mock changed
Sep 16, 2024
c8d9b2d
feat(blogLike): deco records bug fix
Sep 16, 2024
a563bc5
feat(bloglike): extension loaders ended
Sep 16, 2024
6401764
feat(bloglike): submit extension
Sep 16, 2024
64a827d
fix(bloglike): action fix
Sep 17, 2024
e85fab2
chore(bloglike): prettify code
Sep 17, 2024
d6e7437
feat(blogpost): comment feat started
Sep 17, 2024
e348e9f
feat(blogpost): comment action added
Sep 17, 2024
49ad269
Merge pull request #868 from deco-cx/feat-blog-comment
aka-sacci-ccr Sep 17, 2024
b26b7f6
fix(blog): changed schema
Sep 18, 2024
8af05b2
Merge branch 'main' of github.com:deco-cx/apps into feat-blog-comment
Sep 18, 2024
214d7b7
Merge branch 'main' of github.com:deco-cx/apps into feat-blog-like
Sep 18, 2024
62983ae
Merge branch 'feat-blog-like' of github.com:deco-cx/apps into feat-bl…
Sep 18, 2024
7d68653
Merge branch 'feat-blog-like' of github.com:deco-cx/apps into feat-bl…
Sep 18, 2024
164649b
Merge pull request #870 from deco-cx/feat-blog-comment
aka-sacci-ccr Sep 18, 2024
e270c56
feat(blog): extension props
Sep 18, 2024
29026f7
Merge branch 'feat-blog-like' of github.com:deco-cx/apps into feat-bl…
Sep 18, 2024
c943a79
Merge pull request #871 from deco-cx/feat-blog-extesions
aka-sacci-ccr Sep 18, 2024
dba5261
Merge branch 'main' of github.com:deco-cx/apps into feat-blog-like
Sep 23, 2024
8885171
feat(blogpost): new types for carousel
Sep 23, 2024
1695e4e
Merge branch 'main' of github.com:deco-cx/apps into feat-blog-like
Sep 30, 2024
861292a
Merge branch 'main' of github.com:deco-cx/apps into feat-blog-like
Oct 7, 2024
6627bf4
feat(blogextensions): update database script
Oct 7, 2024
af2a955
feat(blogextensions): script workaround
Oct 7, 2024
76b6520
Merge branch 'main' of github.com:deco-cx/apps into feat-blog-like
Oct 11, 2024
1791251
Merge branch 'main' of github.com:deco-cx/apps into feat-blog-like
Oct 15, 2024
2cd96b8
Merge branch 'main' of github.com:deco-cx/apps into feat-blog-like
Oct 21, 2024
b49c320
Merge branch 'main' of github.com:deco-cx/apps into feat-blog-like
Oct 30, 2024
e5ebdde
feat(most-viewed): order by most viewed started
Oct 30, 2024
6ccb944
feat(most-viewed): loader and orderBy finished
Oct 30, 2024
5d46609
feat(most-viewed): action finished
Oct 30, 2024
ff7a1d0
chore(blog): blogposting core logic archives
Oct 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions blog/actions/submitRating.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { and, eq, like, or } from "https://esm.sh/[email protected]";
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<Rating | null> {
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;
}
}
87 changes: 87 additions & 0 deletions blog/actions/submitReview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { eq } from "https://esm.sh/[email protected]";
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<Review | null> {
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;
}
}
25 changes: 25 additions & 0 deletions blog/db/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
integer,
sqliteTable,
text,
} from "https://esm.sh/[email protected]/sqlite-core";

export const rating = sqliteTable("rating", {
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
itemReviewed: text("itemReviewed").notNull(),
author: text("author", { mode: "json" }),
ratingValue: (integer("ratingValue")).notNull(),
additionalType: text("additionalType"),
});

export const review = sqliteTable("review", {
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
itemReviewed: text("itemReviewed").notNull(),
author: text("author", { mode: "json" }),
datePublished: (text("datePublished")).notNull(),
dateModified: (text("dateModified")).notNull(),
reviewHeadline: (text("reviewHeadline")),
reviewBody: (text("reviewBody")).notNull(),
additionalType: (text("additionalType")),
isAnonymous: integer("isAnonymous", { mode: "boolean" }),
});
16 changes: 16 additions & 0 deletions blog/loaders/extensions/BlogpostList.ts
Original file line number Diff line number Diff line change
@@ -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<BlogPost[] | null>,
): Promise<BlogPost[] | null> {
return extend(props);
}

export const cache = "no-cache";
45 changes: 45 additions & 0 deletions blog/loaders/extensions/BlogpostList/ratings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ExtensionOf } from "../../../../website/loaders/extension.ts";
import { AppContext } from "../../../mod.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(
{ ignoreRatings, onlyAggregate }: Props,
_req: Request,
ctx: AppContext,
): ExtensionOf<BlogPost[] | null> {
return async (posts: BlogPost[] | null) => {
if (posts?.length === 0 || !posts) {
return null;
}

const postsWithRatings = await Promise.all(
posts.map(async (post) => {
const ratings = await getRatings({
post,
ctx,
ignoreRatings,
onlyAggregate,
});
return { ...post, ...ratings };
}),
);

return postsWithRatings;
};
}
40 changes: 40 additions & 0 deletions blog/loaders/extensions/BlogpostList/reviews.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ExtensionOf } from "../../../../website/loaders/extension.ts";
import { AppContext } from "../../../mod.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(
{ ignoreReviews, orderBy }: Props,
_req: Request,
ctx: AppContext,
): ExtensionOf<BlogPost[] | null> {
return async (posts: BlogPost[] | null) => {
if (posts?.length === 0 || !posts) {
return null;
}

const postsWithReviews = await Promise.all(
posts.map(async (post) => {
const reviews = await getReviews({ post, ctx, ignoreReviews, orderBy });
return { ...post, ...reviews };
}),
);

return postsWithReviews;
};
}
16 changes: 16 additions & 0 deletions blog/loaders/extensions/BlogpostListing.ts
Original file line number Diff line number Diff line change
@@ -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<BlogPostListingPage | null>,
): Promise<BlogPostListingPage | null> {
return extend(props);
}

export const cache = "no-cache";
48 changes: 48 additions & 0 deletions blog/loaders/extensions/BlogpostListing/ratings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { ExtensionOf } from "../../../../website/loaders/extension.ts";
import { AppContext } from "../../../mod.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 BlogPostListing: Ratings
* @description It can harm performance. Use wisely
*/
export default function ratingsExt(
{ ignoreRatings, onlyAggregate }: Props,
_req: Request,
ctx: AppContext,
): ExtensionOf<BlogPostListingPage | null> {
return async (blogpostListingPage: BlogPostListingPage | null) => {
if (!blogpostListingPage) {
return null;
}

const posts = await Promise.all(
blogpostListingPage.posts.map(async (post) => {
const ratings = await getRatings({
post,
ctx,
onlyAggregate,
ignoreRatings,
});
return { ...post, ...ratings };
}),
);

return {
...blogpostListingPage,
posts,
};
};
}
Loading
Loading