Fix scopes and promise handling (#152)

This commit is contained in:
Anders Gustafsson
2023-05-10 09:17:49 +02:00
committed by GitHub
parent 77f8973a32
commit 7f341f510e
10 changed files with 72 additions and 43 deletions
+20 -13
View File
@@ -7,18 +7,23 @@ When clients want to use the public `data.photowall.com` they use the `/login` R
Third party systems like Unbox (Synergy) will use the `graphql.photowall.com` to read their data like they are doing now. This is to make sure the website wont be affected by larger downloads of data like unbox does. Unbox has their own cognito app client with limited scopes.
## Run it locally
## Running queries on staging or production servers
Create an .env file (see .env.example for what to set).
`COGNITO_LOGIN_CLIENT_SECRET`, `COGNITO_LOGIN_CLIENT_ID` is found in cognito `photowall-test-staff` pool. In `photowall-web-test-client` app.
To tests staging or production servers its possible to run the login rest endpoint to get a Bearer access token. Check SSM for the stacks
`${EnvironmentName}/GRAPHQL_WEB_CLIENT_USERNAME` and `${EnvironmentName}/GRAPHQL_WEB_CLIENT_PASSWORD` and use those as Basic auth for the /login route.
## Publish it to staging
## Running it locally
Run `make publish-to-staging`. Please note that further commits on the same branch will not be updated automatically and the same command must be run again if changes are made.
### .env file
Create an .env file in repo root (see .env.example for what to set). Depending on which resolvers you are going to use different environment variables are needed. Begin by setting DATABASE_URL at least, the rest can be added as needed.
Notes:
COGNITO_LOGIN_CLIENT_ID and COGNITO_LOGIN_CLIENT_SECRET found in cognito `photowall-test-staff` pool. In `photowall-web-test-client` app but are only needed for testing or using the /login route to get proper Bearer authentication.
### Development
This project does only run in docker, so when developing start the docker environment and let it updated when changing files. Sometimes the watch for files doesnt trigger. Like when you edit the .graphql file. Then just edit a .ts file and it will reload.
This project runs in docker, so when developing start the docker environment and it will restart automatically when files are changed. Sometimes the watch for files doesn't trigger. Like when you edit the .graphql file. Then just edit a .ts file and it will reload.
Run
@@ -30,12 +35,16 @@ $ docker compose up
For testing use a tool like Insomnia or similar. Server is running on http://localhost:4000/ and to
connect you need to use `basic auth` with username and password from "GraphQl Basic Auth" in lastpass.
If you wish the Bearer Token used in prodlike environment also works.
If you wish the Bearer Token used in prodlike environment also works. See note under ".env file: above.
If you dont have access to photowall shared lastpass please contact any collegaue.
If you don't have access to photowall shared lastpass please contact any colleague.
If you use Insomnia ask a colleague for the queries that can be exported/imported from Insomnia.
To tests the data.photowall.com its possible to run the login rest endpoint to get a Bearer accessToken. Check code for username and password.
#### Publish feature branch to staging
Run `make publish-to-staging`. Please note that further commits on the same branch will not be updated automatically and the same command must be run again if changes are made.
### Run prod-like container locally
@@ -46,9 +55,7 @@ $ docker compose -f docker-compose-prodlike.yml build
$ docker compose -f docker-compose-prodlike.yml up
```
Get your AD-token from this path in the browser `<url to photowall>/admin/auth/token`
Take the value of the token param and add header in insomnia:Authorization: Bearer <token>.
The token is valid for 2-3 hours.
To access it you must use the login endpoint to get a Bearer token
## Run tests
@@ -58,6 +65,6 @@ Tests
npm test
```
### Force update the staging
### Force update staging
update 5
+1
View File
@@ -21,6 +21,7 @@ export enum Scopes {
LOCALES_READ = 'locales.read',
TEXTS_READ = 'texts.read',
INTERIORS_READ = 'interiors.read',
INTERIORS_PUBLIC_READ = 'interiors.public.read',
INTERIORS_WRITE = 'interiors.write',
}
+6 -3
View File
@@ -103,7 +103,10 @@ export class InteriorAPI extends BaseSQLDataSource {
// ----------------------------
async getInteriors(filter: Maybe<InteriorsFilter>): Promise<Array<Interior>> {
ScopeAccess.validate(this.user).all([Scopes.INTERIORS_READ]);
ScopeAccess.validate(this.user).some([
Scopes.INTERIORS_READ,
Scopes.INTERIORS_PUBLIC_READ,
]);
const sql = /* sql */ `
SELECT rooms.*, room_types.name roomType FROM rooms
LEFT JOIN room_types ON rooms.room_type_id = room_types.id
@@ -161,9 +164,9 @@ export class InteriorAPI extends BaseSQLDataSource {
async getPrintProductInteriorsBatch(
printIds: number[],
): Promise<InteriorWithPrintId[]> {
ScopeAccess.validate(this.user).all([
ScopeAccess.validate(this.user).some([
Scopes.INTERIORS_READ,
Scopes.PRODUCTS_READ,
Scopes.INTERIORS_PUBLIC_READ,
]);
return this.knex
.select('*')
+8 -2
View File
@@ -198,7 +198,10 @@ export class ProductAPI extends BaseSQLDataSource {
// ----------------------------
async getProductsTotal(input: ProductsFilterInput): Promise<number> {
ScopeAccess.validate(this.user).some([Scopes.PRODUCTS_READ]);
ScopeAccess.validate(this.user).some([
Scopes.PRODUCTS_READ,
Scopes.PRODUCTS_PUBLIC_READ,
]);
const query = sql.productsTotal(input);
const res = await this.cachedRaw(query, null)
.cache(MINUTE * 5)
@@ -254,7 +257,10 @@ export class ProductAPI extends BaseSQLDataSource {
}
async getProductByPath(path: string): Promise<Maybe<Product>> {
ScopeAccess.validate(this.user).all([Scopes.PRODUCTS_READ]);
ScopeAccess.validate(this.user).some([
Scopes.PRODUCTS_READ,
Scopes.PRODUCTS_PUBLIC_READ,
]);
const query = sql.productByPath();
const res = await this.knex.raw(query, [path]).then((data) =>
+3 -2
View File
@@ -36,8 +36,9 @@ async function getOrdersResult(
input: GeneralInput,
{ dataSources },
): Promise<OrdersResult> {
const total = (<OrderAPI>dataSources.orderApi).getOrdersTotal(input);
const items = (<OrderAPI>dataSources.orderApi).getOrders(input);
const totalPromise = (<OrderAPI>dataSources.orderApi).getOrdersTotal(input);
const itemsPromise = (<OrderAPI>dataSources.orderApi).getOrders(input);
const [items, total] = await Promise.all([itemsPromise, totalPromise]);
const offset = input.pagination?.offset ?? 0;
return {
items: items,
+23 -10
View File
@@ -114,8 +114,11 @@ async function getProductsResult(
extensions: { code: 'BAD_USER_INPUT' },
});
}
const total = (<ProductAPI>dataSources.productApi).getProductsTotal(input);
const items = (<ProductAPI>dataSources.productApi).getProducts(input);
const totalPromise = (<ProductAPI>dataSources.productApi).getProductsTotal(
input,
);
const itemsPromise = (<ProductAPI>dataSources.productApi).getProducts(input);
const [total, items] = await Promise.all([totalPromise, itemsPromise]);
const offset = input.pagination?.offset ?? 0;
return {
pagination: {
@@ -136,7 +139,7 @@ async function getProductsSearchResult(
const catApi = <CategoryAPI>dataSources.categoryApi;
const keywApi = <KeywordAPI>dataSources.keywordApi;
const designerApi = <DesignerAPI>dataSources.designerApi;
const prods = Promise.all([
const products = await Promise.all([
prodApi.searchByName(q),
prodApi.searchByArtNo(q),
prodApi.searchByCompleteProductId(q),
@@ -152,15 +155,25 @@ async function getProductsSearchResult(
return Object.values(products);
});
const [batches, keywords, categories, designers, copyrights, references] =
await Promise.all([
prodApi.searchByBatch(q),
keywApi.searchKeywords(q),
catApi.searchCategories(q),
designerApi.searchDesigners(q),
prodApi.searchByCopyright(q),
prodApi.searchByReferences(q),
]);
return {
q: q,
products: prods,
batches: prodApi.searchByBatch(q),
keywords: keywApi.searchKeywords(q),
categories: catApi.searchCategories(q),
designers: designerApi.searchDesigners(q),
copyrights: prodApi.searchByCopyright(q),
references: prodApi.searchByReferences(q),
products: products,
batches: batches,
keywords: keywords,
categories: categories,
designers: designers,
copyrights: copyrights,
references: references,
};
}
-2
View File
@@ -1,5 +1,4 @@
import axios from 'axios';
import { Scopes } from '../cognito/access-control';
export const getClientAccessToken = async (req) => {
const token = req.headers.authorization || '';
@@ -29,7 +28,6 @@ export const getClientAccessToken = async (req) => {
data: {
grant_type: 'client_credentials',
client_id: clientId,
scope: `https://graphql.photowall.com/${Scopes.PRODUCTS_PUBLIC_READ} https://graphql.photowall.com/${Scopes.DESIGNERS_PUBLIC_READ} https://graphql.photowall.com/${Scopes.CATEGORIES_PUBLIC_READ}`,
},
});
+1 -1
View File
@@ -2,7 +2,7 @@ import { PaginationResult } from './types';
export interface OrdersResult {
pagination: PaginationResult;
items: Array<Order> | Promise<Array<Order>>;
items: Array<Order>;
}
export interface Address {
+9 -9
View File
@@ -51,17 +51,17 @@ export interface ProductsFilter {
export interface ProductsSearchResult {
q: string;
products: Promise<Array<Product>>;
batches: Promise<Array<Batch>>;
keywords: Promise<Array<Keyword>>;
categories: Promise<Array<Category>>;
designers: Promise<Array<Designer>>;
copyrights: Promise<Array<Copyright>>;
references: Promise<Array<Product>>;
products: Array<Product>;
batches: Array<Batch>;
keywords: Array<Keyword>;
categories: Array<Category>;
designers: Array<Designer>;
copyrights: Array<Copyright>;
references: Array<Product>;
}
export interface ProductListResult {
pagination: PaginationResult;
items: Array<Product> | Promise<Array<Product>>;
items: Array<Product>;
}
export enum ProductGroup {
@@ -133,7 +133,7 @@ export interface Batch {
export interface Copyright {
name: string;
products: Promise<Array<Product>>;
products: Array<Product>;
}
export interface Facet {
+1 -1
View File
@@ -37,7 +37,7 @@ export interface GeneralInput {
}
export interface PaginationResult {
total: Promise<number> | number;
total: number;
offset: number;
limit: number;
}