mirror of
https://git.meshkee.com/Meshkee/backend.git
synced 2026-08-11 22:30:59 +04:30
Initial commit: Meshkee CMS API
NestJS backend with Prisma, Docker Compose for Postgres/Redis, and deploy docs for the production VM.
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
-- Meshkee CMS — shopping cart and orders
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- enums
|
||||
-- ---------------------------------------------------------------------------
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE order_status AS ENUM (
|
||||
'pending',
|
||||
'confirmed',
|
||||
'processing',
|
||||
'shipped',
|
||||
'delivered',
|
||||
'cancelled'
|
||||
);
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE order_source AS ENUM ('website', 'admin');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- carts (one per customer per business)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE carts (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
business_id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT carts_business_id_fkey
|
||||
FOREIGN KEY (business_id) REFERENCES businesses (id) ON DELETE CASCADE,
|
||||
CONSTRAINT carts_user_id_fkey
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
|
||||
CONSTRAINT carts_business_user_unique UNIQUE (business_id, user_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_carts_business_id ON carts (business_id);
|
||||
CREATE INDEX idx_carts_user_id ON carts (user_id);
|
||||
|
||||
CREATE TRIGGER carts_set_updated_at
|
||||
BEFORE UPDATE ON carts
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- cart items (product variants in cart)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE cart_items (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
cart_id BIGINT NOT NULL,
|
||||
variant_id BIGINT NOT NULL,
|
||||
quantity INT NOT NULL DEFAULT 1,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT cart_items_cart_id_fkey
|
||||
FOREIGN KEY (cart_id) REFERENCES carts (id) ON DELETE CASCADE,
|
||||
CONSTRAINT cart_items_variant_id_fkey
|
||||
FOREIGN KEY (variant_id) REFERENCES product_variants (id) ON DELETE CASCADE,
|
||||
CONSTRAINT cart_items_cart_variant_unique UNIQUE (cart_id, variant_id),
|
||||
CONSTRAINT cart_items_quantity_positive CHECK (quantity > 0)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_cart_items_cart_id ON cart_items (cart_id);
|
||||
CREATE INDEX idx_cart_items_variant_id ON cart_items (variant_id);
|
||||
|
||||
CREATE TRIGGER cart_items_set_updated_at
|
||||
BEFORE UPDATE ON cart_items
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- orders
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE orders (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
business_id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
order_number VARCHAR(30) NOT NULL,
|
||||
status order_status NOT NULL DEFAULT 'pending',
|
||||
source order_source NOT NULL DEFAULT 'website',
|
||||
subtotal NUMERIC(12, 2) NOT NULL DEFAULT 0,
|
||||
shipping_total NUMERIC(12, 2) NOT NULL DEFAULT 0,
|
||||
discount_total NUMERIC(12, 2) NOT NULL DEFAULT 0,
|
||||
total NUMERIC(12, 2) NOT NULL DEFAULT 0,
|
||||
shipping_address JSONB NOT NULL DEFAULT '{}',
|
||||
address_id BIGINT,
|
||||
customer_notes TEXT,
|
||||
admin_notes TEXT,
|
||||
created_by BIGINT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT orders_business_id_fkey
|
||||
FOREIGN KEY (business_id) REFERENCES businesses (id) ON DELETE CASCADE,
|
||||
CONSTRAINT orders_user_id_fkey
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT orders_address_id_fkey
|
||||
FOREIGN KEY (address_id) REFERENCES addresses (id) ON DELETE SET NULL,
|
||||
CONSTRAINT orders_created_by_fkey
|
||||
FOREIGN KEY (created_by) REFERENCES users (id) ON DELETE SET NULL,
|
||||
CONSTRAINT orders_business_order_number_unique UNIQUE (business_id, order_number),
|
||||
CONSTRAINT orders_subtotal_non_negative CHECK (subtotal >= 0),
|
||||
CONSTRAINT orders_shipping_total_non_negative CHECK (shipping_total >= 0),
|
||||
CONSTRAINT orders_discount_total_non_negative CHECK (discount_total >= 0),
|
||||
CONSTRAINT orders_total_non_negative CHECK (total >= 0)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_orders_business_created
|
||||
ON orders (business_id, created_at DESC);
|
||||
|
||||
CREATE INDEX idx_orders_business_user
|
||||
ON orders (business_id, user_id, created_at DESC);
|
||||
|
||||
CREATE INDEX idx_orders_business_status
|
||||
ON orders (business_id, status, created_at DESC);
|
||||
|
||||
CREATE TRIGGER orders_set_updated_at
|
||||
BEFORE UPDATE ON orders
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- order items (line items with price snapshots)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE order_items (
|
||||
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
order_id BIGINT NOT NULL,
|
||||
variant_id BIGINT,
|
||||
product_id BIGINT NOT NULL,
|
||||
product_title VARCHAR(255) NOT NULL,
|
||||
variant_sku VARCHAR(100),
|
||||
unit_price NUMERIC(12, 2) NOT NULL,
|
||||
compare_at_price NUMERIC(12, 2),
|
||||
quantity INT NOT NULL,
|
||||
line_total NUMERIC(12, 2) NOT NULL,
|
||||
selections_snapshot JSONB NOT NULL DEFAULT '[]',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT order_items_order_id_fkey
|
||||
FOREIGN KEY (order_id) REFERENCES orders (id) ON DELETE CASCADE,
|
||||
CONSTRAINT order_items_variant_id_fkey
|
||||
FOREIGN KEY (variant_id) REFERENCES product_variants (id) ON DELETE SET NULL,
|
||||
CONSTRAINT order_items_product_id_fkey
|
||||
FOREIGN KEY (product_id) REFERENCES products (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT order_items_quantity_positive CHECK (quantity > 0),
|
||||
CONSTRAINT order_items_unit_price_non_negative CHECK (unit_price >= 0),
|
||||
CONSTRAINT order_items_line_total_non_negative CHECK (line_total >= 0)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_order_items_order_id ON order_items (order_id);
|
||||
CREATE INDEX idx_order_items_variant_id ON order_items (variant_id);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- permissions
|
||||
-- ---------------------------------------------------------------------------
|
||||
INSERT INTO permissions (name, slug, group_name, description) VALUES
|
||||
('Create orders', 'orders.create', 'orders', 'Create orders on behalf of customers'),
|
||||
('Update orders', 'orders.update', 'orders', 'Update order status and admin notes')
|
||||
ON CONFLICT (slug) DO NOTHING;
|
||||
|
||||
-- business_owner
|
||||
INSERT INTO role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM roles r
|
||||
JOIN permissions p ON p.slug IN ('orders.read', 'orders.create', 'orders.update')
|
||||
WHERE r.slug = 'business_owner'
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- owner (legacy global role)
|
||||
INSERT INTO role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM roles r
|
||||
JOIN permissions p ON p.slug IN ('orders.read', 'orders.create', 'orders.update')
|
||||
WHERE r.slug = 'owner'
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- admin
|
||||
INSERT INTO role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM roles r
|
||||
JOIN permissions p ON p.slug IN ('orders.read', 'orders.create', 'orders.update')
|
||||
WHERE r.slug = 'admin'
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- editor: read + update status
|
||||
INSERT INTO role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM roles r
|
||||
JOIN permissions p ON p.slug IN ('orders.read', 'orders.update')
|
||||
WHERE r.slug = 'editor'
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- viewer: read only
|
||||
INSERT INTO role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM roles r
|
||||
JOIN permissions p ON p.slug = 'orders.read'
|
||||
WHERE r.slug = 'viewer'
|
||||
ON CONFLICT DO NOTHING;
|
||||
Reference in New Issue
Block a user