Skip to content

Commit 37599a3

Browse files
authored
Merge pull request #51 from yalla-coop/staging
265 - Restrict access to orders to only the order creator
2 parents 2d478ef + cd6e49c commit 37599a3

16 files changed

+191
-32
lines changed

DATABASE_OVERVIEW.md

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Overview of various Database tables and their purpose:
2+
3+
**1. Users**
4+
This table manages authenticated user sessions that have access to the producer's system (users need to be authenticated via OIDC as a first step to access the producer app)
5+
6+
**2. Sales Sessions**
7+
This table is used to store orders and their associated reservation dates (used to reserve draft order stock on Shopify). The reservation date equals the end dates of a sales session.
8+
9+
**3. Orders**
10+
This table maps draft order IDs to completed order IDs, linking orders through different stages of processing.
11+
12+
**4. Line Items**
13+
This table maintains a stable ID for line items using an external ID provided via the app's API. This helps track and manage line items even if Shopify updates its internal IDs which is the default behaviour when updating ongoing orders.
14+
15+
**5. FDC Variants**
16+
This table tracks products tagged as FDC, making them accessible to Hubs.
17+
18+
**6. Webhooks:**
19+
Every time Shopify triggers a webhook, a record is created in this table. It logs the webhook event and its associated details.
20+
21+
**8. Shopify Sessions:**
22+
This table manages user sessions created and handled by Shopify, including session tokens and expiration details.
23+
24+
25+

DEPLOYMENT_STRATEGY.md

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Deployment Strategy for Jelastic Environments
2+
3+
The application is stored on https://app.jpe.infomaniak.com/ and consists of various parts as described below:
4+
5+
## 1. PRODUCTION Environment Setup
6+
7+
### Application Server (Docker container)
8+
Runs the application and is connected to Shopify via the Application Dashboard (API Configuration Section)
9+
10+
**Automated Deployments:**
11+
- Image Building: Docker images are built automatically via GitHub Actions. The build process triggers on code changes and commits.
12+
- Deployment Notification: GitHub Actions will ping Jelastic via a REST API call with deployment details once the build is complete.
13+
- Tagging: Use branch-sha_commit tags (e.g., main-2d478ef) to keep track of versions.
14+
15+
**Logs:**
16+
- Access logs in the run.log file within the Docker container for troubleshooting.
17+
18+
**Environment Variables:**
19+
- Configure necessary environment variables using the Variables popup in Jelastic.
20+
21+
### Nginx Load Balancer:
22+
23+
- Port Configuration: Configure Nginx to listen on relevant application port (HTTP) and proxy traffic to the appropriate internal nodes based on IP addresses via HTTPS.
24+
- SSL Configuration:
25+
- Ensure Nginx is set up to handle SSL communication internally.
26+
- Use the ssl.conf configuration file to set up SSL keys for secure communication between components.
27+
28+
### Postgres Container:
29+
Stores the internal data store
30+
- Links: Ensure proper linking between the Postgres container and other components.
31+
- Certificates: Use certificates to secure communication between the Postgres container and other services.
32+
33+
### Creating new Environments:
34+
- Simplest way is to clone Environment by using the Jelastic environment cloning feature to duplicate the current setup.
35+
- This will create an exact replica of your existing environment.
36+
- Post-clone, adjust the following settings:
37+
- Update environment-specific settings, such as IP addresses, secrets, and environment variables.
38+
- Ensure any environment-specific configurations (e.g., database URLs, API keys) are updated.
39+
40+
![Bildschirmfoto 2024-09-05 um 13 27 29](https://github.com/user-attachments/assets/eadf8d6a-44e8-45f6-96f5-60ae5aeb2cf9)

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,8 @@ yarn dev --reset
5252
```bash
5353
yarn dev
5454
```
55+
56+
## Useful Resources
57+
- [Main Product Readme](https://github.com/yalla-coop/food-data-collaboration-producer/blob/staging/README_MAIN_PRODUCT.md)
58+
- [Database Overview](https://github.com/yalla-coop/food-data-collaboration-producer/blob/6af3fb3eeb328da489b2923fb2c0bc49cf704073/DATABASE_OVERVIEW.md)
59+
- [Deployment Strategy](https://github.com/yalla-coop/food-data-collaboration-producer/blob/6af3fb3eeb328da489b2923fb2c0bc49cf704073/DEPLOYMENT_STRATEGY.md)
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
ALTER TABLE orders ADD "owner_id" integer NOT NULL;
2+
3+
ALTER TABLE orders
4+
ADD CONSTRAINT fk_user
5+
FOREIGN KEY (owner_id)
6+
REFERENCES users (id);

web/database/orders/orders.js

+23-14
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,33 @@
1-
import { pool } from '../connect.js'
1+
import { pool } from '../connect.js';
22

3-
export const createDraftOrder = async (draftOrderId) => {
4-
const result = await pool.query(`
5-
INSERT INTO orders (draft_order_id)
6-
VALUES ($1)
3+
export const createDraftOrder = async (draftOrderId, openIdUserId) => {
4+
const result = await pool.query(
5+
`
6+
INSERT INTO orders (draft_order_id, owner_id)
7+
VALUES ($1, (SELECT "id" from users where user_id = $2))
78
RETURNING *;
8-
`, [draftOrderId]);
9-
return result.rows;
9+
`,
10+
[draftOrderId, openIdUserId]
11+
);
12+
return result.rows;
1013
};
1114

1215
export const completeDraftOrder = async (draftOrderId, completedOrderId) => {
13-
const result = await pool.query(`
16+
const result = await pool.query(
17+
`
1418
UPDATE orders set completed_order_id = $2
1519
WHERE draft_order_id = $1
1620
RETURNING *;
17-
`, [draftOrderId, completedOrderId]);
18-
return result.rows;
21+
`,
22+
[draftOrderId, completedOrderId]
23+
);
24+
return result.rows;
1925
};
2026

21-
export const getOrders = async () => {
22-
const result = await pool.query(`SELECT draft_order_id as "draftOrderId", completed_order_id as "completedOrderId" from orders order by draft_order_id`, []);
23-
return result.rows;
24-
}
27+
export const getOrder = async (draftOrderId, openIdUserId) => {
28+
const result = await pool.query(
29+
`SELECT draft_order_id as "draftOrderId", completed_order_id as "completedOrderId", owner_id as "ownerId" from orders where draft_order_id = $1 AND owner_id = (select id from users where user_id = $2)`,
30+
[draftOrderId, openIdUserId]
31+
);
32+
return result.rows && result.rows.length === 1 ? result.rows[0] : null;
33+
};

web/database/orders/orders.spec.js

+21-6
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,36 @@
11
import { pool } from '../connect';
2-
import {createDraftOrder, completeDraftOrder, getOrders} from './orders'
2+
import {createDraftOrder, completeDraftOrder, getOrder} from './orders'
33

44
describe('orders', () => {
5+
6+
const openIdUserId = '08fcc438-1eb5-4c63-a8f4-894a3960351d';
7+
8+
const getOrders = async () => {
9+
const result = await pool.query(`SELECT draft_order_id as "draftOrderId", completed_order_id as "completedOrderId", owner_id as "ownerId" from orders order by draft_order_id`, []);
10+
return result.rows;
11+
}
12+
513
beforeAll(async () => {
614
await pool.query(`truncate table orders restart identity`);
715
});
816

917
it('Order can be created', async () => {
10-
await createDraftOrder(55);
11-
await createDraftOrder(56);
18+
await createDraftOrder(55, openIdUserId);
19+
await createDraftOrder(56, openIdUserId);
1220
const result = await getOrders();
13-
expect(result).toStrictEqual([{draftOrderId: "55", completedOrderId: null}, {draftOrderId: "56", completedOrderId: null}])
21+
expect(result).toStrictEqual([{draftOrderId: "55", completedOrderId: null, ownerId: 10}, {draftOrderId: "56", completedOrderId: null, ownerId: 10}])
22+
});
23+
24+
it('Order can be retrieved', async () => {
25+
expect(await getOrder(55, openIdUserId)).toStrictEqual({draftOrderId: "55", completedOrderId: null, ownerId: 10})
26+
expect(await getOrder(55, 12345)).toStrictEqual(null)
27+
expect(await getOrder(99, openIdUserId)).toStrictEqual(null)
1428
});
1529

1630
it('Order can be completed', async () => {
1731
await completeDraftOrder(55, 654);
1832
const result = await getOrders();
19-
expect(result).toStrictEqual([{draftOrderId: "55", completedOrderId: "654"}, {draftOrderId: "56", completedOrderId: null}])
33+
expect(result).toStrictEqual([{draftOrderId: "55", completedOrderId: "654", ownerId: 10}, {draftOrderId: "56", completedOrderId: null, ownerId: 10}])
2034
})
21-
});
35+
});
36+

web/database/orders/schema.sql

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ DROP TABLE IF EXISTS "orders" CASCADE;
33
CREATE TABLE IF NOT EXISTS "orders" (
44
"draft_order_id" bigint PRIMARY KEY,
55
"completed_order_id" bigint NULL,
6+
"owner_id" integer NOT NULL,
67
"created_at" TIMESTAMP NOT NULL DEFAULT NOW(),
7-
"updated_at" TIMESTAMP NOT NULL DEFAULT NOW()
8+
"updated_at" TIMESTAMP NOT NULL DEFAULT NOW(),
9+
CONSTRAINT fk_user
10+
FOREIGN KEY(owner_id)
11+
REFERENCES users(id)
812
);
913
CREATE TRIGGER set_timestamp BEFORE
1014
UPDATE ON "orders" FOR EACH ROW EXECUTE PROCEDURE trigger_set_timestamp();

web/fdc-modules/orders/controllers/create-or-update-order-line.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,19 @@ import { extractOrderLine, createDfcOrderLineFromShopify } from '../dfc/dfc-orde
44
import { persistLineIdMappings } from './lineItemMappings.js'
55
import * as orders from './shopify/orders.js';
66
import * as ids from './shopify/ids.js'
7-
// transaction
7+
import { getOrder } from '../../../database/orders/orders.js';
8+
89
const createOrUpdateOrderLine = async (req, res) => {
910
try {
1011
const session = await getSession(`${req.params.EnterpriseName}.myshopify.com`)
1112
const client = new shopify.api.clients.Graphql({ session });
1213

14+
const order = await getOrder(req.params.id, req.user.id);
15+
16+
if(!order) {
17+
return res.status(403).send('You do not have permission to act on this order');
18+
}
19+
1320
const orderLine = await extractOrderLine(req.body)
1421

1522
const {order: shopifyOrder} = await orders.findOrder(client, req.params.id, {});

web/fdc-modules/orders/controllers/create-order.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ const createOrder = async (req, res) => {
2121

2222
const customerId = await findCustomer(client, req.user.id);
2323

24+
if (!customerId) {
25+
return res.status(403).send(`Customer with email matching ${req.user.id} must exist in shopify for you to create an order`);
26+
}
27+
2428
const { order, saleSession } = await extractOrderAndLinesAndSalesSession(
2529
req.body
2630
);
@@ -37,7 +41,7 @@ const createOrder = async (req, res) => {
3741
shopifyLines
3842
);
3943

40-
await createDraftOrder(ids.extract(shopifyDraftOrder.id));
44+
await createDraftOrder(ids.extract(shopifyDraftOrder.id), req.user.id);
4145
await createSalesSession(
4246
ids.extract(shopifyDraftOrder.id),
4347
saleSession.getEndDate()
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { getAllLineItems } from '../../../database/line_items/lineItems.js';
22
import shopify from '../../../shopify.js';
3+
import { findCustomer } from './shopify/customer.js';
34
import getSession from '../../../utils/getShopifySession.js';
45
import { createBulkDfcOrderFromShopify } from '../dfc/dfc-order.js';
56
import { findOrders } from './shopify/orders.js';
@@ -9,6 +10,12 @@ const getAllOrders = async (req, res) => {
910
const session = await getSession(`${req.params.EnterpriseName}.myshopify.com`)
1011
const client = new shopify.api.clients.Graphql({ session });
1112

13+
const customerId = await findCustomer(client, req.user.id);
14+
15+
if (!customerId) {
16+
return respond(res, await createBulkDfcOrderFromShopify([], [], req.params.EnterpriseName), null);
17+
}
18+
1219
const {before, after, first, last} = req.query;
1320

1421
if ((before && after) || (before && first) || (after && last) && (before && !last) && (after && !first)){
@@ -17,17 +24,23 @@ const getAllOrders = async (req, res) => {
1724

1825
const draftOrdersWithLineItemMappings = await getAllLineItems();
1926

20-
const {orders, pageInfo} = await findOrders(client, {before, after, first: Number(first), last: Number(last)});
27+
const {orders, pageInfo} = await findOrders(client, customerId, {before, after, first: Number(first), last: Number(last)});
2128

2229
const allDfcOrders = await createBulkDfcOrderFromShopify(orders, draftOrdersWithLineItemMappings, req.params.EnterpriseName);
2330

24-
res.type('application/json');
25-
res.set("pageInfo", JSON.stringify(pageInfo));
26-
res.send(allDfcOrders);
31+
return respond(res, allDfcOrders, pageInfo);
2732
} catch (error) {
2833
console.error(error);
2934
res.status(500).end();
3035
}
3136
}
3237

38+
function respond(res, graph, pageInfo) {
39+
res.type('application/json');
40+
if (pageInfo){
41+
res.set("pageInfo", JSON.stringify(pageInfo));
42+
}
43+
res.send(graph);
44+
}
45+
3346
export default getAllOrders

web/fdc-modules/orders/controllers/get-order-line.js

+8
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,19 @@ import getSession from '../../../utils/getShopifySession.js';
33
import { createDfcOrderLineFromShopify } from '../dfc/dfc-order.js';
44
import { findOrder } from './shopify/orders.js';
55
import { getLineItems } from '../../../database/line_items/lineItems.js'
6+
import { getOrder } from '../../../database/orders/orders.js';
7+
68
const getOrderLine = async (req, res) => {
79
try {
810
const session = await getSession(`${req.params.EnterpriseName}.myshopify.com`)
911
const client = new shopify.api.clients.Graphql({ session });
1012

13+
const order = await getOrder(req.params.id, req.user.id);
14+
15+
if(!order) {
16+
return res.status(403).send('You do not have permission to act on this order');
17+
}
18+
1119
const { order: shopifyOrder } = await findOrder(client, req.params.id, {});
1220

1321
if (!shopifyOrder) {

web/fdc-modules/orders/controllers/get-order-lines.js

+8
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,19 @@ import getSession from '../../../utils/getShopifySession.js';
33
import { createDfcOrderLinesFromShopify } from '../dfc/dfc-order.js';
44
import { findOrder } from './shopify/orders.js';
55
import { getLineItems } from '../../../database/line_items/lineItems.js'
6+
import { getOrder } from '../../../database/orders/orders.js';
7+
68
const getOrderLines = async (req, res) => {
79
try {
810
const session = await getSession(`${req.params.EnterpriseName}.myshopify.com`)
911
const client = new shopify.api.clients.Graphql({ session });
1012

13+
const order = await getOrder(req.params.id, req.user.id);
14+
15+
if(!order) {
16+
return res.status(403).send('You do not have permission to act on this order');
17+
}
18+
1119
const {before, after, first, last} = req.query;
1220

1321
if ((before && after) || (before && first) || (after && last) && (before && !last) && (after && !first)){

web/fdc-modules/orders/controllers/get-order.js

+7
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,19 @@ import getSession from '../../../utils/getShopifySession.js';
33
import { createDfcOrderFromShopify } from '../dfc/dfc-order.js';
44
import { findOrder } from './shopify/orders.js';
55
import { getLineItems } from '../../../database/line_items/lineItems.js'
6+
import { getOrder as getOrderMetadata } from '../../../database/orders/orders.js';
67

78
const getOrder = async (req, res) => {
89
try {
910
const session = await getSession(`${req.params.EnterpriseName}.myshopify.com`)
1011
const client = new shopify.api.clients.Graphql({ session });
1112

13+
const order = await getOrderMetadata(req.params.id, req.user.id);
14+
15+
if(!order) {
16+
return res.status(403).send('You do not have permission to act on this order');
17+
}
18+
1219
const { order: shopifyOrder } = await findOrder(client, req.params.id, {});
1320

1421
if (!shopifyOrder) {

web/fdc-modules/orders/controllers/shopify/customer.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export async function findCustomer(client, customerEmail) {
1717
}
1818

1919
if (response.data.customers.nodes.length < 1) {
20-
throw new Error('Unable to find customer with email', customerEmail);
20+
return null;
2121
}
2222

2323
return response.data.customers.nodes[0].id

web/fdc-modules/orders/controllers/shopify/orders.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@ export async function findOrder(client, orderId, { first, last, after, before })
6363
};
6464
}
6565

66-
export async function findOrders(client, { first, last, after, before }) {
66+
export async function findOrders(client, customerId, { first, last, after, before }) {
6767

6868
const query = `
6969
query findDraftOrders($first: Int, $last: Int, $after: String, $before: String) {
70-
draftOrders(first: $first, last: $last, after: $after, before: $before, query: "tag:fdc") {
70+
draftOrders(first: $first, last: $last, after: $after, before: $before, query: "tag:fdc AND customer_id:${ids.extract(customerId)}") {
7171
nodes {
7272
id
7373
status

web/fdc-modules/orders/controllers/update-order.js

+10-2
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,25 @@ import shopify from '../../../shopify.js';
55
import getSession from '../../../utils/getShopifySession.js';
66
import {
77
createDfcOrderFromShopify,
8-
extractOrderAndLines
8+
extractOrderAndLines,
99
} from '../dfc/dfc-order.js';
1010
import { persistLineIdMappings } from './lineItemMappings.js';
1111
import * as ids from './shopify/ids.js';
1212
import * as shopifyOrders from './shopify/orders.js';
1313

14-
//todo: transaction
1514
const updateOrder = async (req, res) => {
1615
try {
1716
console.log('updating order with body:>> ', req.body);
1817
console.log('updating order with params :>> ', req.params);
18+
19+
const orderMetadata = await database.getOrder(req.params.id, req.user.id);
20+
21+
if (!orderMetadata) {
22+
return res
23+
.status(403)
24+
.send('You do not have permission to act on this order');
25+
}
26+
1927
const session = await getSession(
2028
`${req.params.EnterpriseName}.myshopify.com`
2129
);

0 commit comments

Comments
 (0)