Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Design updates #270

Merged
merged 12 commits into from
Sep 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/ISSUE_TEMPLATE/1-bug-report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ body:
- **OS**: (e.g. iOS 16.3.1, macOS 13.2.1, Android 13, etc.)
- **Browser**: (e.g. Brave, Firefox, Safari)
value: |
- OS:
- Browser:
- OS:
- Browser:
placeholder: Platform details
validations:
required: true
Expand All @@ -50,4 +50,4 @@ body:
- type: textarea
id: additional-context
attributes:
label: Additional Information
label: Additional Information
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/2-featured-request.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Feature request
description: Suggest something that you think would improve the store.
labels:
- feature-request
- feature-request
body:
- type: textarea
id: problem
Expand Down Expand Up @@ -34,4 +34,4 @@ body:
id: additional-context
attributes:
label: Additional Information
description: Add any other context or screenshots about the feature request here.
description: Add any other context or screenshots about the feature request here.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
blank_issues_enabled: false
blank_issues_enabled: false
8 changes: 4 additions & 4 deletions .github/workflows/build-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:

build-deploy-store:
runs-on: ubuntu-latest
needs: ["get-environment"]
needs: ['get-environment']
environment:
name: ${{ needs.get-environment.outputs.environment }}
steps:
Expand Down Expand Up @@ -88,7 +88,7 @@ jobs:

- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@9933bf0d77b7f6d52e3886fbb8aae95a677db1ab # v1
uses: aws-actions/amazon-ecs-render-task-definition@3c975f1cb22919a28755c6541b4ca2656a690f49 # v1
with:
task-definition: task-definition.json
container-name: store
Expand All @@ -104,7 +104,7 @@ jobs:

build-deploy-store-backend:
runs-on: ubuntu-latest
needs: ["get-environment"]
needs: ['get-environment']
environment:
name: ${{ needs.get-environment.outputs.environment }}
steps:
Expand Down Expand Up @@ -161,7 +161,7 @@ jobs:

- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@9933bf0d77b7f6d52e3886fbb8aae95a677db1ab # v1
uses: aws-actions/amazon-ecs-render-task-definition@3c975f1cb22919a28755c6541b4ca2656a690f49 # v1
with:
task-definition: task-definition.json
container-name: store-backend
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ node_modules
.env
.env.*
!.env.example
static/nala-icons
6 changes: 5 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ node_modules
/package
.env
.env.*
!.env.example
*.generated.*
src/lib/graphql/*
!src/lib/graphql/*.graphql
api/.keystone/**/*
api/schema.graphql

# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
Expand Down
7 changes: 5 additions & 2 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"singleQuote": true,
"plugins": ["prettier-plugin-svelte"],
"printWidth": 100,
"trailingComma": "none",
"printWidth": 100
"semi": true,
"singleQuote": true,
"quoteProps": "preserve"
}
6 changes: 2 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
FROM public.ecr.aws/docker/library/node:18

ENV NODE_ENV=production

WORKDIR /app

COPY ["package.json", "package-lock.json*", "./"]

RUN npm ci --omit=dev --ignore-scripts
RUN npm ci --ignore-scripts

COPY . .

ARG PUBLIC_ASSETS_PATH

RUN npm ci --omit=dev
RUN npm run postinstall

RUN npm run build

Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Brave Merch Store

## Architecture

This repository is a monorepo with two services:

### Storefront

The storefront is built using [SvelteKit](https://kit.svelte.dev/). All queries to the API are defined using GraphQL, but via an autogenerated SDK created by [`@graphql-codegen`](https://the-guild.dev/graphql/codegen). E.g.

```graphql
Expand All @@ -19,11 +21,13 @@ const { products } = await sdk.FeaturedProducts();
```

### API

The API is powered by [Keystone](https://keystonejs.com/), which is a CMS that provides a GraphQL API which the storefront consumes. Note that this API is not publicly available, and is restricted to requests coming from the `storefront` service.

All of the data comes from Printful via their [API](https://developers.printful.com/docs/).

## Production deployment

### Frontend

#### Build
Expand Down Expand Up @@ -86,9 +90,11 @@ SENTRY_DSN=/* DSN for Sentry alerts */
### Setup

#### PostgreSQL

In order to develop locally, you must have [PostgreSQL](https://www.postgresql.org/download/) downloaded and running on your machine.

#### Environment variables

You'll also need to create two `.env` files – one in the root of the repository, and the other in the `api/` folder:

```bash
Expand All @@ -99,6 +105,7 @@ touch api/.env
The files should be populated with the respective sets of variables as described in [Production deployment](#production-deployment), using the appropriate values for your local environment.

#### Dependencies

Ensure dependencies are installed for both the root director and the `api/` directory.

```bash
Expand All @@ -108,6 +115,7 @@ cd ../ # return to root for subsequent commands
```

### Start API (Keystone)

In order to start the API, open a new terminal window, `cd` into `api`, and start the service.

```bash
Expand All @@ -119,13 +127,15 @@ Running the above command will create the database if necessary and run the migr
You can populate the DB by going to `http://localhost:3000/sync-store` and clicking the `Sync now` button. **Note** that in our staging and production environments, images are synced to our own S3 bucket, and we do not request images directly from Printful's CDN on the storefront. However when running locally, these images _**will**_ be requested from Printful's CDN unless you've authenticated with AWS with the appropriate role.

### Start storefront (SvelteKit)

Leaving the terminal window open for the API, start the storefront from the root of the repository. In order to run this, you'll need to provide the `PUBLIC_ASSETS_PATH` environment variable in order for it to be available during the initial build phase. The value should use the staging URL for the image CDN: `https://cdn.store.bravesoftware.com`.

```bash
PUBLIC_ASSETS_PATH=https://cdn.store.bravesoftware.com npm run dev
```

### Start watch script for `codegen` (optional)

The storefront uses an SDK to interact with the API which is automatically generated by [`@graphql-codegen`](https://the-guild.dev/graphql/codegen) from GraphQL queries defined in `src/lib/graphql/queries.graphql`.

In order to watch for changes to `src/lib/graphql/queries.graphql` and trigger this generation process, you can run the following command **in a new terminal** (this would be the third necessary terminal).
Expand All @@ -135,6 +145,7 @@ npm run gen -- --watch "src/**/*.graphql"
```

## Branch promotion

Upon approval, PR branches are merged to `staging` via `Squash and merge` and the staging environment is rebuilt via CI/CD. Once functionality is confirmed on the staging site and the feature is ready to move to production, the `staging` branch is merged into `main` using the `--ff-only` flag on a local machine and then pushed to the remote `main` branch.

E.g.
Expand Down
8 changes: 3 additions & 5 deletions api/.keystone/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,7 @@ async function downloadImageIfNeeded(imageUrl) {
try {
const existingImage = await getImagePathIfExists(imageUrl);
const imagePath = existingImage || await downloadImage(imageUrl);
if (!imagePath)
return;
if (!imagePath) return;
return `${PUBLIC_ASSETS_PATH}/${imagePath}`;
} catch (e) {
console.log("Error", e.message);
Expand Down Expand Up @@ -222,8 +221,7 @@ function upsertProduct(newProductData, existingProductData, context) {
if (!existingProductData?.variants?.some(
(v) => v.printfulVariantId === variant.printfulVariantId
)) {
if (!updatedProductData.variants)
updatedProductData.variants = { create: [] };
if (!updatedProductData.variants) updatedProductData.variants = { create: [] };
updatedProductData.variants.create = [...updatedProductData.variants.create, variant];
}
});
Expand Down Expand Up @@ -424,7 +422,7 @@ var lists = {
where: { id: item.id.toString() },
query: `product { slug }`
});
return `/p/${product.slug}/${item.printfulVariantId}`;
return `/p/${product.slug}/${item.printfulVariantId}/`;
}
})
}),
Expand Down
4 changes: 2 additions & 2 deletions api/.keystone/config.js.map

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions api/admin/components/CustomNavigation.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { NavigationContainer, ListNavItems, NavItem } from '@keystone-6/core/admin-ui/components';
import type { NavigationProps } from '@keystone-6/core/admin-ui/components';
export function CustomNavigation({ lists, authenticatedItem }: NavigationProps) {
return (
<NavigationContainer authenticatedItem={authenticatedItem}>
<NavItem href="/">Dashboard</NavItem>
<ListNavItems lists={lists} />
<NavItem href="/sync-store">Sync Store</NavItem>
<NavItem href="/decrypt-recipient">Decrypt recipient</NavItem>
</NavigationContainer>
)
}
return (
<NavigationContainer authenticatedItem={authenticatedItem}>
<NavItem href="/">Dashboard</NavItem>
<ListNavItems lists={lists} />
<NavItem href="/sync-store">Sync Store</NavItem>
<NavItem href="/decrypt-recipient">Decrypt recipient</NavItem>
</NavigationContainer>
);
}
6 changes: 3 additions & 3 deletions api/admin/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { AdminConfig } from '@keystone-6/core/types';
import { CustomNavigation } from './components/CustomNavigation';
export const components: AdminConfig['components']= {
Navigation: CustomNavigation
};
export const components: AdminConfig['components'] = {
Navigation: CustomNavigation
};
53 changes: 35 additions & 18 deletions api/admin/pages/decrypt-recipient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,31 @@

import { useState } from 'react';
import { PageContainer } from '@keystone-6/core/admin-ui/components';
import { jsx, Heading, Stack, Box } from "@keystone-ui/core";
import { Button } from "@keystone-ui/button";
import { TextInput, FieldContainer, FieldLabel } from "@keystone-ui/fields"
import { decrypt } from "../../utils/decrypt";
import { ToastProvider, useToasts } from "@keystone-ui/toast";
import { jsx, Heading, Stack, Box } from '@keystone-ui/core';
import { Button } from '@keystone-ui/button';
import { TextInput, FieldContainer, FieldLabel } from '@keystone-ui/fields';
import { decrypt } from '../../utils/decrypt';
import { ToastProvider, useToasts } from '@keystone-ui/toast';

export default function DecryptRecipient() {
const [isLoading, setLoading] = useState(false);
const [keyId, setKeyId] = useState("");
const [encryptedString, setEncryptedString] = useState("");
const [decryptedString, setDecryptedString] = useState(null as object|null);
const [keyId, setKeyId] = useState('');
const [encryptedString, setEncryptedString] = useState('');
const [decryptedString, setDecryptedString] = useState(null as object | null);
const { addToast } = useToasts();

async function decryptString() {
if (keyId && encryptedString) {
setLoading(true);
const response = await fetch("/rest/keys/" + keyId);
const response = await fetch('/rest/keys/' + keyId);
const respBody = await response.json();

if (response.status === 200 && respBody.key) {
try {
const decrypted = decrypt(encryptedString, respBody.key);
setDecryptedString(decrypted);
} catch (e) {
addToast({ title: "Error", message: "Could not decrypt string.", tone: "negative" });
addToast({ title: 'Error', message: 'Could not decrypt string.', tone: 'negative' });
}
}
setLoading(false);
Expand All @@ -37,22 +37,39 @@ export default function DecryptRecipient() {
return (
<PageContainer header={<Heading type="h3">Decrypt recipient</Heading>}>
<h4>Decrypt recipient info</h4>
<p>To inspect recipient info provided to stripe, please paste the keyId and encrypted recipient string in the boxes below.</p>
<p>
To inspect recipient info provided to stripe, please paste the keyId and encrypted recipient
string in the boxes below.
</p>
<Stack width="100%" gap="large">
<FieldContainer>
<FieldLabel>Key ID</FieldLabel>
<TextInput value={keyId} onChange={e => setKeyId(e.target.value)} />
<TextInput value={keyId} onChange={(e) => setKeyId(e.target.value)} />
</FieldContainer>
<FieldContainer>
<FieldLabel>Encrypted recipient string</FieldLabel>
<TextInput value={encryptedString} onChange={e => setEncryptedString(e.target.value)} />
<TextInput value={encryptedString} onChange={(e) => setEncryptedString(e.target.value)} />
</FieldContainer>
<Button isDisabled={!keyId || !encryptedString} isLoading={isLoading} size='medium' tone='active' weight='bold' onClick={decryptString}>Decrypt</Button>
{decryptedString && <Box background="neutral100" padding="medium" rounding="medium"><pre><code>{JSON.stringify(decryptedString, null, 2)}</code></pre></Box>}
<Button
isDisabled={!keyId || !encryptedString}
isLoading={isLoading}
size="medium"
tone="active"
weight="bold"
onClick={decryptString}
>
Decrypt
</Button>
{decryptedString && (
<Box background="neutral100" padding="medium" rounding="medium">
<pre>
<code>{JSON.stringify(decryptedString, null, 2)}</code>
</pre>
</Box>
)}
</Stack>

<ToastProvider>
</ToastProvider>
<ToastProvider></ToastProvider>
</PageContainer>
);
}
}
Loading
Loading