Skip to content

Commit 70e6f62

Browse files
committed
initial commit
0 parents  commit 70e6f62

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+33222
-0
lines changed

.editorconfig

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Editor configuration, see http://editorconfig.org
2+
root = true
3+
4+
[*]
5+
charset = utf-8
6+
indent_style = space
7+
indent_size = 2
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
11+
[*.md]
12+
max_line_length = off
13+
trim_trailing_whitespace = false

.env.example

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
SHOPIFY_API_KEY=
2+
SHOPIFY_API_SECRET=
3+
SHOP=
4+
HOST=
5+
REDIS_HOST=localhost
6+
REDIS_PORT=6379

.eslintrc.json

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"root": true,
3+
"ignorePatterns": ["**/*"],
4+
"plugins": ["@nrwl/nx"],
5+
"overrides": [
6+
{
7+
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
8+
"rules": {
9+
"@nrwl/nx/enforce-module-boundaries": [
10+
"error",
11+
{
12+
"enforceBuildableLibDependency": true,
13+
"allow": [],
14+
"depConstraints": [
15+
{
16+
"sourceTag": "*",
17+
"onlyDependOnLibsWithTags": ["*"]
18+
}
19+
]
20+
}
21+
]
22+
}
23+
},
24+
{
25+
"files": ["*.ts", "*.tsx"],
26+
"extends": ["plugin:@nrwl/nx/typescript"],
27+
"rules": {}
28+
},
29+
{
30+
"files": ["*.js", "*.jsx"],
31+
"extends": ["plugin:@nrwl/nx/javascript"],
32+
"rules": {}
33+
}
34+
]
35+
}

.gitignore

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# See http://help.github.com/ignore-files/ for more about ignoring files.
2+
3+
# compiled output
4+
/dist
5+
/tmp
6+
/out-tsc
7+
8+
# dependencies
9+
node_modules
10+
/.env
11+
*.sqlite3
12+
13+
# IDEs and editors
14+
/.idea
15+
.project
16+
.classpath
17+
.c9/
18+
*.launch
19+
.settings/
20+
*.sublime-workspace
21+
22+
# IDE - VSCode
23+
.vscode/*
24+
!.vscode/settings.json
25+
!.vscode/tasks.json
26+
!.vscode/launch.json
27+
!.vscode/extensions.json
28+
29+
# misc
30+
/.sass-cache
31+
/connect.lock
32+
/coverage
33+
/libpeerconnection.log
34+
npm-debug.log
35+
yarn-error.log
36+
testem.log
37+
/typings
38+
39+
# System Files
40+
.DS_Store
41+
Thumbs.db

.node-version

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
16.15

.prettierignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Add files here to ignore them from prettier formatting
2+
3+
/dist
4+
/coverage

.prettierrc

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"singleQuote": true
3+
}

.vscode/extensions.json

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"recommendations": [
3+
"nrwl.angular-console",
4+
"esbenp.prettier-vscode",
5+
"firsttris.vscode-jest-runner",
6+
"dbaeumer.vscode-eslint"
7+
]
8+
}

README.md

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Example @nestjs-shopify/@nestjs-shopify application
2+
3+
Uses [NX monorepo](https://nx.dev) under the hood. An example Shopify application
4+
with a [NestJS](https://nestjs.com) API backend, and a [NextJS](https://nextjs.org) frontend.
5+
6+
## Architecture
7+
8+
The NestJS `api` application is proxied via NX proxies to `/api`. The `api` application also contains
9+
a global prefix to `/api`.
10+
11+
Because we use NX proxies, we basically disable the usage of NextJS API requests in the `pages/api` folder because the requests are always proxied to the backend.
12+
13+
This application uses [Mikro-ORM](https://mikro-orm.io) for it's database. When performing offline auth, the authenticated shop gets inserted into the `shops` table with an offline token. This token can then be used for webhook/background operations.
14+
15+
## Setup
16+
17+
Install dependencies
18+
19+
```
20+
npm install
21+
```
22+
23+
Copy the example environment variables and fill in yours:
24+
25+
```
26+
cp .env.example .env
27+
```
28+
29+
The `HOST` env var should be your full Ngrok URL eg: https://7c350f27f75f.ngrok.io
30+
31+
Run the migrations:
32+
33+
```
34+
cd apps/api
35+
npx mikro-orm schema:update -r
36+
```
37+
38+
## Running
39+
40+
On terminal window 1:
41+
42+
```
43+
npx nx run api:serve
44+
```
45+
46+
On terminal window 2:
47+
48+
```
49+
npx nx run web:serve
50+
```
51+
52+
Visit `https://<HOST>/?shop=<SHOP>` to start the OAuth installation procedure of your app.
53+
54+
## Authentication with Shopify
55+
56+
The application allows for both Online and Offline authentication. But Shopify recommends using
57+
`offline` auth for only installing your application, and `online` auth for loading data in your frontend.
58+
59+
The `ProductsController` in this application that returns the total product count in Shopify, utilizes `@ShopifyOnlineAuth()` decorator. That signals our application to look for online JWT tokens when calling the `GET /api/products/count` route.
60+
61+
The frontend utilizes `@shopify/app-bridge` to transparently fetch online tokens for us using the `userLoggedInfetch` helper function.

apps/api/.env

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PORT=8080

apps/api/.eslintrc.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"extends": ["../../.eslintrc.json"],
3+
"ignorePatterns": ["!**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7+
"rules": {}
8+
},
9+
{
10+
"files": ["*.ts", "*.tsx"],
11+
"rules": {}
12+
},
13+
{
14+
"files": ["*.js", "*.jsx"],
15+
"rules": {}
16+
}
17+
]
18+
}

apps/api/db/mikro-orm.config.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Options } from '@mikro-orm/core';
2+
import { ShopEntity } from '../src/app/shops/shop.entity';
3+
import path from 'path';
4+
5+
const baseDir = path.resolve(__dirname, '../../..');
6+
7+
const config: Options = {
8+
entities: [ShopEntity],
9+
baseDir,
10+
type: 'sqlite',
11+
forceUtcTimezone: true,
12+
timezone: 'Europe/Amsterdam',
13+
dbName: 'api.sqlite3',
14+
debug: true,
15+
};
16+
17+
export default config;

apps/api/jest.config.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/* eslint-disable */
2+
export default {
3+
displayName: 'api',
4+
preset: '../../jest.preset.js',
5+
globals: {
6+
'ts-jest': {
7+
tsconfig: '<rootDir>/tsconfig.spec.json',
8+
},
9+
},
10+
testEnvironment: 'node',
11+
transform: {
12+
'^.+\\.[tj]s$': 'ts-jest',
13+
},
14+
moduleFileExtensions: ['ts', 'js', 'html'],
15+
coverageDirectory: '../../coverage/apps/api',
16+
};

apps/api/project.json

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
3+
"sourceRoot": "apps/api/src",
4+
"projectType": "application",
5+
"targets": {
6+
"build": {
7+
"executor": "@nrwl/node:webpack",
8+
"outputs": ["{options.outputPath}"],
9+
"options": {
10+
"outputPath": "dist/apps/api",
11+
"main": "apps/api/src/main.ts",
12+
"tsConfig": "apps/api/tsconfig.app.json"
13+
},
14+
"configurations": {
15+
"production": {
16+
"optimization": true,
17+
"extractLicenses": true,
18+
"inspect": false,
19+
"fileReplacements": [
20+
{
21+
"replace": "apps/api/src/environments/environment.ts",
22+
"with": "apps/api/src/environments/environment.prod.ts"
23+
}
24+
]
25+
}
26+
}
27+
},
28+
"serve": {
29+
"executor": "@nrwl/node:node",
30+
"options": {
31+
"buildTarget": "api:build"
32+
},
33+
"configurations": {
34+
"production": {
35+
"buildTarget": "api:build:production"
36+
}
37+
}
38+
},
39+
"lint": {
40+
"executor": "@nrwl/linter:eslint",
41+
"outputs": ["{options.outputFile}"],
42+
"options": {
43+
"lintFilePatterns": ["apps/api/**/*.ts"]
44+
}
45+
},
46+
"test": {
47+
"executor": "@nrwl/jest:jest",
48+
"outputs": ["coverage/apps/api"],
49+
"options": {
50+
"jestConfig": "apps/api/jest.config.ts",
51+
"passWithNoTests": true
52+
}
53+
}
54+
},
55+
"tags": []
56+
}

apps/api/src/app/app.module.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { MikroOrmModule } from '@mikro-orm/nestjs';
2+
import {
3+
ShopifyAuthOfflineModule,
4+
ShopifyAuthOnlineModule,
5+
} from '@nestjs-shopify/auth';
6+
import { ShopifyCoreModule } from '@nestjs-shopify/core';
7+
import { Module } from '@nestjs/common';
8+
import { ConfigModule } from '@nestjs/config';
9+
import { databaseConfig } from './config/database.config';
10+
import { ExceptionFiltersModule } from './exception-filters/exception-filters.module';
11+
import { ProductsModule } from './products/products.module';
12+
import { AfterAuthModule } from './shopify/after-auth/after-auth.module';
13+
import {
14+
shopifyCoreConfig,
15+
shopifyOfflineConfig,
16+
shopifyOnlineConfig,
17+
} from './shopify/config';
18+
import { ShopifyOfflineConfigService } from './shopify/services/shopify-offline-config.service';
19+
import { ShopifyOnlineConfigService } from './shopify/services/shopify-online-config.service';
20+
21+
@Module({
22+
imports: [
23+
ConfigModule.forRoot({
24+
cache: true,
25+
isGlobal: true,
26+
}),
27+
MikroOrmModule.forRootAsync(databaseConfig.asProvider()),
28+
ShopifyCoreModule.forRootAsync(shopifyCoreConfig.asProvider()),
29+
ShopifyAuthOfflineModule.forRootAsync({
30+
imports: [ConfigModule.forFeature(shopifyOfflineConfig), AfterAuthModule],
31+
useClass: ShopifyOfflineConfigService,
32+
}),
33+
ShopifyAuthOnlineModule.forRootAsync({
34+
imports: [ConfigModule.forFeature(shopifyOnlineConfig), AfterAuthModule],
35+
useClass: ShopifyOnlineConfigService,
36+
}),
37+
ExceptionFiltersModule,
38+
ProductsModule,
39+
],
40+
})
41+
export class AppModule {}
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { MikroOrmModuleOptions } from '@mikro-orm/nestjs';
2+
import { Logger } from '@nestjs/common';
3+
import { registerAs } from '@nestjs/config';
4+
import config from '../../../db/mikro-orm.config';
5+
6+
const logger = new Logger('MikroORM');
7+
export const getDatabaseConfig = (): MikroOrmModuleOptions => ({
8+
...config,
9+
dbName: 'apps/api/api.sqlite3',
10+
logger: logger.log.bind(logger),
11+
});
12+
13+
export const databaseConfig = registerAs('database', getDatabaseConfig);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {
2+
ShopifyAuthException,
3+
ShopifyAuthExceptionFilter,
4+
} from '@nestjs-shopify/auth';
5+
import { ArgumentsHost, Catch } from '@nestjs/common';
6+
import { BaseExceptionFilter } from '@nestjs/core';
7+
8+
@Catch()
9+
export class AllExceptionFilter extends BaseExceptionFilter {
10+
constructor(private readonly shopifyFilter: ShopifyAuthExceptionFilter) {
11+
super();
12+
}
13+
14+
catch(exception: unknown, host: ArgumentsHost) {
15+
if (exception instanceof ShopifyAuthException) {
16+
return this.shopifyFilter.catch(exception, host);
17+
}
18+
19+
return super.catch(exception, host);
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { ShopifyAuthExceptionFilter } from '@nestjs-shopify/auth';
2+
import { Module } from '@nestjs/common';
3+
import { APP_FILTER } from '@nestjs/core';
4+
import { AllExceptionFilter } from './all.exception-filter';
5+
6+
@Module({
7+
providers: [
8+
ShopifyAuthExceptionFilter,
9+
{
10+
provide: APP_FILTER,
11+
useClass: AllExceptionFilter,
12+
},
13+
],
14+
})
15+
export class ExceptionFiltersModule {}

0 commit comments

Comments
 (0)