@rebasepro/cli 0.0.1-canary.2 → 0.0.1-canary.4829d6e

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 (83) hide show
  1. package/LICENSE +17 -196
  2. package/README.md +57 -33
  3. package/dist/commands/api-keys.d.ts +1 -0
  4. package/dist/commands/auth.d.ts +1 -0
  5. package/dist/commands/build.d.ts +1 -0
  6. package/dist/commands/cloud/auth.d.ts +3 -0
  7. package/dist/commands/cloud/context.d.ts +61 -0
  8. package/dist/commands/cloud/databases.d.ts +1 -0
  9. package/dist/commands/cloud/deploy.d.ts +2 -0
  10. package/dist/commands/cloud/index.d.ts +1 -0
  11. package/dist/commands/cloud/link.d.ts +5 -0
  12. package/dist/commands/cloud/orgs.d.ts +1 -0
  13. package/dist/commands/cloud/projects.d.ts +13 -0
  14. package/dist/commands/cloud/resources.d.ts +6 -0
  15. package/dist/commands/db.d.ts +1 -0
  16. package/dist/commands/dev.d.ts +1 -0
  17. package/dist/commands/doctor.d.ts +1 -0
  18. package/dist/commands/generate_sdk.d.ts +18 -0
  19. package/dist/commands/init.d.ts +40 -0
  20. package/dist/commands/schema.d.ts +1 -0
  21. package/dist/commands/skills.d.ts +1 -0
  22. package/dist/commands/start.d.ts +1 -0
  23. package/dist/index.d.ts +11 -1
  24. package/dist/index.es.js +3952 -245
  25. package/dist/index.es.js.map +1 -1
  26. package/dist/utils/package-manager.d.ts +33 -0
  27. package/dist/utils/project.d.ts +60 -0
  28. package/package.json +35 -23
  29. package/templates/overlays/baas/README.md +37 -0
  30. package/templates/overlays/baas/backend/package.json +31 -0
  31. package/templates/overlays/baas/backend/src/index.ts +172 -0
  32. package/templates/overlays/baas/backend/tsconfig.json +18 -0
  33. package/templates/overlays/baas/package.json +42 -0
  34. package/templates/overlays/baas/pnpm-workspace.yaml +9 -0
  35. package/templates/template/.cursorrules +2 -0
  36. package/templates/template/.dockerignore +28 -0
  37. package/templates/template/.env.example +120 -0
  38. package/templates/template/.github/copilot-instructions.md +2 -0
  39. package/templates/template/.windsurfrules +2 -0
  40. package/templates/template/AGENTS.md +2 -0
  41. package/templates/template/CLAUDE.md +2 -0
  42. package/templates/template/README.md +88 -21
  43. package/templates/template/ai-instructions.md +17 -0
  44. package/templates/template/backend/Dockerfile +57 -11
  45. package/templates/template/backend/functions/hello.ts +56 -0
  46. package/templates/template/backend/package.json +30 -34
  47. package/templates/template/backend/src/env.ts +23 -0
  48. package/templates/template/backend/src/index.ts +140 -100
  49. package/templates/template/backend/tsconfig.json +1 -1
  50. package/templates/template/config/collections/authors.ts +45 -0
  51. package/templates/template/config/collections/index.ts +26 -0
  52. package/templates/template/{shared → config}/collections/posts.ts +28 -10
  53. package/templates/template/config/collections/presets/blank/index.ts +3 -0
  54. package/templates/template/config/collections/presets/ecommerce/categories.ts +40 -0
  55. package/templates/template/config/collections/presets/ecommerce/index.ts +6 -0
  56. package/templates/template/config/collections/presets/ecommerce/orders.ts +66 -0
  57. package/templates/template/config/collections/presets/ecommerce/products.ts +64 -0
  58. package/templates/template/config/collections/tags.ts +25 -0
  59. package/templates/template/config/collections/users.ts +142 -0
  60. package/templates/template/{shared → config}/index.ts +1 -1
  61. package/templates/template/config/package.json +28 -0
  62. package/templates/template/docker-compose.yml +50 -14
  63. package/templates/template/frontend/Dockerfile +39 -15
  64. package/templates/template/frontend/nginx.conf +40 -0
  65. package/templates/template/frontend/package.json +13 -13
  66. package/templates/template/frontend/src/App.tsx +30 -116
  67. package/templates/template/frontend/src/index.css +15 -1
  68. package/templates/template/frontend/src/main.tsx +6 -2
  69. package/templates/template/frontend/src/virtual.d.ts +6 -0
  70. package/templates/template/frontend/tsconfig.json +3 -2
  71. package/templates/template/frontend/vite.config.ts +48 -5
  72. package/templates/template/package.json +21 -12
  73. package/templates/template/pnpm-workspace.yaml +13 -0
  74. package/templates/template/scripts/example.ts +94 -0
  75. package/dist/auth.d.ts +0 -5
  76. package/dist/index.cjs +0 -293
  77. package/dist/index.cjs.map +0 -1
  78. package/templates/template/.env.template +0 -31
  79. package/templates/template/backend/drizzle.config.ts +0 -22
  80. package/templates/template/backend/scripts/db-generate.ts +0 -60
  81. package/templates/template/shared/collections/index.ts +0 -3
  82. package/templates/template/shared/package.json +0 -28
  83. /package/templates/template/{shared → config}/tsconfig.json +0 -0
@@ -1,134 +1,174 @@
1
- import express from "express";
2
- import cors from "cors";
1
+ import { Hono } from "hono";
2
+ import { cors } from "hono/cors";
3
+ import { secureHeaders } from "hono/secure-headers";
4
+ import { getRequestListener } from "@hono/node-server";
5
+ import { createServer } from "http";
3
6
  import path from "path";
4
7
  import { fileURLToPath } from "url";
5
- import { createServer } from "http";
6
- import { createPostgresDatabaseConnection, initializeRebaseAPI, initializeRebaseBackend, serveSPA } from "@rebasepro/backend";
7
-
8
- import { enums, relations, tables } from "./schema.generated";
9
-
10
- import * as dotenv from "dotenv";
8
+ import {
9
+ initializeRebaseBackend,
10
+ installShutdownHandlers,
11
+ serveSPA,
12
+ HonoEnv,
13
+ listenWithPortRetry,
14
+ cleanupDevPortFile,
15
+ logger
16
+ } from "@rebasepro/server";
17
+ import { createPostgresDatabaseConnection, createPostgresAdapter } from "@rebasepro/server-postgres";
18
+ import { enums, relations, tables } from "./schema.generated.js";
19
+ import { env } from "./env.js";
20
+ import usersCollection from "../../config/collections/users.js";
11
21
 
12
22
  const __filename = fileURLToPath(import.meta.url);
13
23
  const __dirname = path.dirname(__filename);
14
24
 
15
- // Load environment from project root
16
- dotenv.config({ path: path.resolve(__dirname, "../../.env") });
25
+ // ─── App ─────────────────────────────────────────────────────────────
26
+ const app: Hono<HonoEnv> = new Hono<HonoEnv>();
27
+
28
+ const isProduction = env.NODE_ENV === "production";
29
+ const allowedOrigins = isProduction
30
+ ? (() => {
31
+ const origins = env.CORS_ORIGINS || env.FRONTEND_URL;
32
+ if (!origins) {
33
+ throw new Error(
34
+ "CORS_ORIGINS or FRONTEND_URL must be set in production. " +
35
+ "Example: CORS_ORIGINS=https://yourdomain.com"
36
+ );
37
+ }
38
+ return origins.split(",").map(s => s.trim());
39
+ })()
40
+ : [];
41
+
42
+ app.use("/*", cors({
43
+ origin: (origin) => {
44
+ if (!isProduction) return origin || "*";
45
+ return allowedOrigins.includes(origin) ? origin : null;
46
+ },
47
+ credentials: true
48
+ }));
17
49
 
18
- const app = express();
19
- const server = createServer(app);
50
+ app.use("/*", secureHeaders());
20
51
 
21
- // PostgreSQL connection
22
- const databaseUrl = process.env.DATABASE_URL;
23
- if (!databaseUrl) {
24
- throw new Error("DATABASE_URL environment variable is not set");
25
- }
26
- const db = createPostgresDatabaseConnection(databaseUrl);
52
+ // ─── Database ────────────────────────────────────────────────────────
53
+ const databaseUrl = env.DATABASE_URL;
27
54
 
28
- // Middleware
29
- app.use(cors());
30
- app.use(express.json({ limit: "10mb" }));
55
+ const { db, pool, connectionString } = createPostgresDatabaseConnection(databaseUrl);
31
56
 
57
+ // ─── Start ───────────────────────────────────────────────────────────
32
58
  async function startServer() {
33
- const jwtSecret = process.env.JWT_SECRET;
34
- if (!jwtSecret) {
35
- throw new Error("JWT_SECRET environment variable is not set");
36
- }
59
+ const jwtSecret = env.JWT_SECRET;
60
+ const PORT = env.PORT;
61
+ const server = createServer(getRequestListener(app.fetch));
37
62
 
38
- // Initialize Rebase Backend with auth
39
63
  const backend = await initializeRebaseBackend({
40
- collectionsDir: path.resolve(__dirname, "../../shared/collections"),
64
+ collectionsDir: path.resolve(__dirname, "../../config/collections"),
65
+ functionsDir: path.resolve(__dirname, "../functions"),
41
66
  server,
42
67
  app,
43
- datasource: {
44
- "(default)": {
45
- connection: db,
46
- schema: { tables, enums, relations },
47
- adminConnectionString: process.env.ADMIN_CONNECTION_STRING || process.env.DATABASE_URL
48
- }
49
- },
68
+ database: createPostgresAdapter({
69
+ connection: db,
70
+ schema: { tables,
71
+ enums,
72
+ relations },
73
+ adminConnectionString: env.ADMIN_CONNECTION_STRING || databaseUrl,
74
+ connectionString
75
+ }),
50
76
  auth: {
77
+ collection: usersCollection,
51
78
  jwtSecret,
52
- accessExpiresIn: process.env.JWT_ACCESS_EXPIRES_IN || "1h",
53
- refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN || "30d",
54
- google: process.env.GOOGLE_CLIENT_ID ? {
55
- clientId: process.env.GOOGLE_CLIENT_ID
56
- } : undefined,
57
- seedDefaultRoles: true,
58
- allowRegistration: process.env.ALLOW_REGISTRATION === "true",
59
- email: process.env.SMTP_HOST ? {
60
- from: process.env.SMTP_FROM || "noreply@example.com",
61
- appName: process.env.APP_NAME || "Rebase",
62
- resetPasswordUrl: process.env.FRONTEND_URL || "http://localhost:5173",
63
- verifyEmailUrl: process.env.FRONTEND_URL || "http://localhost:5173",
64
- smtp: {
65
- host: process.env.SMTP_HOST,
66
- port: parseInt(process.env.SMTP_PORT || "587"),
67
- secure: process.env.SMTP_SECURE === "true",
68
- auth: process.env.SMTP_USER ? {
69
- user: process.env.SMTP_USER,
70
- pass: process.env.SMTP_PASS || ""
71
- } : undefined
79
+ accessExpiresIn: env.JWT_ACCESS_EXPIRES_IN,
80
+ refreshExpiresIn: env.JWT_REFRESH_EXPIRES_IN,
81
+ serviceKey: env.REBASE_SERVICE_KEY,
82
+ // Cookie-based auth: the refresh token is stored in an httpOnly
83
+ // cookie (not readable by JS) instead of localStorage, so it is
84
+ // not exposed to XSS. The frontend opts in via
85
+ // `authFlowMode: "cookie"` on createRebaseClient. Requires CORS
86
+ // `credentials: true` (set above).
87
+ cookieAuth: { sameSite: "Lax" },
88
+ google: env.GOOGLE_CLIENT_ID
89
+ ? { clientId: env.GOOGLE_CLIENT_ID }
90
+ : undefined,
91
+ allowRegistration: env.ALLOW_REGISTRATION,
92
+ email: env.SMTP_HOST
93
+ ? {
94
+ from: env.SMTP_FROM || `${env.APP_NAME} <noreply@rebase.pro>`,
95
+ smtp: {
96
+ host: env.SMTP_HOST,
97
+ port: env.SMTP_PORT,
98
+ secure: env.SMTP_SECURE,
99
+ auth: env.SMTP_USER
100
+ ? { user: env.SMTP_USER,
101
+ pass: env.SMTP_PASS! }
102
+ : undefined,
103
+ name: env.SMTP_NAME
104
+ },
105
+ appName: env.APP_NAME,
106
+ resetPasswordUrl: env.FRONTEND_URL
72
107
  }
73
- } : undefined
108
+ : undefined
74
109
  },
75
- storage: {
76
- type: "local",
77
- basePath: path.resolve(__dirname, "../../uploads")
78
- }
110
+ storage: env.STORAGE_TYPE === "s3"
111
+ ? {
112
+ type: "s3",
113
+ bucket: env.S3_BUCKET!,
114
+ region: env.S3_REGION || "auto",
115
+ accessKeyId: env.S3_ACCESS_KEY_ID || "",
116
+ secretAccessKey: env.S3_SECRET_ACCESS_KEY || "",
117
+ endpoint: env.S3_ENDPOINT,
118
+ forcePathStyle: env.S3_FORCE_PATH_STYLE
119
+ }
120
+ : {
121
+ type: "local",
122
+ basePath: env.STORAGE_PATH || path.resolve(__dirname, "../../uploads")
123
+ },
124
+ history: true
79
125
  });
80
126
 
81
- // Initialize REST/GraphQL API
82
- await initializeRebaseAPI(app, backend, {
83
- basePath: "/api",
84
- collectionsDir: path.resolve(__dirname, "../../shared/collections"),
85
- enableGraphQL: true,
86
- enableREST: true,
87
- cors: {
88
- origin: true,
89
- credentials: true
90
- }
127
+ // ─── Health check ─────────────────────────────────────────────
128
+ app.get("/health", async (c) => {
129
+ const result = await backend.healthCheck();
130
+ const status = result.healthy ? 200 : 503;
131
+ return c.json({
132
+ status: result.healthy ? "ok" : "degraded",
133
+ latencyMs: result.latencyMs,
134
+ ...(result.details ? { details: result.details } : {})
135
+ }, status);
91
136
  });
92
137
 
93
- // Serve SPA in production
94
- if (process.env.NODE_ENV === "production") {
95
- serveSPA(app, {
96
- frontendPath: path.join(__dirname, "../../frontend/dist"),
97
- excludePaths: ["/health"]
98
- });
138
+ // Serve the frontend in production
139
+ if (isProduction) {
140
+ serveSPA(app, { frontendPath: path.join(__dirname, "../../frontend/dist") });
99
141
  }
100
142
 
101
- app.get("/health", (req, res) => {
102
- res.json({
103
- status: "ok",
104
- timestamp: new Date().toISOString(),
105
- environment: process.env.NODE_ENV
106
- });
107
- });
108
-
109
- // Error handling
110
- app.use((error: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
111
- console.error("Unhandled error:", error);
112
- res.status(500).json({
113
- error: "Internal server error",
114
- message: error.message
143
+ if (!isProduction) {
144
+ // Dev mode: retry the next port if the current one is in use
145
+ const projectRoot = path.resolve(__dirname, "../..");
146
+ const actualPort = await listenWithPortRetry(server, PORT, { portFileDir: projectRoot, serviceKey: env.REBASE_SERVICE_KEY });
147
+
148
+ // Clean up port file on exit
149
+ const cleanup = () => cleanupDevPortFile(projectRoot);
150
+ process.on("SIGINT", cleanup);
151
+ process.on("SIGTERM", cleanup);
152
+ process.on("exit", cleanup);
153
+
154
+ logger.info(`Server running at http://localhost:${actualPort}`);
155
+ } else {
156
+ server.listen(PORT, () => {
157
+ logger.info(`Server running at http://localhost:${PORT}`);
115
158
  });
116
- });
117
-
118
- const PORT = process.env.PORT || 3001;
159
+ }
119
160
 
120
- server.listen(PORT, () => {
121
- console.log(`🚀 Server running at http://localhost:${PORT}`);
122
- console.log(` • REST API: http://localhost:${PORT}/api`);
123
- console.log(` • GraphQL: http://localhost:${PORT}/api/graphql`);
124
- console.log(` • Swagger docs: http://localhost:${PORT}/api/swagger`);
125
- console.log(` • Health: http://localhost:${PORT}/health`);
161
+ // ─── Graceful Shutdown ───────────────────────────────────────────────
162
+ // Drains HTTP, stops crons, tears down realtime, then closes the pool.
163
+ // Guards against double signals and force-exits if shutdown hangs.
164
+ installShutdownHandlers(backend, {
165
+ onCleanup: () => pool.end()
126
166
  });
127
167
  }
128
168
 
129
169
  startServer().catch(err => {
130
- console.error("Failed to start server:", err);
170
+ logger.error("Failed to start server", { error: err instanceof Error ? err : new Error(String(err)) });
131
171
  process.exit(1);
132
172
  });
133
173
 
134
- export { app, server };
174
+ export { app };
@@ -15,5 +15,5 @@
15
15
  "sourceMap": true,
16
16
  "jsx": "preserve"
17
17
  },
18
- "include": ["src/**/*", "../shared/**/*", "drizzle.config.ts"]
18
+ "include": ["src/**/*", "../config/**/*", "drizzle.config.ts"]
19
19
  }
@@ -0,0 +1,45 @@
1
+ import { CollectionConfig } from "@rebasepro/types";
2
+
3
+ const authorsCollection: CollectionConfig = {
4
+ name: "Authors",
5
+ singularName: "Author",
6
+ slug: "authors",
7
+ table: "authors",
8
+ icon: "Person",
9
+ properties: {
10
+ id: {
11
+ name: "ID",
12
+ type: "number",
13
+ isId: "increment"
14
+ },
15
+ name: {
16
+ name: "Name",
17
+ type: "string",
18
+ validation: {
19
+ required: true
20
+ }
21
+ },
22
+ email: {
23
+ name: "Email",
24
+ type: "string",
25
+ validation: {
26
+ required: true
27
+ }
28
+ },
29
+ picture: {
30
+ name: "Picture",
31
+ type: "string",
32
+ storage: {
33
+ storagePath: "author_pictures/"
34
+ }
35
+ }
36
+ },
37
+ propertiesOrder: [
38
+ "id",
39
+ "name",
40
+ "email",
41
+ "picture"
42
+ ]
43
+ };
44
+
45
+ export default authorsCollection;
@@ -0,0 +1,26 @@
1
+ import postsCollection from "./posts.js";
2
+ import authorsCollection from "./authors.js";
3
+ import tagsCollection from "./tags.js";
4
+ import usersCollection from "./users.js";
5
+ import type { SecurityRule } from "@rebasepro/types";
6
+
7
+ export const collections = [postsCollection, authorsCollection, tagsCollection, usersCollection];
8
+
9
+ /**
10
+ * Applied to any collection in this directory that declares no
11
+ * `securityRules` of its own: anyone can read, only admins can write.
12
+ *
13
+ * These live here, next to the collections, because `rebase db push`
14
+ * generates the Postgres policies from these files — that is what actually
15
+ * enforces access. A default declared on the server could never reach the
16
+ * database.
17
+ *
18
+ * A collection that declares neither its own rules nor inherits these is
19
+ * locked by default: admin-only.
20
+ */
21
+ export const defaultSecurityRules: SecurityRule[] = [
22
+ { operation: "select",
23
+ access: "public" },
24
+ { operations: ["insert", "update", "delete"],
25
+ roles: ["admin"] }
26
+ ];
@@ -1,18 +1,18 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import { CollectionConfig } from "@rebasepro/types";
2
+ import authorsCollection from "./authors.js";
3
+ import tagsCollection from "./tags.js";
2
4
 
3
- const postsCollection: EntityCollection = {
5
+ const postsCollection: CollectionConfig = {
4
6
  name: "Posts",
5
7
  singularName: "Post",
6
8
  slug: "posts",
7
- dbPath: "posts",
9
+ table: "posts",
8
10
  icon: "Article",
9
11
  properties: {
10
12
  id: {
11
13
  name: "ID",
12
14
  type: "number",
13
- validation: {
14
- required: true
15
- }
15
+ isId: "increment"
16
16
  },
17
17
  title: {
18
18
  name: "Title",
@@ -24,7 +24,9 @@ const postsCollection: EntityCollection = {
24
24
  content: {
25
25
  name: "Content",
26
26
  type: "string",
27
- multiline: true
27
+ ui: {
28
+ markdown: true
29
+ }
28
30
  },
29
31
  status: {
30
32
  name: "Status",
@@ -33,19 +35,35 @@ const postsCollection: EntityCollection = {
33
35
  {
34
36
  id: "draft",
35
37
  label: "Draft",
36
- color: "grayLight"
38
+ color: "gray"
37
39
  },
38
40
  {
39
41
  id: "review",
40
42
  label: "In Review",
41
- color: "orangeLight"
43
+ color: "orange"
42
44
  },
43
45
  {
44
46
  id: "published",
45
47
  label: "Published",
46
- color: "greenLight"
48
+ color: "green"
47
49
  }
48
50
  ]
51
+ },
52
+ author: {
53
+ name: "Author",
54
+ type: "relation",
55
+ relationName: "author",
56
+ target: () => authorsCollection,
57
+ cardinality: "one",
58
+ direction: "owning"
59
+ },
60
+ tags: {
61
+ name: "Tags",
62
+ type: "relation",
63
+ relationName: "tags",
64
+ target: () => tagsCollection,
65
+ cardinality: "many",
66
+ direction: "owning"
49
67
  }
50
68
  }
51
69
  };
@@ -0,0 +1,3 @@
1
+ import usersCollection from "../../users.js";
2
+
3
+ export const collections = [usersCollection];
@@ -0,0 +1,40 @@
1
+ import { CollectionConfig } from "@rebasepro/types";
2
+
3
+ const categoriesCollection: CollectionConfig = {
4
+ name: "Categories",
5
+ singularName: "Category",
6
+ slug: "categories",
7
+ table: "categories",
8
+ icon: "Category",
9
+ properties: {
10
+ id: {
11
+ name: "ID",
12
+ type: "number",
13
+ isId: "increment"
14
+ },
15
+ name: {
16
+ name: "Name",
17
+ type: "string",
18
+ validation: { required: true }
19
+ },
20
+ slug: {
21
+ name: "Slug",
22
+ type: "string",
23
+ validation: { required: true }
24
+ },
25
+ description: {
26
+ name: "Description",
27
+ type: "string",
28
+ ui: {
29
+ multiline: true
30
+ }
31
+ },
32
+ icon: {
33
+ name: "Icon",
34
+ type: "string"
35
+ }
36
+ },
37
+ propertiesOrder: ["id", "name", "slug", "description", "icon"]
38
+ };
39
+
40
+ export default categoriesCollection;
@@ -0,0 +1,6 @@
1
+ import productsCollection from "./products.js";
2
+ import categoriesCollection from "./categories.js";
3
+ import ordersCollection from "./orders.js";
4
+ import usersCollection from "../../users.js";
5
+
6
+ export const collections = [productsCollection, categoriesCollection, ordersCollection, usersCollection];
@@ -0,0 +1,66 @@
1
+ import { CollectionConfig } from "@rebasepro/types";
2
+
3
+ const ordersCollection: CollectionConfig = {
4
+ name: "Orders",
5
+ singularName: "Order",
6
+ slug: "orders",
7
+ table: "orders",
8
+ icon: "Receipt",
9
+ properties: {
10
+ id: {
11
+ name: "ID",
12
+ type: "number",
13
+ isId: "increment"
14
+ },
15
+ customerEmail: {
16
+ name: "Customer Email",
17
+ type: "string",
18
+ columnName: "customer_email",
19
+ validation: { required: true }
20
+ },
21
+ status: {
22
+ name: "Status",
23
+ type: "string",
24
+ enum: [
25
+ { id: "pending",
26
+ label: "Pending",
27
+ color: "gray" },
28
+ { id: "processing",
29
+ label: "Processing",
30
+ color: "blue" },
31
+ { id: "shipped",
32
+ label: "Shipped",
33
+ color: "orange" },
34
+ { id: "delivered",
35
+ label: "Delivered",
36
+ color: "green" },
37
+ { id: "cancelled",
38
+ label: "Cancelled",
39
+ color: "red" }
40
+ ]
41
+ },
42
+ total: {
43
+ name: "Total",
44
+ type: "number",
45
+ validation: { required: true }
46
+ },
47
+ notes: {
48
+ name: "Notes",
49
+ type: "string",
50
+ ui: {
51
+ multiline: true
52
+ }
53
+ },
54
+ createdAt: {
55
+ name: "Created At",
56
+ type: "date",
57
+ columnName: "created_at",
58
+ autoValue: "on_create",
59
+ ui: {
60
+ readOnly: true
61
+ }
62
+ }
63
+ }
64
+ };
65
+
66
+ export default ordersCollection;
@@ -0,0 +1,64 @@
1
+ import { CollectionConfig } from "@rebasepro/types";
2
+ import categoriesCollection from "./categories.js";
3
+
4
+ const productsCollection: CollectionConfig = {
5
+ name: "Products",
6
+ singularName: "Product",
7
+ slug: "products",
8
+ table: "products",
9
+ icon: "ShoppingCart",
10
+ properties: {
11
+ id: {
12
+ name: "ID",
13
+ type: "number",
14
+ isId: "increment"
15
+ },
16
+ name: {
17
+ name: "Name",
18
+ type: "string",
19
+ validation: { required: true }
20
+ },
21
+ description: {
22
+ name: "Description",
23
+ type: "string",
24
+ ui: {
25
+ markdown: true
26
+ }
27
+ },
28
+ price: {
29
+ name: "Price",
30
+ type: "number",
31
+ validation: { required: true }
32
+ },
33
+ image: {
34
+ name: "Image",
35
+ type: "string",
36
+ storage: { storagePath: "product_images/" }
37
+ },
38
+ status: {
39
+ name: "Status",
40
+ type: "string",
41
+ enum: [
42
+ { id: "draft",
43
+ label: "Draft",
44
+ color: "gray" },
45
+ { id: "active",
46
+ label: "Active",
47
+ color: "green" },
48
+ { id: "archived",
49
+ label: "Archived",
50
+ color: "orange" }
51
+ ]
52
+ },
53
+ category: {
54
+ name: "Category",
55
+ type: "relation",
56
+ relationName: "category",
57
+ target: () => categoriesCollection,
58
+ cardinality: "one",
59
+ direction: "owning"
60
+ }
61
+ }
62
+ };
63
+
64
+ export default productsCollection;
@@ -0,0 +1,25 @@
1
+ import { CollectionConfig } from "@rebasepro/types";
2
+
3
+ const tagsCollection: CollectionConfig = {
4
+ name: "Tags",
5
+ singularName: "Tag",
6
+ slug: "tags",
7
+ table: "tags",
8
+ icon: "Tag",
9
+ properties: {
10
+ id: {
11
+ name: "ID",
12
+ type: "number",
13
+ isId: "increment"
14
+ },
15
+ name: {
16
+ name: "Tag Name",
17
+ type: "string",
18
+ validation: {
19
+ required: true
20
+ }
21
+ }
22
+ }
23
+ };
24
+
25
+ export default tagsCollection;