feat(core-cms): compose @repo/blog/cms into payload config
This commit is contained in:
@@ -15,7 +15,6 @@
|
|||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@repo/core-cms": "workspace:*",
|
|
||||||
"@repo/core-shared": "workspace:*",
|
"@repo/core-shared": "workspace:*",
|
||||||
"@trpc/server": "^11.0.0",
|
"@trpc/server": "^11.0.0",
|
||||||
"inversify": "^6.2.0",
|
"inversify": "^6.2.0",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ function generateSlug(title: string): string {
|
|||||||
|
|
||||||
export async function createArticleUseCase(input: {
|
export async function createArticleUseCase(input: {
|
||||||
title: string;
|
title: string;
|
||||||
content: unknown;
|
content?: unknown;
|
||||||
authorId: string;
|
authorId: string;
|
||||||
slug?: string;
|
slug?: string;
|
||||||
}): Promise<Article> {
|
}): Promise<Article> {
|
||||||
|
|||||||
@@ -5,11 +5,9 @@ vi.mock("payload", () => ({
|
|||||||
getPayload: vi.fn(),
|
getPayload: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("@repo/core-cms", () => ({
|
|
||||||
default: {} as never,
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe("PayloadArticlesRepository", () => {
|
describe("PayloadArticlesRepository", () => {
|
||||||
|
const mockConfig = {} as never;
|
||||||
|
|
||||||
it("maps a Payload doc to a domain Article on getArticleBySlug", async () => {
|
it("maps a Payload doc to a domain Article on getArticleBySlug", async () => {
|
||||||
const { getPayload } = await import("payload");
|
const { getPayload } = await import("payload");
|
||||||
const findMock = vi.fn().mockResolvedValue({
|
const findMock = vi.fn().mockResolvedValue({
|
||||||
@@ -30,7 +28,7 @@ describe("PayloadArticlesRepository", () => {
|
|||||||
find: findMock,
|
find: findMock,
|
||||||
});
|
});
|
||||||
|
|
||||||
const repo = new PayloadArticlesRepository();
|
const repo = new PayloadArticlesRepository(mockConfig);
|
||||||
const result = await repo.getArticleBySlug("hello");
|
const result = await repo.getArticleBySlug("hello");
|
||||||
|
|
||||||
expect(findMock).toHaveBeenCalledWith({
|
expect(findMock).toHaveBeenCalledWith({
|
||||||
@@ -52,7 +50,7 @@ describe("PayloadArticlesRepository", () => {
|
|||||||
find: vi.fn().mockResolvedValue({ docs: [] }),
|
find: vi.fn().mockResolvedValue({ docs: [] }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const repo = new PayloadArticlesRepository();
|
const repo = new PayloadArticlesRepository(mockConfig);
|
||||||
const result = await repo.getArticleBySlug("missing");
|
const result = await repo.getArticleBySlug("missing");
|
||||||
expect(result).toBeUndefined();
|
expect(result).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import "reflect-metadata";
|
import "reflect-metadata";
|
||||||
import { injectable } from "inversify";
|
import { injectable } from "inversify";
|
||||||
import { getPayload } from "payload";
|
import { getPayload } from "payload";
|
||||||
|
import type { SanitizedConfig } from "payload";
|
||||||
|
|
||||||
import config from "@repo/core-cms";
|
|
||||||
import type { IArticlesRepository } from "@/application/repositories/articles-repository.interface";
|
import type { IArticlesRepository } from "@/application/repositories/articles-repository.interface";
|
||||||
import type { Article } from "@/entities/article";
|
import type { Article } from "@/entities/article";
|
||||||
|
|
||||||
@@ -38,8 +38,14 @@ function mapDoc(doc: PayloadArticleDoc): Article {
|
|||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class PayloadArticlesRepository implements IArticlesRepository {
|
export class PayloadArticlesRepository implements IArticlesRepository {
|
||||||
|
private config: SanitizedConfig;
|
||||||
|
|
||||||
|
constructor(config: SanitizedConfig) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
async getArticle(id: string): Promise<Article | undefined> {
|
async getArticle(id: string): Promise<Article | undefined> {
|
||||||
const payload = await getPayload({ config });
|
const payload = await getPayload({ config: this.config });
|
||||||
try {
|
try {
|
||||||
const doc = await payload.findByID({
|
const doc = await payload.findByID({
|
||||||
collection: "articles",
|
collection: "articles",
|
||||||
@@ -53,7 +59,7 @@ export class PayloadArticlesRepository implements IArticlesRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getArticleBySlug(slug: string): Promise<Article | undefined> {
|
async getArticleBySlug(slug: string): Promise<Article | undefined> {
|
||||||
const payload = await getPayload({ config });
|
const payload = await getPayload({ config: this.config });
|
||||||
const result = await payload.find({
|
const result = await payload.find({
|
||||||
collection: "articles",
|
collection: "articles",
|
||||||
where: { slug: { equals: slug } },
|
where: { slug: { equals: slug } },
|
||||||
@@ -70,7 +76,7 @@ export class PayloadArticlesRepository implements IArticlesRepository {
|
|||||||
limit?: number;
|
limit?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
}): Promise<Article[]> {
|
}): Promise<Article[]> {
|
||||||
const payload = await getPayload({ config });
|
const payload = await getPayload({ config: this.config });
|
||||||
const where: Record<string, { equals: string }> = {};
|
const where: Record<string, { equals: string }> = {};
|
||||||
if (options?.status) where.status = { equals: options.status };
|
if (options?.status) where.status = { equals: options.status };
|
||||||
if (options?.authorId) where.author = { equals: options.authorId };
|
if (options?.authorId) where.author = { equals: options.authorId };
|
||||||
@@ -88,7 +94,7 @@ export class PayloadArticlesRepository implements IArticlesRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createArticle(input: Article): Promise<Article> {
|
async createArticle(input: Article): Promise<Article> {
|
||||||
const payload = await getPayload({ config });
|
const payload = await getPayload({ config: this.config });
|
||||||
const created = await payload.create({
|
const created = await payload.create({
|
||||||
collection: "articles",
|
collection: "articles",
|
||||||
data: {
|
data: {
|
||||||
@@ -107,7 +113,7 @@ export class PayloadArticlesRepository implements IArticlesRepository {
|
|||||||
id: string,
|
id: string,
|
||||||
input: Partial<Article>,
|
input: Partial<Article>,
|
||||||
): Promise<Article | undefined> {
|
): Promise<Article | undefined> {
|
||||||
const payload = await getPayload({ config });
|
const payload = await getPayload({ config: this.config });
|
||||||
try {
|
try {
|
||||||
const updated = await payload.update({
|
const updated = await payload.update({
|
||||||
collection: "articles",
|
collection: "articles",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { createArticleUseCase } from "@/application/use-cases/create-article.use
|
|||||||
|
|
||||||
const createInputSchema = z.object({
|
const createInputSchema = z.object({
|
||||||
title: z.string().min(1).max(255),
|
title: z.string().min(1).max(255),
|
||||||
content: z.unknown(),
|
content: z.unknown().optional(),
|
||||||
authorId: z.string(),
|
authorId: z.string(),
|
||||||
slug: z.string().optional(),
|
slug: z.string().optional(),
|
||||||
});
|
});
|
||||||
@@ -35,7 +35,12 @@ export async function createArticleController(
|
|||||||
cause: parsed.error,
|
cause: parsed.error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return createArticleUseCase(parsed.data);
|
return createArticleUseCase({
|
||||||
|
title: parsed.data.title,
|
||||||
|
content: parsed.data.content ?? null,
|
||||||
|
authorId: parsed.data.authorId,
|
||||||
|
slug: parsed.data.slug,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getArticlesController(
|
export async function getArticlesController(
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@repo/blog": "workspace:*",
|
||||||
"payload": "^3.14.0",
|
"payload": "^3.14.0",
|
||||||
"@payloadcms/db-postgres": "^3.14.0",
|
"@payloadcms/db-postgres": "^3.14.0",
|
||||||
"@payloadcms/richtext-lexical": "^3.14.0"
|
"@payloadcms/richtext-lexical": "^3.14.0"
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export interface Config {
|
|||||||
};
|
};
|
||||||
blocks: {};
|
blocks: {};
|
||||||
collections: {
|
collections: {
|
||||||
|
articles: Article;
|
||||||
'payload-kv': PayloadKv;
|
'payload-kv': PayloadKv;
|
||||||
users: User;
|
users: User;
|
||||||
'payload-locked-documents': PayloadLockedDocument;
|
'payload-locked-documents': PayloadLockedDocument;
|
||||||
@@ -75,6 +76,7 @@ export interface Config {
|
|||||||
};
|
};
|
||||||
collectionsJoins: {};
|
collectionsJoins: {};
|
||||||
collectionsSelect: {
|
collectionsSelect: {
|
||||||
|
articles: ArticlesSelect<false> | ArticlesSelect<true>;
|
||||||
'payload-kv': PayloadKvSelect<false> | PayloadKvSelect<true>;
|
'payload-kv': PayloadKvSelect<false> | PayloadKvSelect<true>;
|
||||||
users: UsersSelect<false> | UsersSelect<true>;
|
users: UsersSelect<false> | UsersSelect<true>;
|
||||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||||
@@ -115,6 +117,42 @@ export interface UserAuthOperations {
|
|||||||
password: string;
|
password: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "articles".
|
||||||
|
*/
|
||||||
|
export interface Article {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
/**
|
||||||
|
* Auto-generated from title if left empty
|
||||||
|
*/
|
||||||
|
slug?: string | null;
|
||||||
|
content?: {
|
||||||
|
root: {
|
||||||
|
type: string;
|
||||||
|
children: {
|
||||||
|
type: any;
|
||||||
|
version: number;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
direction: ('ltr' | 'rtl') | null;
|
||||||
|
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||||
|
indent: number;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
[k: string]: unknown;
|
||||||
|
} | null;
|
||||||
|
status: 'draft' | 'published';
|
||||||
|
/**
|
||||||
|
* Temporary text field; restored to users relationship in Plan 3.
|
||||||
|
*/
|
||||||
|
author: string;
|
||||||
|
publishedAt?: string | null;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
_status?: ('draft' | 'published') | null;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "payload-kv".
|
* via the `definition` "payload-kv".
|
||||||
@@ -163,10 +201,15 @@ export interface User {
|
|||||||
*/
|
*/
|
||||||
export interface PayloadLockedDocument {
|
export interface PayloadLockedDocument {
|
||||||
id: number;
|
id: number;
|
||||||
document?: {
|
document?:
|
||||||
|
| ({
|
||||||
|
relationTo: 'articles';
|
||||||
|
value: number | Article;
|
||||||
|
} | null)
|
||||||
|
| ({
|
||||||
relationTo: 'users';
|
relationTo: 'users';
|
||||||
value: number | User;
|
value: number | User;
|
||||||
} | null;
|
} | null);
|
||||||
globalSlug?: string | null;
|
globalSlug?: string | null;
|
||||||
user: {
|
user: {
|
||||||
relationTo: 'users';
|
relationTo: 'users';
|
||||||
@@ -209,6 +252,21 @@ export interface PayloadMigration {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
|
* via the `definition` "articles_select".
|
||||||
|
*/
|
||||||
|
export interface ArticlesSelect<T extends boolean = true> {
|
||||||
|
title?: T;
|
||||||
|
slug?: T;
|
||||||
|
content?: T;
|
||||||
|
status?: T;
|
||||||
|
author?: T;
|
||||||
|
publishedAt?: T;
|
||||||
|
updatedAt?: T;
|
||||||
|
createdAt?: T;
|
||||||
|
_status?: T;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `Config`'s JSON-Schema
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "payload-kv_select".
|
* via the `definition` "payload-kv_select".
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import { lexicalEditor } from "@payloadcms/richtext-lexical";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
import { articles } from "@repo/blog/cms";
|
||||||
|
|
||||||
const filename = fileURLToPath(import.meta.url);
|
const filename = fileURLToPath(import.meta.url);
|
||||||
const dirname = path.dirname(filename);
|
const dirname = path.dirname(filename);
|
||||||
|
|
||||||
export default buildConfig({
|
export default buildConfig({
|
||||||
editor: lexicalEditor(),
|
editor: lexicalEditor(),
|
||||||
collections: [],
|
collections: [articles],
|
||||||
globals: [],
|
globals: [],
|
||||||
secret: process.env.PAYLOAD_SECRET || "default-secret-change-me",
|
secret: process.env.PAYLOAD_SECRET || "default-secret-change-me",
|
||||||
db: postgresAdapter({
|
db: postgresAdapter({
|
||||||
|
|||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -230,9 +230,6 @@ importers:
|
|||||||
|
|
||||||
packages/blog:
|
packages/blog:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@repo/core-cms':
|
|
||||||
specifier: workspace:*
|
|
||||||
version: link:../core-cms
|
|
||||||
'@repo/core-shared':
|
'@repo/core-shared':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../core-shared
|
version: link:../core-shared
|
||||||
@@ -355,6 +352,9 @@ importers:
|
|||||||
'@payloadcms/richtext-lexical':
|
'@payloadcms/richtext-lexical':
|
||||||
specifier: ^3.14.0
|
specifier: ^3.14.0
|
||||||
version: 3.81.0(@faceless-ui/modal@3.0.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@faceless-ui/scroll-info@2.0.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@payloadcms/next@3.81.0(graphql@16.13.2)(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.99.0))(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.99.0))(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(yjs@13.6.30)
|
version: 3.81.0(@faceless-ui/modal@3.0.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@faceless-ui/scroll-info@2.0.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@payloadcms/next@3.81.0(graphql@16.13.2)(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.99.0))(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(next@16.2.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(sass@1.99.0))(payload@3.81.0(graphql@16.13.2)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(yjs@13.6.30)
|
||||||
|
'@repo/blog':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../blog
|
||||||
payload:
|
payload:
|
||||||
specifier: ^3.14.0
|
specifier: ^3.14.0
|
||||||
version: 3.81.0(graphql@16.13.2)(typescript@5.9.3)
|
version: 3.81.0(graphql@16.13.2)(typescript@5.9.3)
|
||||||
|
|||||||
Reference in New Issue
Block a user