fabrica-e-commerce 0.1.17 → 0.2.1
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 +12 -5
- package/src/clean.js +184 -0
- package/src/cli.js +84 -54
- package/src/deps.js +160 -52
- package/src/sql.js +227 -16
- package/src/ui.js +100 -73
package/src/deps.js
CHANGED
|
@@ -2,29 +2,22 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import process from 'node:process';
|
|
4
4
|
import { commandExists, runCommand, runCommandCapture } from './system.js';
|
|
5
|
-
import { kv, section, endSections, spinner, subBox, log } from './ui.js';
|
|
5
|
+
import { kv, kvSuccess, kvFail, section, endSections, spinner, subBox, log, logInfo, logWarn, divider, orange, dimOrange, bold, dim, green, red, cyan, yellow } from './ui.js';
|
|
6
6
|
|
|
7
|
-
function isTermux() {
|
|
8
|
-
return
|
|
9
|
-
|
|
7
|
+
export function isTermux() {
|
|
8
|
+
return (
|
|
9
|
+
process.platform === 'android' ||
|
|
10
|
+
(process.platform === 'linux' &&
|
|
11
|
+
(process.env.TERMUX_VERSION || process.env.PREFIX?.includes('com.termux')))
|
|
12
|
+
);
|
|
10
13
|
}
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
return runCommand(command, args, options);
|
|
15
|
+
function platform() {
|
|
16
|
+
if (isTermux()) return 'termux';
|
|
17
|
+
return process.platform; // win32 | linux | darwin
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
return runCommand('bash', ['-c', script], options);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const DEPENDENCIES = [
|
|
23
|
-
{ name: 'git', label: 'Git', check: () => checkWithPathRefresh('git'), install: installGit, manualUrl: 'https://git-scm.com/downloads' },
|
|
24
|
-
{ name: 'gh', label: 'GitHub CLI (gh)', check: () => checkWithPathRefresh('gh'), install: installGithubCli, manualUrl: 'https://github.com/cli/cli#installation' },
|
|
25
|
-
{ name: 'vercel', label: 'Vercel CLI (via npx)', check: checkVercelCli, install: warmVercelCli, manualUrl: 'https://vercel.com/docs/cli' },
|
|
26
|
-
];
|
|
27
|
-
|
|
20
|
+
// ── PATH helpers ──────────────────────────────────────────────────────────────
|
|
28
21
|
function addToPathIfDirectory(directory) {
|
|
29
22
|
if (!directory || !fs.existsSync(directory)) return;
|
|
30
23
|
const current = process.env.PATH || '';
|
|
@@ -47,7 +40,10 @@ function refreshWindowsToolPaths() {
|
|
|
47
40
|
addToPathIfDirectory(pf && path.join(pf, 'nodejs'));
|
|
48
41
|
}
|
|
49
42
|
|
|
50
|
-
async function checkWithPathRefresh(cmd) {
|
|
43
|
+
async function checkWithPathRefresh(cmd) {
|
|
44
|
+
refreshWindowsToolPaths();
|
|
45
|
+
return commandExists(cmd);
|
|
46
|
+
}
|
|
51
47
|
|
|
52
48
|
async function runFirstSuccessful(candidates, options = {}) {
|
|
53
49
|
for (const [cmd, args] of candidates) {
|
|
@@ -57,6 +53,7 @@ async function runFirstSuccessful(candidates, options = {}) {
|
|
|
57
53
|
return { code: 1, stdout: '', stderr: 'All fallback commands failed' };
|
|
58
54
|
}
|
|
59
55
|
|
|
56
|
+
// ── Vercel ────────────────────────────────────────────────────────────────────
|
|
60
57
|
async function checkVercelCli() {
|
|
61
58
|
refreshWindowsToolPaths();
|
|
62
59
|
const r = await runFirstSuccessful([
|
|
@@ -78,12 +75,19 @@ async function warmVercelCli() {
|
|
|
78
75
|
await runCommand('npm', ['install', '-g', 'vercel@latest'], { allowFailure: true });
|
|
79
76
|
}
|
|
80
77
|
|
|
78
|
+
// ── Git ───────────────────────────────────────────────────────────────────────
|
|
81
79
|
async function installGit() {
|
|
82
80
|
refreshWindowsToolPaths();
|
|
83
|
-
const p =
|
|
84
|
-
|
|
81
|
+
const p = platform();
|
|
82
|
+
|
|
83
|
+
if (p === 'termux') {
|
|
84
|
+
return runCommand('pkg', ['install', '-y', 'git'], { allowFailure: true });
|
|
85
|
+
}
|
|
85
86
|
if (p === 'linux') {
|
|
86
|
-
if (await commandExists('apt-get')) {
|
|
87
|
+
if (await commandExists('apt-get')) {
|
|
88
|
+
await runPrivileged('apt-get', ['update', '-qq'], { allowFailure: true });
|
|
89
|
+
return runPrivileged('apt-get', ['install', '-y', 'git'], { allowFailure: true });
|
|
90
|
+
}
|
|
87
91
|
if (await commandExists('dnf')) return runPrivileged('dnf', ['install', '-y', 'git'], { allowFailure: true });
|
|
88
92
|
if (await commandExists('yum')) return runPrivileged('yum', ['install', '-y', 'git'], { allowFailure: true });
|
|
89
93
|
if (await commandExists('pacman')) return runPrivileged('pacman', ['-Sy', '--noconfirm', 'git'], { allowFailure: true });
|
|
@@ -95,30 +99,44 @@ async function installGit() {
|
|
|
95
99
|
return runCommand('xcode-select', ['--install'], { allowFailure: true });
|
|
96
100
|
}
|
|
97
101
|
if (p === 'win32') {
|
|
98
|
-
if (await commandExists('winget')) {
|
|
99
|
-
|
|
102
|
+
if (await commandExists('winget')) {
|
|
103
|
+
await runCommand('winget', ['install', '--id', 'Git.Git', '-e', '--source', 'winget', '--silent'], { allowFailure: true });
|
|
104
|
+
refreshWindowsToolPaths();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (await commandExists('choco')) {
|
|
108
|
+
await runCommand('choco', ['install', 'git', '-y'], { allowFailure: true });
|
|
109
|
+
refreshWindowsToolPaths();
|
|
110
|
+
}
|
|
100
111
|
}
|
|
101
112
|
}
|
|
102
113
|
|
|
114
|
+
// ── GitHub CLI ────────────────────────────────────────────────────────────────
|
|
103
115
|
async function installGithubCli() {
|
|
104
116
|
refreshWindowsToolPaths();
|
|
105
|
-
const p =
|
|
106
|
-
|
|
117
|
+
const p = platform();
|
|
118
|
+
|
|
119
|
+
if (p === 'termux') {
|
|
120
|
+
return runCommand('pkg', ['install', '-y', 'gh'], { allowFailure: true });
|
|
121
|
+
}
|
|
107
122
|
if (p === 'linux') {
|
|
108
123
|
if (await commandExists('apt-get')) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
: runCommandCapture('apt-get', ['install', '-y', 'gh']));
|
|
124
|
+
// Try direct first (gh is in Ubuntu 22.04+ repos)
|
|
125
|
+
const direct = await runCommandCapture('apt-get', ['install', '-y', 'gh']);
|
|
112
126
|
if (direct.code === 0) return;
|
|
127
|
+
// Fall back to GitHub's official APT repo
|
|
113
128
|
const pre = (await commandExists('sudo')) ? 'sudo ' : '';
|
|
114
|
-
await
|
|
115
|
-
'set -e',
|
|
129
|
+
await runCommand('bash', ['-c', [
|
|
130
|
+
'set -e',
|
|
131
|
+
`${pre}apt-get update -qq`,
|
|
132
|
+
`${pre}apt-get install -y curl ca-certificates gnupg`,
|
|
116
133
|
`${pre}mkdir -p -m 755 /etc/apt/keyrings`,
|
|
117
134
|
`curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | ${pre}tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null`,
|
|
118
135
|
`${pre}chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg`,
|
|
119
136
|
`echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | ${pre}tee /etc/apt/sources.list.d/github-cli.list > /dev/null`,
|
|
120
|
-
`${pre}apt-get update
|
|
121
|
-
|
|
137
|
+
`${pre}apt-get update -qq`,
|
|
138
|
+
`${pre}apt-get install -y gh`,
|
|
139
|
+
].join(' && ')], { allowFailure: true });
|
|
122
140
|
return;
|
|
123
141
|
}
|
|
124
142
|
if (await commandExists('dnf')) return runPrivileged('dnf', ['install', '-y', 'gh'], { allowFailure: true });
|
|
@@ -130,45 +148,135 @@ async function installGithubCli() {
|
|
|
130
148
|
if (await commandExists('brew')) return runCommand('brew', ['install', 'gh'], { allowFailure: true });
|
|
131
149
|
}
|
|
132
150
|
if (p === 'win32') {
|
|
133
|
-
if (await commandExists('winget')) {
|
|
134
|
-
|
|
151
|
+
if (await commandExists('winget')) {
|
|
152
|
+
await runCommand('winget', ['install', '--id', 'GitHub.cli', '-e', '--source', 'winget', '--silent'], { allowFailure: true });
|
|
153
|
+
refreshWindowsToolPaths();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (await commandExists('choco')) {
|
|
157
|
+
await runCommand('choco', ['install', 'gh', '-y'], { allowFailure: true });
|
|
158
|
+
refreshWindowsToolPaths();
|
|
159
|
+
}
|
|
135
160
|
}
|
|
136
161
|
}
|
|
137
162
|
|
|
163
|
+
async function runPrivileged(command, args, options = {}) {
|
|
164
|
+
const p = platform();
|
|
165
|
+
if (p === 'win32' || p === 'termux') return runCommand(command, args, options);
|
|
166
|
+
if (await commandExists('sudo')) return runCommand('sudo', [command, ...args], options);
|
|
167
|
+
return runCommand(command, args, options);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── dependency table ──────────────────────────────────────────────────────────
|
|
171
|
+
const DEPENDENCIES = [
|
|
172
|
+
{
|
|
173
|
+
name: 'git',
|
|
174
|
+
label: 'Git',
|
|
175
|
+
description: 'Version control — clones the storefront repo',
|
|
176
|
+
check: () => checkWithPathRefresh('git'),
|
|
177
|
+
install: installGit,
|
|
178
|
+
manualUrl: 'https://git-scm.com/downloads',
|
|
179
|
+
termuxPkg: 'git',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: 'gh',
|
|
183
|
+
label: 'GitHub CLI',
|
|
184
|
+
description: 'Creates your GitHub repo and manages auth',
|
|
185
|
+
check: () => checkWithPathRefresh('gh'),
|
|
186
|
+
install: installGithubCli,
|
|
187
|
+
manualUrl: 'https://github.com/cli/cli#installation',
|
|
188
|
+
termuxPkg: 'gh',
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: 'vercel',
|
|
192
|
+
label: 'Vercel CLI',
|
|
193
|
+
description: 'Deploys the storefront to Vercel cloud',
|
|
194
|
+
check: checkVercelCli,
|
|
195
|
+
install: warmVercelCli,
|
|
196
|
+
manualUrl: 'https://vercel.com/docs/cli',
|
|
197
|
+
termuxPkg: null, // via npx only
|
|
198
|
+
},
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
// ── ensureDependencies (shared) ───────────────────────────────────────────────
|
|
138
202
|
export async function ensureDependencies({ autoInstall = true, names } = {}) {
|
|
139
203
|
const targets = names ? DEPENDENCIES.filter((d) => names.includes(d.name)) : DEPENDENCIES;
|
|
140
204
|
const results = [];
|
|
141
205
|
for (const dep of targets) {
|
|
142
|
-
const spin = spinner(`Checking ${dep.label}
|
|
206
|
+
const spin = spinner(`Checking ${dep.label}…`);
|
|
143
207
|
let present = await dep.check();
|
|
144
|
-
if (present) {
|
|
145
|
-
|
|
208
|
+
if (present) {
|
|
209
|
+
spin.succeed(`${green(dep.label)} is ready`);
|
|
210
|
+
results.push({ ...dep, present, installed: false });
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
spin.fail(`${dep.label} not found`);
|
|
146
214
|
if (!autoInstall) { results.push({ ...dep, present: false, installed: false }); continue; }
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
215
|
+
|
|
216
|
+
const p = platform();
|
|
217
|
+
if (p === 'termux' && dep.termuxPkg === null) {
|
|
218
|
+
// Vercel on Termux — npx-only, attempt inline
|
|
219
|
+
const wSpin = spinner(`Warming Vercel CLI via npx (Termux)…`);
|
|
220
|
+
await dep.install();
|
|
221
|
+
present = await dep.check();
|
|
222
|
+
if (present) wSpin.succeed(`Vercel CLI ready via npx`);
|
|
223
|
+
else wSpin.fail(`Vercel CLI not available — use: npx vercel`);
|
|
224
|
+
} else {
|
|
225
|
+
const installSpin = spinner(`Auto-installing ${dep.label}…`);
|
|
226
|
+
await dep.install();
|
|
227
|
+
present = await dep.check();
|
|
228
|
+
if (present) installSpin.succeed(`${green(dep.label)} installed successfully`);
|
|
229
|
+
else installSpin.fail(`${red(dep.label)} could not be installed automatically`);
|
|
230
|
+
}
|
|
152
231
|
results.push({ ...dep, present, installed: present });
|
|
153
232
|
}
|
|
154
233
|
return results;
|
|
155
234
|
}
|
|
156
235
|
|
|
236
|
+
// ── vins command ──────────────────────────────────────────────────────────────
|
|
157
237
|
export async function vinsCommand() {
|
|
158
|
-
|
|
159
|
-
|
|
238
|
+
const p = platform();
|
|
239
|
+
const platformLabel = p === 'termux' ? 'Termux / Android' : p === 'win32' ? 'Windows' : p === 'darwin' ? 'macOS' : 'Linux';
|
|
240
|
+
|
|
241
|
+
section('System info');
|
|
242
|
+
kv('Platform', platformLabel);
|
|
243
|
+
kv('Node.js', process.version);
|
|
244
|
+
kv('Arch', process.arch);
|
|
245
|
+
if (p === 'termux') {
|
|
246
|
+
logInfo('Termux detected — using pkg for native installs, npx for Node tools');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
section('Checking dependencies');
|
|
160
250
|
const results = await ensureDependencies();
|
|
161
251
|
|
|
162
|
-
section('
|
|
163
|
-
|
|
252
|
+
section('Dependency summary');
|
|
253
|
+
divider();
|
|
164
254
|
let allGood = true;
|
|
165
255
|
for (const dep of results) {
|
|
166
|
-
|
|
167
|
-
|
|
256
|
+
if (dep.present) {
|
|
257
|
+
kvSuccess(dep.label, dep.description);
|
|
258
|
+
} else {
|
|
259
|
+
kvFail(dep.label, `Missing — ${dep.description}`);
|
|
260
|
+
allGood = false;
|
|
261
|
+
}
|
|
168
262
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
263
|
+
divider();
|
|
264
|
+
|
|
265
|
+
const missing = results.filter((d) => !d.present);
|
|
266
|
+
if (missing.length) {
|
|
267
|
+
logWarn(`${missing.length} dependency(ies) could not be installed automatically`);
|
|
268
|
+
for (const dep of missing) {
|
|
269
|
+
if (p === 'termux' && dep.termuxPkg) {
|
|
270
|
+
log(`Install ${dep.label}: ${cyan(`pkg install ${dep.termuxPkg}`)}`);
|
|
271
|
+
} else {
|
|
272
|
+
log(`Install ${dep.label}: ${cyan(dep.manualUrl)}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
log('Then re-run: fabrica vins');
|
|
276
|
+
} else {
|
|
277
|
+
log(green('All dependencies ready — run: fabrica build'));
|
|
278
|
+
}
|
|
279
|
+
|
|
172
280
|
endSections();
|
|
173
281
|
if (!allGood) process.exitCode = 1;
|
|
174
282
|
return results;
|
package/src/sql.js
CHANGED
|
@@ -1,24 +1,234 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// SCHEMA
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1
4
|
export const SCHEMA_SQL = `
|
|
2
5
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
3
|
-
|
|
4
|
-
CREATE TABLE IF NOT EXISTS public.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
CREATE TABLE IF NOT EXISTS public.
|
|
12
|
-
|
|
6
|
+
|
|
7
|
+
CREATE TABLE IF NOT EXISTS public.site_content (
|
|
8
|
+
id text NOT NULL,
|
|
9
|
+
content jsonb NOT NULL,
|
|
10
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
11
|
+
CONSTRAINT site_content_pkey PRIMARY KEY (id)
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
CREATE TABLE IF NOT EXISTS public.collections (
|
|
15
|
+
id text NOT NULL,
|
|
16
|
+
name text NOT NULL,
|
|
17
|
+
description text NOT NULL,
|
|
18
|
+
image_url text NOT NULL,
|
|
19
|
+
item_count_label text NOT NULL,
|
|
20
|
+
sort_order integer NOT NULL DEFAULT 0,
|
|
21
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
22
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
23
|
+
CONSTRAINT collections_pkey PRIMARY KEY (id)
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
CREATE TABLE IF NOT EXISTS public.products (
|
|
27
|
+
id text NOT NULL,
|
|
28
|
+
name text NOT NULL,
|
|
29
|
+
price numeric NOT NULL CHECK (price >= 0),
|
|
30
|
+
main_image_url text NOT NULL,
|
|
31
|
+
gallery_image_urls text[] NOT NULL DEFAULT '{}',
|
|
32
|
+
category_label text,
|
|
33
|
+
description text NOT NULL,
|
|
34
|
+
details text[] NOT NULL DEFAULT '{}',
|
|
35
|
+
sizes text[] NOT NULL DEFAULT '{}',
|
|
36
|
+
section text NOT NULL DEFAULT 'general' CHECK (section = ANY (ARRAY['general','new_arrivals','best_sellers'])),
|
|
37
|
+
collection_ids text[] NOT NULL DEFAULT '{}',
|
|
38
|
+
sort_order integer NOT NULL DEFAULT 0,
|
|
39
|
+
is_active boolean NOT NULL DEFAULT true,
|
|
40
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
41
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
42
|
+
CONSTRAINT products_pkey PRIMARY KEY (id)
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
CREATE TABLE IF NOT EXISTS public.app_users (
|
|
46
|
+
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
47
|
+
email text NOT NULL UNIQUE,
|
|
48
|
+
first_name text NOT NULL,
|
|
49
|
+
last_name text NOT NULL,
|
|
50
|
+
phone text NOT NULL,
|
|
51
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
52
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
53
|
+
CONSTRAINT app_users_pkey PRIMARY KEY (id)
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
CREATE TABLE IF NOT EXISTS public.saved_addresses (
|
|
57
|
+
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
58
|
+
user_id uuid NOT NULL,
|
|
59
|
+
label text NOT NULL,
|
|
60
|
+
first_name text NOT NULL,
|
|
61
|
+
last_name text NOT NULL,
|
|
62
|
+
phone text NOT NULL,
|
|
63
|
+
address text NOT NULL,
|
|
64
|
+
apartment text,
|
|
65
|
+
city text NOT NULL,
|
|
66
|
+
state text NOT NULL,
|
|
67
|
+
zip_code text NOT NULL,
|
|
68
|
+
country text NOT NULL DEFAULT 'India',
|
|
69
|
+
is_default boolean NOT NULL DEFAULT false,
|
|
70
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
71
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
72
|
+
CONSTRAINT saved_addresses_pkey PRIMARY KEY (id),
|
|
73
|
+
CONSTRAINT saved_addresses_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.app_users(id)
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
CREATE TABLE IF NOT EXISTS public.order_policies (
|
|
77
|
+
id boolean NOT NULL DEFAULT true CHECK (id = true),
|
|
78
|
+
shipping_amount numeric NOT NULL DEFAULT 15 CHECK (shipping_amount >= 0),
|
|
79
|
+
free_shipping_threshold numeric NOT NULL DEFAULT 200 CHECK (free_shipping_threshold >= 0),
|
|
80
|
+
tax_rate numeric NOT NULL DEFAULT 8 CHECK (tax_rate >= 0),
|
|
81
|
+
automatic_shipping_enabled boolean NOT NULL DEFAULT false,
|
|
82
|
+
shippo_from_name text NOT NULL DEFAULT '',
|
|
83
|
+
shippo_from_company text NOT NULL DEFAULT '',
|
|
84
|
+
shippo_from_street1 text NOT NULL DEFAULT '',
|
|
85
|
+
shippo_from_street2 text NOT NULL DEFAULT '',
|
|
86
|
+
shippo_from_city text NOT NULL DEFAULT '',
|
|
87
|
+
shippo_from_state text NOT NULL DEFAULT '',
|
|
88
|
+
shippo_from_zip text NOT NULL DEFAULT '',
|
|
89
|
+
shippo_from_country text NOT NULL DEFAULT 'IN',
|
|
90
|
+
shippo_from_phone text NOT NULL DEFAULT '',
|
|
91
|
+
shippo_from_email text NOT NULL DEFAULT '',
|
|
92
|
+
shippo_from_is_residential boolean NOT NULL DEFAULT false,
|
|
93
|
+
shippo_parcel_length numeric NOT NULL DEFAULT 10 CHECK (shippo_parcel_length > 0),
|
|
94
|
+
shippo_parcel_width numeric NOT NULL DEFAULT 10 CHECK (shippo_parcel_width > 0),
|
|
95
|
+
shippo_parcel_height numeric NOT NULL DEFAULT 4 CHECK (shippo_parcel_height > 0),
|
|
96
|
+
shippo_parcel_weight numeric NOT NULL DEFAULT 1 CHECK (shippo_parcel_weight > 0),
|
|
97
|
+
shippo_parcel_distance_unit text NOT NULL DEFAULT 'in' CHECK (shippo_parcel_distance_unit = ANY (ARRAY['in','cm'])),
|
|
98
|
+
shippo_parcel_mass_unit text NOT NULL DEFAULT 'lb' CHECK (shippo_parcel_mass_unit = ANY (ARRAY['lb','oz','g','kg'])),
|
|
99
|
+
shippo_label_file_type text NOT NULL DEFAULT 'PDF_4x6' CHECK (shippo_label_file_type = ANY (ARRAY['PNG','PNG_2.3x7.5','PDF','PDF_2.3x7.5','PDF_4x6','PDF_4x8','PDF_A4','PDF_A5','PDF_A6','ZPLII'])),
|
|
100
|
+
active_theme_name text DEFAULT 'default',
|
|
101
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
102
|
+
show_ticker boolean NOT NULL DEFAULT true,
|
|
103
|
+
section_styles jsonb NOT NULL DEFAULT '{"homeHero":"video"}',
|
|
104
|
+
CONSTRAINT order_policies_pkey PRIMARY KEY (id)
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
CREATE TABLE IF NOT EXISTS public.coupons (
|
|
108
|
+
code text NOT NULL,
|
|
109
|
+
label text NOT NULL,
|
|
110
|
+
coupon_type text NOT NULL CHECK (coupon_type = ANY (ARRAY['universal','one_time'])),
|
|
111
|
+
discount_type text NOT NULL CHECK (discount_type = ANY (ARRAY['percent','amount'])),
|
|
112
|
+
discount_value numeric NOT NULL CHECK (discount_value >= 0),
|
|
113
|
+
active boolean NOT NULL DEFAULT true,
|
|
114
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
115
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
116
|
+
CONSTRAINT coupons_pkey PRIMARY KEY (code)
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
CREATE TABLE IF NOT EXISTS public.orders (
|
|
120
|
+
id text NOT NULL,
|
|
121
|
+
user_id uuid,
|
|
122
|
+
user_email text NOT NULL,
|
|
123
|
+
status text NOT NULL DEFAULT 'placed' CHECK (status = ANY (ARRAY['placed','packed','in_transit','delivered'])),
|
|
124
|
+
payment_method text NOT NULL CHECK (payment_method = ANY (ARRAY['cod','razorpay'])),
|
|
125
|
+
payment_verified boolean NOT NULL DEFAULT false,
|
|
126
|
+
razorpay_payment_id text,
|
|
127
|
+
coupon_code text,
|
|
128
|
+
shipping_address jsonb NOT NULL,
|
|
129
|
+
totals jsonb NOT NULL,
|
|
130
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
131
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
132
|
+
CONSTRAINT orders_pkey PRIMARY KEY (id),
|
|
133
|
+
CONSTRAINT orders_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.app_users(id),
|
|
134
|
+
CONSTRAINT orders_coupon_code_fkey FOREIGN KEY (coupon_code) REFERENCES public.coupons(code)
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
CREATE TABLE IF NOT EXISTS public.order_items (
|
|
138
|
+
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
139
|
+
order_id text NOT NULL,
|
|
140
|
+
product_id text,
|
|
141
|
+
product_name text NOT NULL,
|
|
142
|
+
product_image text,
|
|
143
|
+
size text NOT NULL,
|
|
144
|
+
quantity integer NOT NULL CHECK (quantity > 0),
|
|
145
|
+
unit_price numeric NOT NULL CHECK (unit_price >= 0),
|
|
146
|
+
line_total numeric NOT NULL CHECK (line_total >= 0),
|
|
147
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
148
|
+
CONSTRAINT order_items_pkey PRIMARY KEY (id),
|
|
149
|
+
CONSTRAINT order_items_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(id),
|
|
150
|
+
CONSTRAINT order_items_product_id_fkey FOREIGN KEY (product_id) REFERENCES public.products(id)
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
CREATE TABLE IF NOT EXISTS public.themes (
|
|
154
|
+
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
155
|
+
name text NOT NULL UNIQUE,
|
|
156
|
+
label text NOT NULL,
|
|
157
|
+
colors jsonb NOT NULL,
|
|
158
|
+
is_active boolean NOT NULL DEFAULT true,
|
|
159
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
160
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
161
|
+
CONSTRAINT themes_pkey PRIMARY KEY (id)
|
|
162
|
+
);
|
|
13
163
|
`;
|
|
164
|
+
|
|
165
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
166
|
+
// SEED — full mock data from uploaded SQL files
|
|
167
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
14
168
|
export const SEED_SQL = `
|
|
15
|
-
|
|
16
|
-
INSERT INTO public.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
169
|
+
-- Themes
|
|
170
|
+
INSERT INTO public.themes (id, name, label, colors, is_active) VALUES
|
|
171
|
+
('36c8ea36-0aa5-4100-b0c4-d61c6b9bccd3','crimson','Crimson Elegance','{"muted":"oklch(0.96 0.02 20)","accent":"oklch(0.6 0.15 30)","border":"oklch(0.9 0.05 20)","primary":"oklch(0.5 0.18 25)","secondary":"oklch(0.95 0.03 20)","background":"oklch(0.98 0.01 20)","foreground":"oklch(0.2 0.05 20)","primary_foreground":"oklch(0.98 0.01 20)"}',true),
|
|
172
|
+
('48234595-c916-4e52-a1d7-5dc489fec14d','golden','Light Golden','{"muted":"oklch(0.88 0.08 70)","accent":"oklch(0.75 0.18 62)","border":"oklch(0.90 0.10 70)","primary":"oklch(0.70 0.18 62)","secondary":"oklch(0.85 0.12 70)","background":"oklch(0.98 0.01 70)","foreground":"oklch(0.25 0.08 50)","primary_foreground":"oklch(0.96 0.12 75)"}',true),
|
|
173
|
+
('7f33e885-6629-4f6b-8dde-382efa03698c','default','Minimalist White','{"muted":"oklch(0.97 0 0)","accent":"oklch(0.97 0 0)","border":"oklch(0.922 0 0)","primary":"oklch(0.205 0 0)","secondary":"oklch(0.97 0 0)","background":"oklch(0.985 0 0)","foreground":"oklch(0.145 0 0)","primary_foreground":"oklch(0.985 0 0)"}',true),
|
|
174
|
+
('bcf786ec-a658-4e9f-9115-0067fc313a34','peachy','Peachy Delight','{"muted":"oklch(0.901 0.012 162.023)","accent":"oklch(0.888 0.061 5.752)","border":"oklch(0.85 0.02 0)","primary":"oklch(0.816 0.086 9.042)","secondary":"oklch(0.888 0.061 5.752)","background":"oklch(0.95 0.01 162)","foreground":"oklch(0.35 0.03 0)","primary_foreground":"oklch(0.98 0.01 0)"}',true),
|
|
175
|
+
('c3f4dba4-17db-47d6-9e84-315425d78e91','midnight','Midnight Dark','{"muted":"oklch(0.2 0.03 240)","accent":"oklch(0.7 0.1 200)","border":"oklch(0.3 0.05 240)","primary":"oklch(0.6 0.15 250)","secondary":"oklch(0.25 0.05 240)","background":"oklch(0.15 0.02 240)","foreground":"oklch(0.98 0.01 240)","primary_foreground":"oklch(0.98 0.01 240)"}',true),
|
|
176
|
+
('d132bca7-872e-475c-adfc-1f8b2e9e271c','ocean','Ocean Deep','{"muted":"oklch(0.9 0.03 220)","accent":"oklch(0.6 0.12 200)","border":"oklch(0.8 0.06 220)","primary":"oklch(0.5 0.15 220)","secondary":"oklch(0.85 0.05 220)","background":"oklch(0.95 0.02 220)","foreground":"oklch(0.2 0.08 220)","primary_foreground":"oklch(0.98 0.01 220)"}',true),
|
|
177
|
+
('dab26fe5-31e9-4748-b82a-15d3b91c5af1','forest','Forest Nature','{"muted":"oklch(0.92 0.03 140)","accent":"oklch(0.5 0.12 150)","border":"oklch(0.85 0.05 140)","primary":"oklch(0.4 0.1 140)","secondary":"oklch(0.9 0.05 140)","background":"oklch(0.97 0.01 140)","foreground":"oklch(0.2 0.05 140)","primary_foreground":"oklch(0.98 0.01 140)"}',true),
|
|
178
|
+
('fd49d578-a0a1-44a9-9d83-93dcec092dc8','lavender','Lavender Mist','{"muted":"oklch(0.94 0.03 290)","accent":"oklch(0.75 0.15 300)","border":"oklch(0.88 0.06 290)","primary":"oklch(0.7 0.12 290)","secondary":"oklch(0.92 0.05 290)","background":"oklch(0.96 0.02 290)","foreground":"oklch(0.3 0.08 290)","primary_foreground":"oklch(0.98 0.01 290)"}',true)
|
|
179
|
+
ON CONFLICT (name) DO UPDATE SET label=EXCLUDED.label, colors=EXCLUDED.colors, updated_at=now();
|
|
180
|
+
|
|
181
|
+
-- Collections
|
|
182
|
+
INSERT INTO public.collections (id, name, description, image_url, item_count_label, sort_order) VALUES
|
|
183
|
+
('contemporary','Contemporary Collection','Modern cuts and innovative styling for the forward-thinking gentleman.','/thudarum-sky-blue-blazer.jpg','10 items',30),
|
|
184
|
+
('evening','Evening Collection','Luxurious velvet and satin pieces designed to make a statement at formal occasions.','/thudarum-navy-velvet-blazer.jpg','6 items',40),
|
|
185
|
+
('executive','Executive Collection','Bold, sophisticated pieces for the modern power dresser. Featuring rich textures and commanding colors.','/thudarum-burgundy-evening-suit.jpg','12 items',10),
|
|
186
|
+
('heritage','Heritage Collection','Classic tailoring with timeless appeal. Traditional patterns reimagined for contemporary elegance.','/thudarum-green-check-blazer.jpg','8 items',20)
|
|
187
|
+
ON CONFLICT (id) DO NOTHING;
|
|
188
|
+
|
|
189
|
+
-- Products
|
|
190
|
+
INSERT INTO public.products (id,name,price,main_image_url,gallery_image_urls,category_label,description,details,sizes,section,collection_ids,sort_order,is_active) VALUES
|
|
191
|
+
('burgundy-blazer-cream-trousers','Burgundy Blazer with Cream Trousers',985.00,'/thudarum-burgundy-blazer-combo.jpg',ARRAY['/thudarum-burgundy-combo-detail.jpg','/thudarum-burgundy-combo-side.jpg'],'Separates','A striking burgundy blazer combination with cream trousers for a polished statement look.',ARRAY['Premium wool construction','Double-breasted design','Gold-tone buttons','Tailored separates'],ARRAY['38','40','42','44','46','48'],'best_sellers',ARRAY['executive','evening'],10,true),
|
|
192
|
+
('camel-trench-coat','Camel Trench Coat',645.00,'/camel-trench-coat-elegant.jpg',ARRAY['/minimalist-fashion-studio-elegant-clothing.jpg'],'Outerwear','A refined camel trench coat with a versatile silhouette for smart everyday dressing.',ARRAY['Water-resistant cotton blend','Belted waist','Classic storm flap'],ARRAY['S','M','L','XL'],'general',ARRAY['heritage','contemporary'],20,true),
|
|
193
|
+
('classic-taupe-double-breasted-suit','Classic Taupe Double-Breasted Suit',1289.00,'/thudarum-taupe-suit-hero.jpg',ARRAY['/thudarum-taupe-suit-detail.jpg','/thudarum-taupe-suit-side.jpg'],'Suits','An impeccably tailored double-breasted suit in refined taupe wool with peak lapels and matching trousers.',ARRAY['100% Italian wool','Double-breasted closure','Peak lapels','Complete with trousers'],ARRAY['38','40','42','44','46','48'],'new_arrivals',ARRAY['executive','contemporary'],10,true),
|
|
194
|
+
('elegant-black-wool-trousers','Elegant Black Wool Trousers',325.00,'/elegant-black-wool-trousers.jpg',ARRAY[],'Trousers','A foundational black wool trouser with a sharp line and versatile formal appeal.',ARRAY['Wool blend','Pressed front crease','Tailored waistband'],ARRAY['30','32','34','36','38'],'general',ARRAY[],30,true),
|
|
195
|
+
('heritage-green-check-blazer','Heritage Green Check Blazer',895.00,'/thudarum-green-check-blazer.jpg',ARRAY['/thudarum-green-check-detail.jpg','/thudarum-green-check-side.jpg'],'Blazers','A distinguished houndstooth blazer with classic green patterning, refined tailoring, and gold-tone buttons.',ARRAY['Premium wool houndstooth','Double-breasted design','Gold-tone buttons','Made in England'],ARRAY['38','40','42','44','46','48'],'new_arrivals',ARRAY['heritage'],20,true),
|
|
196
|
+
('luxe-burgundy-evening-suit','Luxe Burgundy Evening Suit',1545.00,'/thudarum-burgundy-evening-suit.jpg',ARRAY['/thudarum-burgundy-suit-detail.jpg','/thudarum-burgundy-suit-side.jpg'],'Suits','A sophisticated burgundy suit designed for elevated evening occasions with a timeless tailored silhouette.',ARRAY['Fine Italian wool','Double-breasted style','Tone-on-tone buttons','Full suit ensemble'],ARRAY['38','40','42','44','46','48'],'new_arrivals',ARRAY['executive','evening'],30,true),
|
|
197
|
+
('minimalist-white-linen-shirt','Minimalist White Linen Shirt',245.00,'/minimalist-white-linen-shirt-fashion.jpg',ARRAY['/ivory-silk-blouse-minimal.jpg'],'Shirts','A clean white linen shirt designed for effortless layering and warm-weather polish.',ARRAY['Premium linen','Relaxed tailored fit','Breathable finish'],ARRAY['S','M','L','XL'],'general',ARRAY['contemporary'],10,true),
|
|
198
|
+
('modern-slate-blazer-set','Modern Slate Blazer Set',1095.00,'/thudarum-slate-blazer-set.jpg',ARRAY['/thudarum-slate-blazer-detail.jpg','/thudarum-slate-blazer-side.jpg'],'Separates','A sophisticated slate blazer set that blends timeless tailoring with modern styling.',ARRAY['Premium wool blend','Double-breasted design','Classic buttons','Made in England'],ARRAY['38','40','42','44','46','48'],'best_sellers',ARRAY['contemporary'],40,true),
|
|
199
|
+
('navy-velvet-double-breasted-jacket','Navy Velvet Double-Breasted Jacket',1195.00,'/thudarum-navy-velvet-blazer.jpg',ARRAY['/thudarum-navy-velvet-detail.jpg','/thudarum-navy-velvet-side.jpg'],'Blazers','A luxurious navy velvet jacket with elevated texture and refined eveningwear presence.',ARRAY['Italian cotton velvet','Double-breasted style','Silver-tone buttons','Evening-ready finish'],ARRAY['38','40','42','44','46','48'],'best_sellers',ARRAY['evening'],20,true),
|
|
200
|
+
('refined-gray-double-breasted-suit','Refined Gray Double-Breasted Suit',1345.00,'/thudarum-gray-suit-refined.jpg',ARRAY['/thudarum-gray-suit-detail.jpg','/thudarum-gray-suit-side.jpg'],'Suits','A modern gray double-breasted suit with a clean profile and professional finish.',ARRAY['Super 120s wool','Double-breasted closure','Peak lapels','Complete suit'],ARRAY['38','40','42','44','46','48'],'best_sellers',ARRAY['executive','contemporary'],30,true),
|
|
201
|
+
('sky-blue-textured-blazer','Sky Blue Textured Blazer',795.00,'/thudarum-sky-blue-blazer.jpg',ARRAY['/thudarum-sky-blue-detail.jpg','/thudarum-sky-blue-side.jpg'],'Blazers','A contemporary sky blue blazer with subtle texture, sharp tailoring, and distinctive button details.',ARRAY['Textured wool blend','Double-breasted cut','Gold button details','Made in Italy'],ARRAY['38','40','42','44','46','48'],'new_arrivals',ARRAY['contemporary'],40,true)
|
|
202
|
+
ON CONFLICT (id) DO NOTHING;
|
|
203
|
+
|
|
204
|
+
-- Coupons
|
|
205
|
+
INSERT INTO public.coupons (code, label, coupon_type, discount_type, discount_value, active) VALUES
|
|
206
|
+
('WELCOME10','Welcome 10%','universal','percent',10.00,true)
|
|
207
|
+
ON CONFLICT (code) DO NOTHING;
|
|
208
|
+
|
|
209
|
+
-- Order policies
|
|
210
|
+
INSERT INTO public.order_policies (id, shipping_amount, free_shipping_threshold, tax_rate, automatic_shipping_enabled, shippo_from_name, shippo_from_company, shippo_from_street1, shippo_from_street2, shippo_from_city, shippo_from_state, shippo_from_zip, shippo_from_country, shippo_from_phone, shippo_from_email, shippo_from_is_residential, shippo_parcel_length, shippo_parcel_width, shippo_parcel_height, shippo_parcel_weight, shippo_parcel_distance_unit, shippo_parcel_mass_unit, shippo_label_file_type, active_theme_name, show_ticker, section_styles) VALUES
|
|
211
|
+
(true, 15.00, 200.00, 8.00, true, 'FABRICA', 'SPARROW AI SOLUTIONS', '20 W 34th St', 'New York', 'New York', 'NY', '10001', 'US', '8852099490', 'sparrowaisolutions@gmail.com', false, 10.00, 10.00, 4.00, 1.00, 'in', 'lb', 'PDF_4x6', 'default', true, '{"homeHero":"video"}')
|
|
212
|
+
ON CONFLICT (id) DO NOTHING;
|
|
213
|
+
|
|
214
|
+
-- Site content
|
|
215
|
+
INSERT INTO public.site_content (id, content) VALUES
|
|
216
|
+
('site', '{"brandName":"FABRICA","tickerMessages":["FREE SHIPPING ON ORDERS OVER \u20b9200","30-DAY RETURNS","HOST YOUR OWN STORE IN MINUTES"]}'),
|
|
217
|
+
('home', '{"heroTitle":"Launch Your Store","heroSubtitle":"Discover powerful tools crafted for the modern merchant","heroVideoUrl":"https://www.youtube.com/embed/u9FEg5qur14?autoplay=1&mute=1&loop=1&playlist=u9FEg5qur14&controls=0&showinfo=0&rel=0&modestbranding=1&playsinline=1","footerTagline":"Contemporary commerce for the independent entrepreneur.","bestSellersTitle":"Best Sellers","collectionsTitle":"Collections","newArrivalsTitle":"New Arrivals"}'),
|
|
218
|
+
('about', '{"values":[{"title":"Craftsmanship","description":"Every feature is constructed with meticulous attention to detail by skilled engineers who take pride in their work."},{"title":"Quality","description":"We source only the finest open-source tools and frameworks, ensuring reliability and speed in every build."},{"title":"Timelessness","description":"Our designs transcend fleeting trends, offering interfaces that remain elegant and relevant season after season."}],"ctaTitle":"Experience Fabrica","heroTitle":"About Fabrica","storyTitle":"Our Story","valuesTitle":"Our Values","heroImageUrl":"/thudarum-burgundy-evening-suit.jpg","heroSubtitle":"Building seamless commerce tools for the modern entrepreneur","storyImageUrl":"/thudarum-taupe-suit-detail.jpg","ctaDescription":"Discover our latest platform of meticulously crafted storefronts and dashboards designed for the modern merchant.","storyParagraphs":["Founded with a vision to redefine modern e-commerce, Fabrica represents the perfect marriage of powerful infrastructure and intuitive design.","Every feature in our platform is meticulously crafted using proven technology and assembled by engineers who have honed their expertise over decades.","Our storefronts and admin tools are designed for the discerning merchant who appreciates quality, understands branding, and values a platform that will remain powerful and relevant for years to come."]}'),
|
|
219
|
+
('collections', '{"title":"Collections","description":"Explore our curated collections, each telling a unique story of style, craftsmanship, and modern elegance.","featuredTitle":"Crafted for Excellence","featuredDescription":"Each collection is carefully curated to offer distinctive pieces that complement your personal style."}'),
|
|
220
|
+
('shop', '{"title":"All Products"}'),
|
|
221
|
+
('page:contact', '{"body":["Reach out to our support team for help."],"title":"Contact","contact":{"email":"support@fabrica.com","phone":"+91 00000 00000","facebook":"https://facebook.com/fabricahq","whatsapp":"https://wa.me/910000000000","instagram":"https://instagram.com/fabricahq"},"description":"Contact us"}'),
|
|
222
|
+
('page:privacy', '{"body":["We respect your privacy and protect your data."],"title":"Privacy Policy","description":"Privacy information"}'),
|
|
223
|
+
('page:returns', '{"body":["30-day return policy for unworn items."],"title":"Returns","description":"Returns information"}'),
|
|
224
|
+
('page:shipping', '{"body":["Complimentary shipping rules are controlled from Admin Policies."],"title":"Shipping","description":"Shipping information"}'),
|
|
225
|
+
('page:terms', '{"body":["By using this site, you agree to our terms."],"title":"Terms of Service","description":"Terms information"}')
|
|
226
|
+
ON CONFLICT (id) DO UPDATE SET content=EXCLUDED.content, updated_at=now();
|
|
21
227
|
`;
|
|
228
|
+
|
|
229
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
230
|
+
// STORAGE POLICIES
|
|
231
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
232
|
export const STORAGE_POLICIES_SQL = `
|
|
23
233
|
INSERT INTO storage.buckets (id, name, public) VALUES ('pic', 'pic', true) ON CONFLICT (id) DO NOTHING;
|
|
24
234
|
DO $$ BEGIN CREATE POLICY "pic_select_all" ON storage.objects FOR SELECT USING (bucket_id = 'pic'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
@@ -26,4 +236,5 @@ DO $$ BEGIN CREATE POLICY "pic_insert_all" ON storage.objects FOR INSERT WITH CH
|
|
|
26
236
|
DO $$ BEGIN CREATE POLICY "pic_update_all" ON storage.objects FOR UPDATE USING (bucket_id = 'pic'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
27
237
|
DO $$ BEGIN CREATE POLICY "pic_delete_all" ON storage.objects FOR DELETE USING (bucket_id = 'pic'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
28
238
|
`;
|
|
239
|
+
|
|
29
240
|
export const FULL_SQL = `${SCHEMA_SQL}\n${SEED_SQL}\n${STORAGE_POLICIES_SQL}`;
|