@arch-cadre/blog-module 1.0.8 → 1.0.9

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.
Files changed (51) hide show
  1. package/package.json +6 -5
  2. package/src/actions/index.d.ts +67 -0
  3. package/src/actions/index.js +149 -0
  4. package/src/actions/index.ts +157 -0
  5. package/src/components/BlogStatsWidget.d.ts +1 -0
  6. package/src/components/BlogStatsWidget.js +17 -0
  7. package/src/components/BlogStatsWidget.tsx +46 -0
  8. package/src/components/RecentCommentsWidget.d.ts +1 -0
  9. package/src/components/RecentCommentsWidget.js +24 -0
  10. package/src/components/RecentCommentsWidget.tsx +71 -0
  11. package/src/components/RecentPostsWidget.d.ts +1 -0
  12. package/src/components/RecentPostsWidget.js +24 -0
  13. package/src/components/RecentPostsWidget.tsx +68 -0
  14. package/src/components/ui/button.d.ts +11 -0
  15. package/src/components/ui/button.js +33 -0
  16. package/src/components/ui/button.tsx +56 -0
  17. package/src/components/ui/card.d.ts +6 -0
  18. package/src/components/ui/card.js +12 -0
  19. package/src/components/ui/card.tsx +51 -0
  20. package/src/components/ui/input.d.ts +5 -0
  21. package/src/components/ui/input.js +8 -0
  22. package/src/components/ui/input.tsx +24 -0
  23. package/src/components/ui/table.d.ts +8 -0
  24. package/src/components/ui/table.js +16 -0
  25. package/src/components/ui/table.tsx +83 -0
  26. package/src/components/ui/textarea.d.ts +5 -0
  27. package/src/components/ui/textarea.js +8 -0
  28. package/src/components/ui/textarea.tsx +23 -0
  29. package/src/index.d.ts +3 -0
  30. package/src/index.js +98 -0
  31. package/src/index.ts +121 -0
  32. package/src/intl.d.ts +7 -0
  33. package/src/lib/utils.d.ts +2 -0
  34. package/src/lib/utils.js +5 -0
  35. package/src/lib/utils.ts +6 -0
  36. package/src/lib/validation.d.ts +24 -0
  37. package/src/lib/validation.js +11 -0
  38. package/src/lib/validation.ts +13 -0
  39. package/src/navigation.d.ts +2 -0
  40. package/src/navigation.js +21 -0
  41. package/src/navigation.ts +23 -0
  42. package/src/routes.d.ts +3 -0
  43. package/src/routes.js +55 -0
  44. package/src/routes.tsx +74 -0
  45. package/src/schema.d.ts +736 -0
  46. package/src/schema.js +60 -0
  47. package/src/schema.ts +67 -0
  48. package/src/styles/globals.css +123 -0
  49. package/src/ui/views.d.ts +15 -0
  50. package/src/ui/views.js +119 -0
  51. package/src/ui/views.tsx +538 -0
@@ -0,0 +1,11 @@
1
+ import { type VariantProps } from "class-variance-authority";
2
+ import * as React from "react";
3
+ declare const buttonVariants: (props?: ({
4
+ variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
5
+ size?: "default" | "sm" | "lg" | "icon" | null | undefined;
6
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
8
+ asChild?: boolean;
9
+ }
10
+ declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
11
+ export { Button, buttonVariants };
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva } from "class-variance-authority";
4
+ import * as React from "react";
5
+ import { cn } from "../../lib/utils.js";
6
+ const buttonVariants = cva("inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", {
7
+ variants: {
8
+ variant: {
9
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
10
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
11
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
12
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
13
+ ghost: "hover:bg-accent hover:text-accent-foreground",
14
+ link: "text-primary underline-offset-4 hover:underline",
15
+ },
16
+ size: {
17
+ default: "h-9 px-4 py-2",
18
+ sm: "h-8 rounded-md px-3 text-xs",
19
+ lg: "h-10 rounded-md px-8",
20
+ icon: "size-9",
21
+ },
22
+ },
23
+ defaultVariants: {
24
+ variant: "default",
25
+ size: "default",
26
+ },
27
+ });
28
+ const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
29
+ const Comp = asChild ? Slot : "button";
30
+ return (_jsx(Comp, { className: cn(buttonVariants({ variant, size, className })), ref: ref, ...props }));
31
+ });
32
+ Button.displayName = "Button";
33
+ export { Button, buttonVariants };
@@ -0,0 +1,56 @@
1
+ import { Slot } from "@radix-ui/react-slot";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import * as React from "react";
4
+ import { cn } from "../../lib/utils.js";
5
+
6
+ const buttonVariants = cva(
7
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
15
+ outline:
16
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
19
+ ghost: "hover:bg-accent hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ size: {
23
+ default: "h-9 px-4 py-2",
24
+ sm: "h-8 rounded-md px-3 text-xs",
25
+ lg: "h-10 rounded-md px-8",
26
+ icon: "size-9",
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: "default",
31
+ size: "default",
32
+ },
33
+ },
34
+ );
35
+
36
+ export interface ButtonProps
37
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38
+ VariantProps<typeof buttonVariants> {
39
+ asChild?: boolean;
40
+ }
41
+
42
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
43
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
44
+ const Comp = asChild ? Slot : "button";
45
+ return (
46
+ <Comp
47
+ className={cn(buttonVariants({ variant, size, className }))}
48
+ ref={ref}
49
+ {...props}
50
+ />
51
+ );
52
+ },
53
+ );
54
+ Button.displayName = "Button";
55
+
56
+ export { Button, buttonVariants };
@@ -0,0 +1,6 @@
1
+ import * as React from "react";
2
+ declare const Card: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
3
+ declare const CardHeader: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
4
+ declare const CardTitle: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
5
+ declare const CardContent: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
6
+ export { Card, CardHeader, CardTitle, CardContent };
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../../lib/utils.js";
4
+ const Card = React.forwardRef(({ className, ...props }, ref) => (_jsx("div", { ref: ref, className: cn("rounded-xl border bg-card text-card-foreground shadow", className), ...props })));
5
+ Card.displayName = "Card";
6
+ const CardHeader = React.forwardRef(({ className, ...props }, ref) => (_jsx("div", { ref: ref, className: cn("flex flex-col space-y-1.5 p-6", className), ...props })));
7
+ CardHeader.displayName = "CardHeader";
8
+ const CardTitle = React.forwardRef(({ className, ...props }, ref) => (_jsx("div", { ref: ref, className: cn("font-semibold leading-none tracking-tight", className), ...props })));
9
+ CardTitle.displayName = "CardTitle";
10
+ const CardContent = React.forwardRef(({ className, ...props }, ref) => (_jsx("div", { ref: ref, className: cn("p-6 pt-0", className), ...props })));
11
+ CardContent.displayName = "CardContent";
12
+ export { Card, CardHeader, CardTitle, CardContent };
@@ -0,0 +1,51 @@
1
+ import * as React from "react";
2
+ import { cn } from "../../lib/utils.js";
3
+
4
+ const Card = React.forwardRef<
5
+ HTMLDivElement,
6
+ React.HTMLAttributes<HTMLDivElement>
7
+ >(({ className, ...props }, ref) => (
8
+ <div
9
+ ref={ref}
10
+ className={cn(
11
+ "rounded-xl border bg-card text-card-foreground shadow",
12
+ className,
13
+ )}
14
+ {...props}
15
+ />
16
+ ));
17
+ Card.displayName = "Card";
18
+
19
+ const CardHeader = React.forwardRef<
20
+ HTMLDivElement,
21
+ React.HTMLAttributes<HTMLDivElement>
22
+ >(({ className, ...props }, ref) => (
23
+ <div
24
+ ref={ref}
25
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
26
+ {...props}
27
+ />
28
+ ));
29
+ CardHeader.displayName = "CardHeader";
30
+
31
+ const CardTitle = React.forwardRef<
32
+ HTMLDivElement,
33
+ React.HTMLAttributes<HTMLDivElement>
34
+ >(({ className, ...props }, ref) => (
35
+ <div
36
+ ref={ref}
37
+ className={cn("font-semibold leading-none tracking-tight", className)}
38
+ {...props}
39
+ />
40
+ ));
41
+ CardTitle.displayName = "CardTitle";
42
+
43
+ const CardContent = React.forwardRef<
44
+ HTMLDivElement,
45
+ React.HTMLAttributes<HTMLDivElement>
46
+ >(({ className, ...props }, ref) => (
47
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
48
+ ));
49
+ CardContent.displayName = "CardContent";
50
+
51
+ export { Card, CardHeader, CardTitle, CardContent };
@@ -0,0 +1,5 @@
1
+ import * as React from "react";
2
+ export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
3
+ }
4
+ declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
5
+ export { Input };
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../../lib/utils.js";
4
+ const Input = React.forwardRef(({ className, type, ...props }, ref) => {
5
+ return (_jsx("input", { type: type, className: cn("flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", className), ref: ref, ...props }));
6
+ });
7
+ Input.displayName = "Input";
8
+ export { Input };
@@ -0,0 +1,24 @@
1
+ import * as React from "react";
2
+ import { cn } from "../../lib/utils.js";
3
+
4
+ export interface InputProps
5
+ extends React.InputHTMLAttributes<HTMLInputElement> { }
6
+
7
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
8
+ ({ className, type, ...props }, ref) => {
9
+ return (
10
+ <input
11
+ type={type}
12
+ className={cn(
13
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
14
+ className,
15
+ )}
16
+ ref={ref}
17
+ {...props}
18
+ />
19
+ );
20
+ },
21
+ );
22
+ Input.displayName = "Input";
23
+
24
+ export { Input };
@@ -0,0 +1,8 @@
1
+ import * as React from "react";
2
+ declare const Table: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableElement> & React.RefAttributes<HTMLTableElement>>;
3
+ declare const TableHeader: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>;
4
+ declare const TableBody: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableSectionElement> & React.RefAttributes<HTMLTableSectionElement>>;
5
+ declare const TableRow: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLTableRowElement> & React.RefAttributes<HTMLTableRowElement>>;
6
+ declare const TableHead: React.ForwardRefExoticComponent<React.ThHTMLAttributes<HTMLTableCellElement> & React.RefAttributes<HTMLTableCellElement>>;
7
+ declare const TableCell: React.ForwardRefExoticComponent<React.TdHTMLAttributes<HTMLTableCellElement> & React.RefAttributes<HTMLTableCellElement>>;
8
+ export { Table, TableHeader, TableBody, TableHead, TableRow, TableCell };
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../../lib/utils.js";
4
+ const Table = React.forwardRef(({ className, ...props }, ref) => (_jsx("div", { className: "relative w-full overflow-auto", children: _jsx("table", { ref: ref, className: cn("w-full caption-bottom text-sm", className), ...props }) })));
5
+ Table.displayName = "Table";
6
+ const TableHeader = React.forwardRef(({ className, ...props }, ref) => (_jsx("thead", { ref: ref, className: cn("[&_tr]:border-b", className), ...props })));
7
+ TableHeader.displayName = "TableHeader";
8
+ const TableBody = React.forwardRef(({ className, ...props }, ref) => (_jsx("tbody", { ref: ref, className: cn("[&_tr:last-child]:border-0", className), ...props })));
9
+ TableBody.displayName = "TableBody";
10
+ const TableRow = React.forwardRef(({ className, ...props }, ref) => (_jsx("tr", { ref: ref, className: cn("border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted", className), ...props })));
11
+ TableRow.displayName = "TableRow";
12
+ const TableHead = React.forwardRef(({ className, ...props }, ref) => (_jsx("th", { ref: ref, className: cn("h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", className), ...props })));
13
+ TableHead.displayName = "TableHead";
14
+ const TableCell = React.forwardRef(({ className, ...props }, ref) => (_jsx("td", { ref: ref, className: cn("p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", className), ...props })));
15
+ TableCell.displayName = "TableCell";
16
+ export { Table, TableHeader, TableBody, TableHead, TableRow, TableCell };
@@ -0,0 +1,83 @@
1
+ import * as React from "react";
2
+ import { cn } from "../../lib/utils.js";
3
+
4
+ const Table = React.forwardRef<
5
+ HTMLTableElement,
6
+ React.HTMLAttributes<HTMLTableElement>
7
+ >(({ className, ...props }, ref) => (
8
+ <div className="relative w-full overflow-auto">
9
+ <table
10
+ ref={ref}
11
+ className={cn("w-full caption-bottom text-sm", className)}
12
+ {...props}
13
+ />
14
+ </div>
15
+ ));
16
+ Table.displayName = "Table";
17
+
18
+ const TableHeader = React.forwardRef<
19
+ HTMLTableSectionElement,
20
+ React.HTMLAttributes<HTMLTableSectionElement>
21
+ >(({ className, ...props }, ref) => (
22
+ <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
23
+ ));
24
+ TableHeader.displayName = "TableHeader";
25
+
26
+ const TableBody = React.forwardRef<
27
+ HTMLTableSectionElement,
28
+ React.HTMLAttributes<HTMLTableSectionElement>
29
+ >(({ className, ...props }, ref) => (
30
+ <tbody
31
+ ref={ref}
32
+ className={cn("[&_tr:last-child]:border-0", className)}
33
+ {...props}
34
+ />
35
+ ));
36
+ TableBody.displayName = "TableBody";
37
+
38
+ const TableRow = React.forwardRef<
39
+ HTMLTableRowElement,
40
+ React.HTMLAttributes<HTMLTableRowElement>
41
+ >(({ className, ...props }, ref) => (
42
+ <tr
43
+ ref={ref}
44
+ className={cn(
45
+ "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
46
+ className,
47
+ )}
48
+ {...props}
49
+ />
50
+ ));
51
+ TableRow.displayName = "TableRow";
52
+
53
+ const TableHead = React.forwardRef<
54
+ HTMLTableCellElement,
55
+ React.ThHTMLAttributes<HTMLTableCellElement>
56
+ >(({ className, ...props }, ref) => (
57
+ <th
58
+ ref={ref}
59
+ className={cn(
60
+ "h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
61
+ className,
62
+ )}
63
+ {...props}
64
+ />
65
+ ));
66
+ TableHead.displayName = "TableHead";
67
+
68
+ const TableCell = React.forwardRef<
69
+ HTMLTableCellElement,
70
+ React.TdHTMLAttributes<HTMLTableCellElement>
71
+ >(({ className, ...props }, ref) => (
72
+ <td
73
+ ref={ref}
74
+ className={cn(
75
+ "p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
76
+ className,
77
+ )}
78
+ {...props}
79
+ />
80
+ ));
81
+ TableCell.displayName = "TableCell";
82
+
83
+ export { Table, TableHeader, TableBody, TableHead, TableRow, TableCell };
@@ -0,0 +1,5 @@
1
+ import * as React from "react";
2
+ export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
3
+ }
4
+ declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
5
+ export { Textarea };
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../../lib/utils.js";
4
+ const Textarea = React.forwardRef(({ className, ...props }, ref) => {
5
+ return (_jsx("textarea", { className: cn("flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", className), ref: ref, ...props }));
6
+ });
7
+ Textarea.displayName = "Textarea";
8
+ export { Textarea };
@@ -0,0 +1,23 @@
1
+ import * as React from "react";
2
+ import { cn } from "../../lib/utils.js";
3
+
4
+ export interface TextareaProps
5
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement> { }
6
+
7
+ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
8
+ ({ className, ...props }, ref) => {
9
+ return (
10
+ <textarea
11
+ className={cn(
12
+ "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
13
+ className,
14
+ )}
15
+ ref={ref}
16
+ {...props}
17
+ />
18
+ );
19
+ },
20
+ );
21
+ Textarea.displayName = "Textarea";
22
+
23
+ export { Textarea };
package/src/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { IModule } from "@arch-cadre/modules";
2
+ declare const blogModule: IModule;
3
+ export default blogModule;
package/src/index.js ADDED
@@ -0,0 +1,98 @@
1
+ import { assignPermissionToRole, createPermission, db, getRoles, permissionsTable, } from "@arch-cadre/core/server";
2
+ import { inArray, sql } from "drizzle-orm";
3
+ import manifest from "../manifest.json" with { type: "json" };
4
+ import BlogStatsWidget from "./components/BlogStatsWidget.js";
5
+ import RecentCommentsWidget from "./components/RecentCommentsWidget.js";
6
+ import RecentPostsWidget from "./components/RecentPostsWidget.js";
7
+ import { navigation } from "./navigation.js";
8
+ import { privateRoutes, publicRoutes } from "./routes.js";
9
+ const BLOG_PERMISSIONS = [
10
+ { name: "post:create", description: "Allow creating blog posts" },
11
+ { name: "post:update", description: "Allow updating blog posts" },
12
+ { name: "post:delete", description: "Allow deleting blog posts" },
13
+ { name: "comment:create", description: "Allow creating comments" },
14
+ { name: "comment:update", description: "Allow updating comments" },
15
+ { name: "comment:delete", description: "Allow deleting comments" },
16
+ ];
17
+ // --- Module Definition ---
18
+ const blogModule = {
19
+ manifest: manifest,
20
+ init: async () => {
21
+ console.log("[BlogModule] ready.");
22
+ },
23
+ widgets: [
24
+ {
25
+ id: "blog-stats",
26
+ name: "Blog Stats",
27
+ area: "dashboard-stats",
28
+ component: BlogStatsWidget,
29
+ priority: 20,
30
+ },
31
+ {
32
+ id: "recent-posts",
33
+ name: "Recent Posts",
34
+ area: "dashboard-main",
35
+ component: RecentPostsWidget,
36
+ priority: 20,
37
+ },
38
+ {
39
+ id: "recent-comments",
40
+ name: "Recent Comments",
41
+ area: "dashboard-main",
42
+ component: RecentCommentsWidget,
43
+ priority: 30,
44
+ },
45
+ ],
46
+ onEnable: async () => {
47
+ console.log("[BlogModule] enabling and registering permissions...");
48
+ try {
49
+ // 1. Create permissions
50
+ for (const perm of BLOG_PERMISSIONS) {
51
+ await createPermission(perm.name, perm.description);
52
+ }
53
+ // 2. Assign to admin role
54
+ const roles = await getRoles();
55
+ const adminRole = roles.find((r) => r.name === "admin");
56
+ if (adminRole) {
57
+ const blogPermNames = BLOG_PERMISSIONS.map((p) => p.name);
58
+ const blogPerms = await db
59
+ .select()
60
+ .from(permissionsTable)
61
+ .where(inArray(permissionsTable.name, blogPermNames));
62
+ for (const p of blogPerms) {
63
+ await assignPermissionToRole(adminRole.id, p.id);
64
+ }
65
+ console.log("[BlogModule] Permissions assigned to admin role.");
66
+ }
67
+ }
68
+ catch (error) {
69
+ console.error("[BlogModule] Error during permission registration:", error);
70
+ }
71
+ console.log("[BlogModule] enabled.");
72
+ },
73
+ onDisable: async () => {
74
+ console.log("[Blog] onDisable: Cleaning up tables and permissions...");
75
+ try {
76
+ // 1. Remove permissions (cascades to role-permission mappings)
77
+ const blogPermNames = BLOG_PERMISSIONS.map((p) => p.name);
78
+ await db
79
+ .delete(permissionsTable)
80
+ .where(inArray(permissionsTable.name, blogPermNames));
81
+ console.log("[BlogModule] Permissions and mappings removed.");
82
+ // 2. Drop tables
83
+ const tables = ["blog_posts", "blog_comments"];
84
+ for (const table of tables) {
85
+ await db.execute(sql.raw(`DROP TABLE IF EXISTS ${table} CASCADE`));
86
+ }
87
+ }
88
+ catch (e) {
89
+ console.error("[Blog] onDisable Error:", e);
90
+ }
91
+ },
92
+ routes: {
93
+ public: publicRoutes,
94
+ private: privateRoutes,
95
+ },
96
+ navigation: navigation,
97
+ };
98
+ export default blogModule;
package/src/index.ts ADDED
@@ -0,0 +1,121 @@
1
+ import {
2
+ assignPermissionToRole,
3
+ createPermission,
4
+ db,
5
+ getRoles,
6
+ permissionsTable,
7
+ } from "@arch-cadre/core/server";
8
+ import type { IModule } from "@arch-cadre/modules";
9
+ import { inArray, sql } from "drizzle-orm";
10
+ import manifest from "../manifest.json" with { type: "json" };
11
+ import BlogStatsWidget from "./components/BlogStatsWidget.js";
12
+ import RecentCommentsWidget from "./components/RecentCommentsWidget.js";
13
+ import RecentPostsWidget from "./components/RecentPostsWidget.js";
14
+ import { navigation } from "./navigation.js";
15
+ import { privateRoutes, publicRoutes } from "./routes.js";
16
+
17
+ const BLOG_PERMISSIONS = [
18
+ { name: "post:create", description: "Allow creating blog posts" },
19
+ { name: "post:update", description: "Allow updating blog posts" },
20
+ { name: "post:delete", description: "Allow deleting blog posts" },
21
+ { name: "comment:create", description: "Allow creating comments" },
22
+ { name: "comment:update", description: "Allow updating comments" },
23
+ { name: "comment:delete", description: "Allow deleting comments" },
24
+ ];
25
+
26
+ // --- Module Definition ---
27
+ const blogModule: IModule = {
28
+ manifest: manifest as any,
29
+
30
+ init: async () => {
31
+ console.log("[BlogModule] ready.");
32
+ },
33
+
34
+ widgets: [
35
+ {
36
+ id: "blog-stats",
37
+ name: "Blog Stats",
38
+ area: "dashboard-stats",
39
+ component: BlogStatsWidget,
40
+ priority: 20,
41
+ },
42
+ {
43
+ id: "recent-posts",
44
+ name: "Recent Posts",
45
+ area: "dashboard-main",
46
+ component: RecentPostsWidget,
47
+ priority: 20,
48
+ },
49
+ {
50
+ id: "recent-comments",
51
+ name: "Recent Comments",
52
+ area: "dashboard-main",
53
+ component: RecentCommentsWidget,
54
+ priority: 30,
55
+ },
56
+ ],
57
+
58
+ onEnable: async () => {
59
+ console.log("[BlogModule] enabling and registering permissions...");
60
+
61
+ try {
62
+ // 1. Create permissions
63
+ for (const perm of BLOG_PERMISSIONS) {
64
+ await createPermission(perm.name, perm.description);
65
+ }
66
+
67
+ // 2. Assign to admin role
68
+ const roles = await getRoles();
69
+ const adminRole = roles.find((r) => r.name === "admin");
70
+
71
+ if (adminRole) {
72
+ const blogPermNames = BLOG_PERMISSIONS.map((p) => p.name);
73
+ const blogPerms = await db
74
+ .select()
75
+ .from(permissionsTable)
76
+ .where(inArray(permissionsTable.name, blogPermNames));
77
+
78
+ for (const p of blogPerms) {
79
+ await assignPermissionToRole(adminRole.id, p.id);
80
+ }
81
+ console.log("[BlogModule] Permissions assigned to admin role.");
82
+ }
83
+ } catch (error) {
84
+ console.error(
85
+ "[BlogModule] Error during permission registration:",
86
+ error,
87
+ );
88
+ }
89
+
90
+ console.log("[BlogModule] enabled.");
91
+ },
92
+
93
+ onDisable: async () => {
94
+ console.log("[Blog] onDisable: Cleaning up tables and permissions...");
95
+ try {
96
+ // 1. Remove permissions (cascades to role-permission mappings)
97
+ const blogPermNames = BLOG_PERMISSIONS.map((p) => p.name);
98
+ await db
99
+ .delete(permissionsTable)
100
+ .where(inArray(permissionsTable.name, blogPermNames));
101
+ console.log("[BlogModule] Permissions and mappings removed.");
102
+
103
+ // 2. Drop tables
104
+ const tables = ["blog_posts", "blog_comments"];
105
+ for (const table of tables) {
106
+ await db.execute(sql.raw(`DROP TABLE IF EXISTS ${table} CASCADE`));
107
+ }
108
+ } catch (e) {
109
+ console.error("[Blog] onDisable Error:", e);
110
+ }
111
+ },
112
+
113
+ routes: {
114
+ public: publicRoutes,
115
+ private: privateRoutes,
116
+ },
117
+
118
+ navigation: navigation,
119
+ };
120
+
121
+ export default blogModule;
package/src/intl.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type messages from "../locales/en/global.json";
2
+
3
+ type JsonDataType = typeof messages;
4
+
5
+ declare module "@arch-cadre/intl" {
6
+ export interface IntlMessages extends JsonDataType {}
7
+ }
@@ -0,0 +1,2 @@
1
+ import { type ClassValue } from "clsx";
2
+ export declare function cn(...inputs: ClassValue[]): string;
@@ -0,0 +1,5 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ export function cn(...inputs) {
4
+ return twMerge(clsx(inputs));
5
+ }
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,24 @@
1
+ import { z } from "zod";
2
+ export declare const postSchema: z.ZodObject<{
3
+ title: z.ZodString;
4
+ content: z.ZodString;
5
+ slug: z.ZodString;
6
+ }, "strip", z.ZodTypeAny, {
7
+ title: string;
8
+ slug: string;
9
+ content: string;
10
+ }, {
11
+ title: string;
12
+ slug: string;
13
+ content: string;
14
+ }>;
15
+ export declare const commentSchema: z.ZodObject<{
16
+ content: z.ZodString;
17
+ postId: z.ZodString;
18
+ }, "strip", z.ZodTypeAny, {
19
+ content: string;
20
+ postId: string;
21
+ }, {
22
+ content: string;
23
+ postId: string;
24
+ }>;
@@ -0,0 +1,11 @@
1
+ import { z } from "zod";
2
+ // --- Validation Schemas ---
3
+ export const postSchema = z.object({
4
+ title: z.string().min(3, "Title must be at least 3 characters"),
5
+ content: z.string().min(10, "Content must be at least 10 characters"),
6
+ slug: z.string().min(3, "Slug is required"),
7
+ });
8
+ export const commentSchema = z.object({
9
+ content: z.string().min(2, "Comment is too short"),
10
+ postId: z.string().uuid(),
11
+ });