create-m5kdev 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ResourceGrant } from "@m5kdev/backend/modules/base/base.grants";
|
|
2
|
+
|
|
3
|
+
export const postsGrants: ResourceGrant[] = [
|
|
4
|
+
{
|
|
5
|
+
action: "write",
|
|
6
|
+
level: "team",
|
|
7
|
+
role: "owner",
|
|
8
|
+
access: "own",
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
action: "publish",
|
|
12
|
+
level: "team",
|
|
13
|
+
role: "owner",
|
|
14
|
+
access: "own",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
action: "delete",
|
|
18
|
+
level: "team",
|
|
19
|
+
role: "owner",
|
|
20
|
+
access: "own",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
action: "write",
|
|
24
|
+
level: "organization",
|
|
25
|
+
role: "owner",
|
|
26
|
+
access: "own",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
action: "publish",
|
|
30
|
+
level: "organization",
|
|
31
|
+
role: "owner",
|
|
32
|
+
access: "own",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
action: "delete",
|
|
36
|
+
level: "organization",
|
|
37
|
+
role: "owner",
|
|
38
|
+
access: "own",
|
|
39
|
+
},
|
|
40
|
+
];
|
|
@@ -1,135 +1,155 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
PostCreateInputSchema,
|
|
3
|
-
PostCreateOutputSchema,
|
|
1
|
+
import type {
|
|
2
|
+
PostCreateInputSchema,
|
|
3
|
+
PostCreateOutputSchema,
|
|
4
4
|
PostPublishInputSchema,
|
|
5
5
|
PostPublishOutputSchema,
|
|
6
6
|
PostSoftDeleteInputSchema,
|
|
7
7
|
PostSoftDeleteOutputSchema,
|
|
8
8
|
PostsListInputSchema,
|
|
9
9
|
PostsListOutputSchema,
|
|
10
|
-
PostUpdateInputSchema,
|
|
11
|
-
PostUpdateOutputSchema,
|
|
12
|
-
} from "{{PACKAGE_SCOPE}}/shared/modules/posts/posts.schema";
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
10
|
+
PostUpdateInputSchema,
|
|
11
|
+
PostUpdateOutputSchema,
|
|
12
|
+
} from "{{PACKAGE_SCOPE}}/shared/modules/posts/posts.schema";
|
|
13
|
+
import type { Context } from "@m5kdev/backend/modules/auth/auth.lib";
|
|
14
|
+
import { BasePermissionService } from "@m5kdev/backend/modules/base/base.service";
|
|
15
|
+
import type { ServerResultAsync } from "@m5kdev/backend/utils/types";
|
|
16
|
+
import { err, ok } from "neverthrow";
|
|
17
|
+
import type { PostsRepository } from "./posts.repository";
|
|
18
|
+
|
|
19
|
+
type RequestContext = Context;
|
|
20
|
+
|
|
21
|
+
export class PostsService extends BasePermissionService<
|
|
22
|
+
{ posts: PostsRepository },
|
|
23
|
+
Record<string, never>,
|
|
24
|
+
RequestContext
|
|
25
|
+
> {
|
|
26
|
+
readonly list = this.procedure<PostsListInputSchema>("list")
|
|
27
|
+
.requireAuth()
|
|
28
|
+
.handle(({ input }): ServerResultAsync<PostsListOutputSchema> => {
|
|
29
|
+
return this.repository.posts.list(input);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
readonly create = this.procedure<PostCreateInputSchema>("create")
|
|
33
|
+
.requireAuth()
|
|
34
|
+
.access({
|
|
35
|
+
action: "write",
|
|
36
|
+
entities: ({ ctx }) => ({
|
|
37
|
+
organizationId: ctx.session.activeOrganizationId ?? null,
|
|
38
|
+
teamId: ctx.session.activeTeamId ?? null,
|
|
39
|
+
}),
|
|
40
|
+
})
|
|
41
|
+
.handle(async ({ input, ctx }): ServerResultAsync<PostCreateOutputSchema> => {
|
|
42
|
+
const uniqueSlug = await this.repository.posts.resolveUniqueSlug(
|
|
43
|
+
this.slugify(input.slug ?? input.title)
|
|
44
|
+
);
|
|
45
|
+
if (uniqueSlug.isErr()) {
|
|
46
|
+
return err(uniqueSlug.error);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return this.repository.posts.create({
|
|
50
|
+
authorUserId: ctx.user.id,
|
|
51
|
+
organizationId: ctx.session.activeOrganizationId ?? null,
|
|
52
|
+
teamId: ctx.session.activeTeamId ?? null,
|
|
53
|
+
title: input.title.trim(),
|
|
54
|
+
slug: uniqueSlug.value,
|
|
55
|
+
excerpt: this.createExcerpt(input.excerpt, input.content),
|
|
56
|
+
content: input.content.trim(),
|
|
57
|
+
status: "draft",
|
|
58
|
+
}) as ServerResultAsync<PostCreateOutputSchema>;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
readonly update = this.procedure<PostUpdateInputSchema>("update")
|
|
62
|
+
.requireAuth()
|
|
63
|
+
.use("post", async ({ input }) => {
|
|
64
|
+
const current = await this.repository.posts.findById(input.id);
|
|
65
|
+
if (current.isErr()) {
|
|
66
|
+
return err(current.error);
|
|
67
|
+
}
|
|
68
|
+
if (!current.value || current.value.deletedAt) {
|
|
69
|
+
return this.error("NOT_FOUND", "Post not found");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return current.value;
|
|
73
|
+
})
|
|
74
|
+
.access({
|
|
75
|
+
action: "write",
|
|
76
|
+
entityStep: "post",
|
|
77
|
+
})
|
|
78
|
+
.handle(async ({ input }): ServerResultAsync<PostUpdateOutputSchema> => {
|
|
79
|
+
const uniqueSlug = await this.repository.posts.resolveUniqueSlug(
|
|
80
|
+
this.slugify(input.slug ?? input.title),
|
|
81
|
+
input.id
|
|
82
|
+
);
|
|
83
|
+
if (uniqueSlug.isErr()) {
|
|
84
|
+
return err(uniqueSlug.error);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return this.repository.posts.update({
|
|
88
|
+
id: input.id,
|
|
89
|
+
title: input.title.trim(),
|
|
90
|
+
slug: uniqueSlug.value,
|
|
91
|
+
excerpt: this.createExcerpt(input.excerpt, input.content),
|
|
92
|
+
content: input.content.trim(),
|
|
93
|
+
}) as ServerResultAsync<PostUpdateOutputSchema>;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
readonly publish = this.procedure<PostPublishInputSchema>("publish")
|
|
97
|
+
.requireAuth()
|
|
98
|
+
.use("post", async ({ input }) => {
|
|
99
|
+
const current = await this.repository.posts.findById(input.id);
|
|
100
|
+
if (current.isErr()) {
|
|
101
|
+
return err(current.error);
|
|
102
|
+
}
|
|
103
|
+
if (!current.value || current.value.deletedAt) {
|
|
104
|
+
return this.error("NOT_FOUND", "Post not found");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return current.value;
|
|
108
|
+
})
|
|
109
|
+
.access({
|
|
110
|
+
action: "publish",
|
|
111
|
+
entityStep: "post",
|
|
112
|
+
})
|
|
113
|
+
.handle(({ input, state }): ServerResultAsync<PostPublishOutputSchema> => {
|
|
114
|
+
return this.repository.posts.update({
|
|
115
|
+
id: input.id,
|
|
116
|
+
status: "published",
|
|
117
|
+
publishedAt: state.post.publishedAt ?? new Date(),
|
|
118
|
+
}) as ServerResultAsync<PostPublishOutputSchema>;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
readonly softDelete = this.procedure<PostSoftDeleteInputSchema>("softDelete")
|
|
122
|
+
.requireAuth()
|
|
123
|
+
.use("post", async ({ input }) => {
|
|
124
|
+
const current = await this.repository.posts.findById(input.id);
|
|
125
|
+
if (current.isErr()) {
|
|
126
|
+
return err(current.error);
|
|
127
|
+
}
|
|
128
|
+
if (!current.value || current.value.deletedAt) {
|
|
129
|
+
return this.error("NOT_FOUND", "Post not found");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return current.value;
|
|
133
|
+
})
|
|
134
|
+
.access({
|
|
135
|
+
action: "delete",
|
|
136
|
+
entityStep: "post",
|
|
137
|
+
})
|
|
138
|
+
.handle(async ({ input }): ServerResultAsync<PostSoftDeleteOutputSchema> => {
|
|
139
|
+
const updated = await this.repository.posts.update({
|
|
140
|
+
id: input.id,
|
|
141
|
+
deletedAt: new Date(),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (updated.isErr()) {
|
|
145
|
+
return err(updated.error);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return ok({ id: updated.value.id });
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
private slugify(value: string): string {
|
|
152
|
+
const slug = value
|
|
133
153
|
.trim()
|
|
134
154
|
.toLowerCase()
|
|
135
155
|
.replace(/[^a-z0-9]+/g, "-")
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { AuthService } from "@m5kdev/backend/modules/auth/auth.service";
|
|
2
|
-
import { LocalEmailService } from "./lib/localEmailService";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { AuthService } from "@m5kdev/backend/modules/auth/auth.service";
|
|
2
|
+
import { LocalEmailService } from "./lib/localEmailService";
|
|
3
|
+
import { postsGrants } from "./modules/posts/posts.grants";
|
|
4
|
+
import { PostsService } from "./modules/posts/posts.service";
|
|
5
|
+
import { authRepository, postsRepository } from "./repository";
|
|
5
6
|
|
|
6
7
|
export const emailService = new LocalEmailService({
|
|
7
8
|
appName: "{{APP_NAME}}",
|
|
8
9
|
appUrl: process.env.VITE_APP_URL ?? "http://localhost:5173",
|
|
9
10
|
});
|
|
10
|
-
|
|
11
|
-
export const authService = new AuthService({ auth: authRepository }, { email: emailService });
|
|
12
|
-
export const postsService = new PostsService({ posts: postsRepository }, {});
|
|
11
|
+
|
|
12
|
+
export const authService = new AuthService({ auth: authRepository }, { email: emailService });
|
|
13
|
+
export const postsService = new PostsService({ posts: postsRepository }, {}, postsGrants);
|