Skip to content
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
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ registerProductDiscoverabilityFilter({ hiddenTagValue: 'hidden' });
- Add filters to `Query.orders` to filter by payment and delivery providers
- Allow to call `Query.deliveryInterfaces` without a type
- Add support for SMS providers BudgetSMS and Bulkgate next to Twilio on our privacy-focused mission to always support European alternatives.
- Add `productTags` & `assortmentTags` field in `shopInfo.adminUiConfig` that will return existing tags used for products and assortments and can also be customized to include default tags using `UNCHAINED_ADMIN_UI_DEFAULT_PRODUCT_TAGS` and/or `UNCHAINED_ADMIN_UI_DEFAULT_ASSORTMENT_TAGS`.
- Add option to configure `adminUiConfig.customProperties` through `UNCHAINED_ADMIN_UI_CUSTOM_PROPERTIES` env in addition to platform configuration option that accepts a json file
- Add option to configure `adminUiConfig.singleSignOnURL` through `UNCHAINED_ADMIN_UI_SINGLE_SIGN_ON_URL` env in addition to platform configuration option

## Patch
- Update to ESlint 9
Expand Down
4 changes: 4 additions & 0 deletions examples/kitchensink-express/.env.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ UNCHAINED_SECRET=secret
UNCHAINED_GRIDFS_PUT_UPLOAD_SECRET=secret
UNCHAINED_TOKEN_SECRET=random-token-that-is-not-secret-at-all
UNCHAINED_COOKIE_SAMESITE=none
UNCHAINED_ADMIN_UI_DEFAULT_PRODUCT_TAGS=new,featured,bestseller
UNCHAINED_ADMIN_UI_DEFAULT_ASSORTMENT_TAGS=
UNCHAINED_ADMIN_UI_SINGLE_SIGN_ON_URL=
UNCHAINED_ADMIN_UI_CUSTOM_PROPERTIES=
4 changes: 4 additions & 0 deletions examples/kitchensink/.env.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ UNCHAINED_SECRET=secret
UNCHAINED_GRIDFS_PUT_UPLOAD_SECRET=secret
UNCHAINED_TOKEN_SECRET=random-token-that-is-not-secret-at-all
UNCHAINED_COOKIE_SAMESITE=none
UNCHAINED_ADMIN_UI_DEFAULT_PRODUCT_TAGS=new,featured,bestseller
UNCHAINED_ADMIN_UI_DEFAULT_ASSORTMENT_TAGS=
UNCHAINED_ADMIN_UI_SINGLE_SIGN_ON_URL=
UNCHAINED_ADMIN_UI_CUSTOM_PROPERTIES=./fragments.json
Comment thread
Mikearaya marked this conversation as resolved.
2 changes: 2 additions & 0 deletions packages/api/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export interface CustomAdminUiProperties {
export interface AdminUiConfig {
customProperties?: CustomAdminUiProperties[];
singleSignOnURL?: string;
defaultProductTags?: string[];
defaultAssortmentTags?: string[];
}

export interface UnchainedHTTPServerContext {
Expand Down
40 changes: 37 additions & 3 deletions packages/api/src/resolvers/queries/shopInfo.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { readFile } from 'fs/promises';
import { Context } from '../../context.js';
import { log } from '@unchainedshop/logger';

Expand All @@ -10,14 +11,23 @@ export default function shopInfo(
adminUiConfig?: Record<string, any>;
vapidPublicKey?: string;
} {
const { adminUiConfig } = context;
const { adminUiConfig, modules } = context;
log('query shopInfo', { userId: context.userId });

return {
version: context.version,
adminUiConfig: {
customProperties: adminUiConfig?.customProperties ?? [],
singleSignOnURL: adminUiConfig?.singleSignOnURL,
customProperties: async () => {
try {
const raw = await readFile(process.env.UNCHAINED_ADMIN_UI_CUSTOM_PROPERTIES, 'utf-8');
const parsed = JSON.parse(raw);
return parsed;
} catch {
return adminUiConfig?.customProperties ?? [];
}
},
singleSignOnURL:
process.env.UNCHAINED_ADMIN_UI_SINGLE_SIGN_ON_URL || adminUiConfig?.singleSignOnURL,
externalLinks: () => {
try {
const parsed = JSON.parse(process.env.EXTERNAL_LINKS);
Expand All @@ -26,6 +36,30 @@ export default function shopInfo(
return [];
}
},
productTags: async () => {
const existingProductTags = await modules.products.existingTags();
const envTags = (process.env.UNCHAINED_ADMIN_UI_DEFAULT_PRODUCT_TAGS || '')
.split(',')
.map((t) => t.trim())
.filter(Boolean);
const normalizedDefaultTags = envTags?.length
? envTags
: (adminUiConfig?.defaultProductTags || []).filter(Boolean);
const normalizedTags = Array.from(new Set(normalizedDefaultTags.concat(existingProductTags)));
return normalizedTags;
},
assortmentTags: async () => {
const existingAssortmentTags = await modules.assortments.existingTags();
const envTags = (process.env.UNCHAINED_ADMIN_UI_DEFAULT_ASSORTMENT_TAGS || '')
.split(',')
.map((t) => t.trim())
.filter(Boolean);
const normalizedDefaultTags = envTags?.length
? envTags
: (adminUiConfig?.defaultAssortmentTags || []).filter(Boolean);
const normalizedTags = Array.from(new Set(normalizedDefaultTags.concat(existingAssortmentTags)));
return normalizedTags;
},
},
vapidPublicKey: process.env?.PUSH_NOTIFICATION_PUBLIC_KEY,
};
Expand Down
1 change: 0 additions & 1 deletion packages/api/src/resolvers/type/shop-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { checkAction } from '../../acl.js';
import { allRoles, actions } from '../../roles/index.js';

type HelperType<T> = (root: never, params: never, context: Context) => Promise<T>;

export interface ShopHelperTypes {
_id: () => string;
country: HelperType<Country>;
Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/schema/types/shop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export default [
customProperties: [AdminUiConfigCustomEntityInterface!]!
externalLinks: [AdminUiLink!]!
singleSignOnURL: String
productTags: [String!]!
assortmentTags: [String!]!
}

type Shop @cacheControl(maxAge: 180) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,13 @@ export const configureAssortmentsModule = async ({
links: assortmentLinks,
products: assortmentProducts,
texts: assortmentTexts,
existingTags: async (): Promise<string[]> => {
const tags = await Assortments.distinct('tags', {
tags: { $exists: true },
deleted: { $exists: false },
});
return tags.sort();
},
};
};

Expand Down
7 changes: 7 additions & 0 deletions packages/core-products/src/module/configureProductsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,13 @@ export const configureProductsModule = async ({
},

texts: productTexts,
existingTags: async (): Promise<string[]> => {
const tags = await Products.distinct('tags', {
tags: { $exists: true },
status: { $ne: ProductStatus.DELETED },
});
return tags.sort();
},
};
};

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ const initModules = async (
const products = await configureProductsModule({
db,
migrationRepository,
options: options.products,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently nothing is passed but i noticed that it was the only that module options not passed down to it. (consistency)

});
const quotations = await configureQuotationsModule({
db,
Expand Down