Merge branch 'develop' into enhancement/config-password-rules-u

This commit is contained in:
Bassel Kanso 2024-09-30 16:30:11 +03:00 committed by GitHub
commit 68b78b685a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
72 changed files with 3460 additions and 5982 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"name": "check-pr-status",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"private": true,
"license": "MIT",
"main": "dist/index.js",
@ -14,6 +14,6 @@
"@actions/github": "6.0.0"
},
"devDependencies": {
"@vercel/ncc": "0.38.0"
"@vercel/ncc": "0.38.2"
}
}

View File

@ -9,7 +9,7 @@
<h3 align="center">Open-source headless CMS, self-hosted or Cloud youre in control.</h3>
<p align="center">The leading open-source headless CMS, 100% JavaScript/TypeScript, flexible and fully customizable.</p>
<p align="center"><a href="https://cloud.strapi.io/signups?source=github1">Cloud</a> · <a href="https://strapi.io/demo?utm_campaign=Growth-Experiments&utm_source=strapi%2Fstrapi%20README.md">Try live demo</a> · <a href="https://strapi.io/five?utm_campaign=Product%20Marketing%20Strapi%205%20Launch%20Q1%20Q2%202024&utm_source=GitHub&utm_medium=strapi%2Fstrapi%20README.md">Strapi 5 (coming soon)</a></p>
<p align="center"><a href="https://cloud.strapi.io/signups?source=github1">Cloud</a> · <a href="https://strapi.io/demo?utm_campaign=Growth-Experiments&utm_source=strapi%2Fstrapi%20README.md">Try live demo</a></p>
<br />
<p align="center">

View File

@ -27,9 +27,9 @@
},
"dependencies": {
"@cmfcmf/docusaurus-search-local": "1.1.0",
"@docusaurus/core": "3.1.1",
"@docusaurus/preset-classic": "3.1.1",
"@docusaurus/theme-mermaid": "3.1.1",
"@docusaurus/core": "3.5.2",
"@docusaurus/preset-classic": "3.5.2",
"@docusaurus/theme-mermaid": "3.5.2",
"@mdx-js/react": "^3.0.0",
"clsx": "^1.1.1",
"prism-react-renderer": "^2.1.0",
@ -37,7 +37,7 @@
"react-dom": "18.3.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.1.1",
"@docusaurus/module-type-aliases": "3.5.2",
"docusaurus-plugin-typedoc": "0.22.0",
"typedoc": "0.25.9",
"typedoc-plugin-markdown": "3.17.1",

File diff suppressed because it is too large Load Diff

View File

@ -29,6 +29,6 @@
"devDependencies": {
"@vitejs/plugin-react": "4.2.1",
"babel-plugin-react-compiler": "0.0.0-experimental-c23de8d-20240515",
"vite": "5.1.7"
"vite": "5.2.14"
}
}

View File

@ -1,4 +1,4 @@
{
"version": "5.0.1",
"version": "5.0.2-beta.0",
"npmClient": "yarn"
}

View File

@ -101,7 +101,7 @@
"@typescript-eslint/eslint-plugin": "6.7.3",
"@typescript-eslint/parser": "6.7.3",
"chalk": "4.1.2",
"chokidar": "3.5.3",
"chokidar": "3.6.0",
"coffee": "5.5.1",
"core-js": "3.36.0",
"create-strapi-app": "workspace:*",
@ -136,7 +136,7 @@
"jest-environment-jsdom": "29.6.1",
"jest-watch-typeahead": "2.2.2",
"lerna": "8.1.2",
"lint-staged": "13.3.0",
"lint-staged": "15.2.10",
"lodash": "4.17.21",
"nx": "18.2.2",
"plop": "2.7.6",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/admin-test-utils",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"private": true,
"description": "Test utilities for the Strapi administration panel",
"license": "MIT",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/cloud-cli",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Commands to interact with the Strapi Cloud",
"keywords": [
"strapi",

View File

@ -1,6 +1,6 @@
{
"name": "create-strapi-app",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Generate a new Strapi application.",
"keywords": [
"create-strapi-app",

View File

@ -1,6 +1,6 @@
{
"name": "create-strapi",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Generate a new Strapi application.",
"keywords": [
"create-strapi",

View File

@ -460,7 +460,7 @@ const LinkCustom = styled(LinkButton)`
display: flex;
align-items: center;
border: none;
justify-content: left;
svg {
width: ${({ theme }) => theme.spaces[6]};
height: ${({ theme }) => theme.spaces[6]};

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/admin",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Strapi Admin",
"repository": {
"type": "git",
@ -164,7 +164,7 @@
"react-dom": "18.3.1",
"react-router-dom": "6.22.3",
"styled-components": "6.1.8",
"vite": "5.1.7",
"vite": "5.2.14",
"vite-plugin-dts": "3.7.3"
},
"peerDependencies": {

View File

@ -202,6 +202,12 @@ describe('useDocument', () => {
"visible": false,
"writable": false,
},
"dz": {
"components": [
"blog.test-como",
],
"type": "dynamiczone",
},
"id": {
"type": "string",
},

View File

@ -71,6 +71,32 @@ describe('useDocumentLayout', () => {
expect(result.current.edit.layout).toMatchInlineSnapshot(`
[
[
[
{
"attribute": {
"components": [
"blog.test-como",
],
"type": "dynamiczone",
},
"disabled": false,
"hint": "",
"label": "dz",
"mainField": {
"name": "name",
"type": "string",
},
"name": "dz",
"placeholder": "",
"required": false,
"size": 12,
"type": "dynamiczone",
"unique": false,
"visible": true,
},
],
],
[
[
{
@ -90,6 +116,8 @@ describe('useDocumentLayout', () => {
"visible": true,
},
],
],
[
[
{
"attribute": {

View File

@ -250,9 +250,10 @@ const formatEditLayout = (
currentPanelIndex += 2;
} else {
if (!panels[currentPanelIndex]) {
panels.push([]);
panels.push([row]);
} else {
panels[currentPanelIndex].push(row);
}
panels[currentPanelIndex].push(row);
}
return panels;

View File

@ -120,6 +120,7 @@ const EditViewPage = () => {
return transformDocument(schema, components)(form);
}, [document, isCreatingDocument, isSingleType, schema, components]);
if (hasError) {
return <Page.Error />;
}

View File

@ -19,9 +19,10 @@ import {
ButtonProps,
} from '@strapi/design-system';
import { Cross, More, WarningCircle } from '@strapi/icons';
import mapValues from 'lodash/fp/mapValues';
import { useIntl } from 'react-intl';
import { useMatch, useNavigate } from 'react-router-dom';
import { styled, DefaultTheme } from 'styled-components';
import { DefaultTheme } from 'styled-components';
import { PUBLISHED_AT_ATTRIBUTE_NAME } from '../../../constants/attributes';
import { SINGLE_TYPES } from '../../../constants/collections';
@ -35,6 +36,7 @@ import { getTranslation } from '../../../utils/translations';
import type { RelationsFormValue } from './FormInputs/Relations';
import type { DocumentActionComponent } from '../../../content-manager';
/* -------------------------------------------------------------------------------------------------
* Types
* -----------------------------------------------------------------------------------------------*/
@ -493,6 +495,22 @@ const DocumentActionModal = ({
);
};
const transformData = (data: Record<string, any>): any => {
if (Array.isArray(data)) {
return data.map(transformData);
}
if (typeof data === 'object' && data !== null) {
if ('apiData' in data) {
return data.apiData;
}
return mapValues(transformData)(data);
}
return data;
};
/* -------------------------------------------------------------------------------------------------
* DocumentActionComponents
* -----------------------------------------------------------------------------------------------*/
@ -643,7 +661,7 @@ const PublishAction: DocumentActionComponent = ({
documentId,
params,
},
formValues
transformData(formValues)
);
if ('data' in res && collectionType !== SINGLE_TYPES) {
@ -797,7 +815,7 @@ const UpdateAction: DocumentActionComponent = ({
documentId: cloneMatch.params.origin!,
params,
},
document
transformData(document)
);
if ('data' in res) {
@ -823,7 +841,7 @@ const UpdateAction: DocumentActionComponent = ({
documentId,
params,
},
document
transformData(document)
);
if (
@ -841,7 +859,7 @@ const UpdateAction: DocumentActionComponent = ({
model,
params,
},
document
transformData(document)
);
if ('data' in res && collectionType !== SINGLE_TYPES) {

View File

@ -84,7 +84,13 @@ function useHandleDisconnect(fieldName: string, consumerName: string) {
}
}
addFieldRow(`${fieldName}.disconnect`, { id: relation.id });
addFieldRow(`${fieldName}.disconnect`, {
id: relation.id,
apiData: {
documentId: relation.documentId,
locale: relation.locale,
},
});
};
return handleDisconnect;
@ -145,14 +151,23 @@ const RelationsField = React.forwardRef<HTMLDivElement, RelationsFieldProps>(
const isMorph = props.attribute.relation.toLowerCase().includes('morph');
const isDisabled = isMorph || disabled;
const { id: componentId, uid } = useComponent('RelationsField', ({ uid, id }) => ({ id, uid }));
const { componentId, componentUID } = useComponent('RelationsField', ({ uid, id }) => ({
componentId: id,
componentUID: uid,
}));
const isSubmitting = useForm('RelationsList', (state) => state.isSubmitting);
React.useEffect(() => {
setCurrentPage(1);
}, [isSubmitting]);
/**
* We'll always have a documentId in a created entry, so we look for a componentId first.
* Same with `uid` and `documentModel`.
*/
const id = componentId ? componentId.toString() : documentId;
const model = uid ?? documentModel;
const model = componentUID ?? documentModel;
/**
* The `name` prop is a complete path to the field, e.g. `field1.field2.field3`.
@ -199,6 +214,7 @@ const RelationsField = React.forwardRef<HTMLDivElement, RelationsFieldProps>(
const realServerRelationsCount =
'pagination' in data && data.pagination ? data.pagination.total : 0;
/**
* Items that are already connected, but reordered would be in
* this list, so to get an accurate figure, we remove them.
@ -259,6 +275,10 @@ const RelationsField = React.forwardRef<HTMLDivElement, RelationsFieldProps>(
const item = {
id: relation.id,
apiData: {
documentId: relation.documentId,
locale: relation.locale,
},
status: relation.status,
/**
* If there's a last item, that's the first key we use to generate out next one.
@ -268,7 +288,7 @@ const RelationsField = React.forwardRef<HTMLDivElement, RelationsFieldProps>(
[props.mainField?.name ?? 'documentId']: relation[props.mainField?.name ?? 'documentId'],
label: getRelationLabel(relation, props.mainField),
// @ts-expect-error targetModel does exist on the attribute, but it's not typed.
href: `../${COLLECTION_TYPES}/${props.attribute.targetModel}/${relation.documentId}`,
href: `../${COLLECTION_TYPES}/${props.attribute.targetModel}/${relation.documentId}?${relation.locale ? `plugins[i18n][locale]=${relation.locale}` : ''}`,
};
if (ONE_WAY_RELATIONS.includes(props.attribute.relation)) {
@ -294,7 +314,8 @@ const RelationsField = React.forwardRef<HTMLDivElement, RelationsFieldProps>(
<StyledFlex direction="column" alignItems="start" gap={2} width="100%">
<RelationsInput
disabled={isDisabled}
id={id}
// NOTE: we should not default to using the documentId if the component is being created (componentUID is undefined)
id={componentUID ? (componentId ? `${componentId}` : '') : documentId}
label={`${label} ${relationsCount > 0 ? `(${relationsCount})` : ''}`}
model={model}
onChange={handleConnect}
@ -389,7 +410,7 @@ const addLabelAndHref =
// Fallback to `id` if there is no `mainField` value, which will overwrite the above `documentId` property with the exact same data.
[mainField?.name ?? 'documentId']: relation[mainField?.name ?? 'documentId'],
label: getRelationLabel(relation, mainField),
href: `${href}/${relation.documentId}`,
href: `${href}/${relation.documentId}?${relation.locale ? `plugins[i18n][locale]=${relation.locale}` : ''}`,
};
});
@ -454,6 +475,7 @@ const RelationsInput = ({
* individual components. Therefore we split the string and take the last item.
*/
const [targetField] = name.split('.').slice(-1);
searchForTrigger({
model,
targetField,
@ -701,9 +723,7 @@ const RelationsList = ({
*/
const connectedRelations = newData
.reduce<Relation[]>((acc, relation, currentIndex, array) => {
const relationOnServer = serverData.find(
(oldRelation) => oldRelation.documentId === relation.documentId
);
const relationOnServer = serverData.find((oldRelation) => oldRelation.id === relation.id);
const relationInFront = array[currentIndex + 1];
@ -712,11 +732,23 @@ const RelationsList = ({
? {
before: relationInFront.documentId,
locale: relationInFront.locale,
status: relationInFront.status,
status:
'publishedAt' in relationInFront && relationInFront.publishedAt
? 'published'
: 'draft',
}
: { end: true };
const relationWithPosition: Relation = { ...relation, position };
const relationWithPosition: Relation = {
...relation,
...{
apiData: {
documentId: relation.documentId,
locale: relation.locale,
position,
},
},
};
return [...acc, relationWithPosition];
}
@ -899,7 +931,7 @@ const ListItem = ({ data, index, style }: ListItemProps) => {
} = data;
const { formatMessage } = useIntl();
const { href, documentId, label, status } = relations[index];
const { href, id, label, status } = relations[index];
const [{ handlerId, isDragging, handleKeyDown }, relationRef, dropRef, dragRef, dragPreviewRef] =
useDragAndDrop<number, Omit<RelationDragPreviewProps, 'width'>, HTMLDivElement>(
@ -910,7 +942,7 @@ const ListItem = ({ data, index, style }: ListItemProps) => {
item: {
displayedValue: label,
status,
id: documentId,
id: id,
index,
},
onMoveItem: handleMoveItem,

View File

@ -67,11 +67,8 @@ const relationsApi = contentManagerApi.injectEndpoints({
* Relations will always have unique IDs, so we can therefore assume
* that we only need to push the new items to the cache.
*/
const existingIds = currentCache.results.map((item) => item.documentId);
const uniqueNewItems = newItems.results.filter(
(item) => !existingIds.includes(item.documentId)
);
currentCache.results.push(...prepareTempKeys(uniqueNewItems, currentCache.results));
currentCache.results.push(...prepareTempKeys(newItems.results, currentCache.results));
currentCache.pagination = newItems.pagination;
} else if (newItems.pagination.page === 1) {
/**

View File

@ -398,6 +398,10 @@ const CM_CONTENT_TYPE_MOCK_DATA = [
allowedTypes: ['images'],
pluginOptions: {},
},
dz: {
type: 'dynamiczone',
components: ['blog.test-como'],
},
city: {
type: 'string',
required: true,
@ -718,6 +722,22 @@ const CM_COLLECTION_TYPE_LAYOUT_MOCK_DATA = {
mainField: 'name',
},
},
dz: {
edit: {
label: 'dz',
description: '',
placeholder: '',
visible: true,
editable: true,
mainField: 'name',
},
list: {
label: 'dz',
searchable: false,
sortable: false,
mainField: 'name',
},
},
cover: {
edit: {
label: 'cover',
@ -894,6 +914,7 @@ const CM_COLLECTION_TYPE_LAYOUT_MOCK_DATA = {
layouts: {
list: ['id', 'categories', 'cover', 'postal_code'],
edit: [
[{ name: 'dz', size: 12 }],
[
{
name: 'slug',

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/content-manager",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "A powerful UI to easily manage your data.",
"repository": {
"type": "git",

View File

@ -49,17 +49,39 @@ const sanitizeMainField = (model: any, mainField: any, userAbility: any) => {
return mainField;
};
const addStatusToRelations = async (uid: UID.ContentType, relations: RelationEntity[]) => {
if (!contentTypes.hasDraftAndPublish(strapi.contentTypes[uid])) {
/**
*
* All relations sent to this function should have the same status or no status
*/
const addStatusToRelations = async (targetUid: UID.Schema, relations: RelationEntity[]) => {
if (!contentTypes.hasDraftAndPublish(strapi.getModel(targetUid))) {
return relations;
}
const documentMetadata = getService('document-metadata');
const documentsAvailableStatus = await documentMetadata.getManyAvailableStatus(uid, relations);
if (!relations.length) {
return relations;
}
const firstRelation = relations[0];
const filters: any = {
documentId: { $in: relations.map((r) => r.documentId) },
// NOTE: find the "opposite" status
publishedAt: firstRelation.publishedAt !== null ? { $null: true } : { $notNull: true },
};
const availableStatus = await strapi.query(targetUid).findMany({
select: ['id', 'documentId', 'locale', 'updatedAt', 'createdAt', 'publishedAt'],
filters,
});
return relations.map((relation: RelationEntity) => {
const availableStatuses = documentsAvailableStatus.filter(
(availableDocument: RelationEntity) => availableDocument.documentId === relation.documentId
const availableStatuses = availableStatus.filter(
(availableDocument: RelationEntity) =>
availableDocument.documentId === relation.documentId &&
(relation.locale ? availableDocument.locale === relation.locale : true)
);
return {
@ -396,14 +418,14 @@ export default {
attribute,
targetField,
fieldsToSelect,
source: {
schema: { uid: sourceUid },
},
target: {
schema: { uid: targetUid },
},
status,
source: { schema: sourceSchema },
target: { schema: targetSchema },
} = await this.extractAndValidateRequestInfo(ctx, id);
const { uid: sourceUid } = sourceSchema;
const { uid: targetUid } = targetSchema;
const permissionQuery = await getService('permission-checker')
.create({ userAbility, model: targetUid })
.sanitizedQuery.read({ fields: fieldsToSelect });
@ -424,6 +446,23 @@ export default {
// Ensure response is an array
.then((res) => ({ results: res ? [res] : [] }));
const filters: {
publishedAt?: Record<string, any>;
} = {};
if (sourceSchema?.options?.draftAndPublish) {
if (targetSchema?.options?.draftAndPublish) {
if (status === 'published') {
filters.publishedAt = { $notNull: true };
} else {
filters.publishedAt = { $null: true };
}
}
} else if (targetSchema?.options?.draftAndPublish) {
// NOTE: we must return the drafts as some targets might not have a published version yet
filters.publishedAt = { $null: true };
}
/**
* If user does not have access to specific relations (custom conditions),
* only the ids of the relations are returned.
@ -434,10 +473,11 @@ export default {
* The response contains the union of the two queries.
*/
const res = await loadRelations({ id: entryId }, targetField, {
select: ['id', 'documentId', 'locale', 'publishedAt'],
select: ['id', 'documentId', 'locale', 'publishedAt', 'updatedAt'],
ordering: 'desc',
page: ctx.request.query.page,
pageSize: ctx.request.query.pageSize,
filters,
});
/**
@ -458,6 +498,7 @@ export default {
ordering: 'desc',
});
// NOTE: the order is very import to make sure sanitized relations are kept in priority
const relationsUnion = uniqBy('id', concat(sanitizedRes.results, res.results));
ctx.body = {

View File

@ -7,11 +7,11 @@ import type { DocumentMetadata } from '../../../shared/contracts/collection-type
import { getValidatableFieldsPopulate } from './utils/populate';
export interface DocumentVersion {
id: number;
id: string | number;
documentId: Modules.Documents.ID;
locale: string;
updatedAt: string | null | Date;
publishedAt: string | null | Date;
locale?: string;
updatedAt?: string | null | Date;
publishedAt?: string | null | Date;
}
const AVAILABLE_STATUS_FIELDS = [
@ -86,7 +86,9 @@ export default ({ strapi }: { strapi: Core.Strapi }) => ({
const versionsByLocale = groupBy('locale', allVersions);
// Delete the current locale
delete versionsByLocale[version.locale];
if (version.locale) {
delete versionsByLocale[version.locale];
}
// For each locale, get the ones with the same status
// There will not be a draft and a version counterpart if the content

View File

@ -1,8 +1,11 @@
import '@strapi/types';
import { DocumentManagerService } from 'src/services/document-manager';
import DocumentMetadata from 'src/services/document-metadata';
type Services = {
'document-manager': DocumentManagerService;
'document-metadata': typeof DocumentMetadata;
[key: string]: any;
};

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/content-releases",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Strapi plugin for organizing and releasing content",
"repository": {
"type": "git",
@ -85,11 +85,11 @@
"react-query": "3.39.3",
"react-router-dom": "6.22.3",
"styled-components": "6.1.8",
"typescript": "5.2.2"
"typescript": "5.3.2"
},
"peerDependencies": {
"@strapi/admin": "^5.0.0 || ^5.0.0-beta || ^5.0.0-alpha || ^5.0.0-rc",
"@strapi/content-manager": "^5.0.0 || ^5.0.0-beta || ^5.0.0-alpha || ^5.0.0-rc",
"@strapi/admin": "^5.0.0",
"@strapi/content-manager": "^5.0.0",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0",
"react-router-dom": "^6.0.0",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/content-type-builder",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Create and manage content types",
"repository": {
"type": "git",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/core",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Core of Strapi",
"homepage": "https://strapi.io",
"bugs": {
@ -102,7 +102,7 @@
"resolve.exports": "2.0.2",
"semver": "7.5.4",
"statuses": "2.0.1",
"typescript": "5.2.2",
"typescript": "5.3.2",
"undici": "6.19.2",
"yup": "0.32.9"
},

View File

@ -291,7 +291,10 @@ export const createContentTypeRepository: RepositoryFactoryMethod = (uid) => {
]);
// Load any unidirectional relation targetting the old published entries
const relationsToSync = await unidirectionalRelations.load(uid, oldPublishedVersions);
const relationsToSync = await unidirectionalRelations.load(uid, {
newVersions: draftsToPublish,
oldVersions: oldPublishedVersions,
});
// Delete old published versions
await async.map(oldPublishedVersions, (entry: any) => entries.delete(entry.id));
@ -302,7 +305,11 @@ export const createContentTypeRepository: RepositoryFactoryMethod = (uid) => {
);
// Sync unidirectional relations with the new published entries
await unidirectionalRelations.sync(oldPublishedVersions, publishedEntries, relationsToSync);
await unidirectionalRelations.sync(
[...oldPublishedVersions, ...draftsToPublish],
publishedEntries,
relationsToSync
);
publishedEntries.forEach(emitEvent('entry.publish'));
@ -358,7 +365,10 @@ export const createContentTypeRepository: RepositoryFactoryMethod = (uid) => {
]);
// Load any unidirectional relation targeting the old drafts
const relationsToSync = await unidirectionalRelations.load(uid, oldDrafts);
const relationsToSync = await unidirectionalRelations.load(uid, {
newVersions: versionsToDraft,
oldVersions: oldDrafts,
});
// Delete old drafts
await async.map(oldDrafts, (entry: any) => entries.delete(entry.id));
@ -369,7 +379,11 @@ export const createContentTypeRepository: RepositoryFactoryMethod = (uid) => {
);
// Sync unidirectional relations with the new draft entries
await unidirectionalRelations.sync(oldDrafts, draftEntries, relationsToSync);
await unidirectionalRelations.sync(
[...oldDrafts, ...versionsToDraft],
draftEntries,
relationsToSync
);
draftEntries.forEach(emitEvent('entry.draft-discard'));
return { documentId, entries: draftEntries };

View File

@ -1,18 +1,19 @@
/* eslint-disable no-continue */
import { keyBy } from 'lodash/fp';
import { keyBy, omit } from 'lodash/fp';
import { UID, Schema } from '@strapi/types';
interface LoadContext {
oldVersions: { id: string; locale: string }[];
newVersions: { id: string; locale: string }[];
}
/**
* Loads lingering relations that need to be updated when overriding a published or draft entry.
* This is necessary because the relations are uni-directional and the target entry is not aware of the source entry.
* This is not the case for bi-directional relations, where the target entry is also linked to the source entry.
*
* @param uid The content type uid
* @param oldEntries The old entries that are being overridden
* @returns An array of relations that need to be updated with the join table reference.
*/
const load = async (uid: UID.ContentType, oldEntries: { id: string; locale: string }[]) => {
const load = async (uid: UID.ContentType, { oldVersions, newVersions }: LoadContext) => {
const updates = [] as any;
// Iterate all components and content types to find relations that need to be updated
@ -27,29 +28,80 @@ const load = async (uid: UID.ContentType, oldEntries: { id: string; locale: stri
/**
* Only consider unidirectional relations
*/
if (attribute.type !== 'relation') continue;
if (attribute.target !== uid) continue;
if (attribute.inversedBy || attribute.mappedBy) continue;
const joinTable = attribute.joinTable;
// TODO: joinColumn relations
if (!joinTable) continue;
if (
attribute.type !== 'relation' ||
attribute.target !== uid ||
attribute.inversedBy ||
attribute.mappedBy
) {
continue;
}
const { name } = joinTable.inverseJoinColumn;
// TODO: joinColumn relations
const joinTable = attribute.joinTable;
if (!joinTable) {
continue;
}
const { name: sourceColumnName } = joinTable.joinColumn;
const { name: targetColumnName } = joinTable.inverseJoinColumn;
/**
* Load all relations that need to be updated
*/
const oldEntriesIds = oldEntries.map((entry) => entry.id);
const relations = await strapi.db
// NOTE: when the model has draft and publish, we can assume relation are only draft to draft & published to published
const ids = oldVersions.map((entry) => entry.id);
const oldVersionsRelations = await strapi.db
.getConnection()
.select('*')
.from(joinTable.name)
.whereIn(name, oldEntriesIds)
.whereIn(targetColumnName, ids)
.transacting(trx);
if (relations.length === 0) continue;
if (oldVersionsRelations.length > 0) {
updates.push({ joinTable, relations: oldVersionsRelations });
}
updates.push({ joinTable, relations });
/**
* if publishing
* if published version exists
* updated published versions links
* else
* create link to newly published version
*
* if discarding
* if published version link exists & not draft version link
* create link to new draft version
*/
if (!model.options?.draftAndPublish) {
const ids = newVersions.map((entry) => entry.id);
const newVersionsRelations = await strapi.db
.getConnection()
.select('*')
.from(joinTable.name)
.whereIn(targetColumnName, ids)
.transacting(trx);
if (newVersionsRelations.length > 0) {
// when publishing a draft that doesn't have a published version yet,
// copy the links to the draft over to the published version
// when discarding a published version, if no drafts exists
const discardToAdd = newVersionsRelations
.filter((relation) => {
const matchingOldVerion = oldVersionsRelations.find((oldRelation) => {
return oldRelation[sourceColumnName] === relation[sourceColumnName];
});
return !matchingOldVerion;
})
.map(omit('id'));
updates.push({ joinTable, relations: discardToAdd });
}
}
}
}
});
@ -89,8 +141,9 @@ const sync = async (
// Iterate old relations that are deleted and insert the new ones
for (const { joinTable, relations } of oldRelations) {
// Update old ids with the new ones
const column = joinTable.inverseJoinColumn.name;
const newRelations = relations.map((relation) => {
const column = joinTable.inverseJoinColumn.name;
const newId = oldEntriesMap[relation[column]];
return { ...relation, [column]: newId };
});

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/data-transfer",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Data transfer capabilities for Strapi",
"keywords": [
"strapi",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/database",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Strapi's database layer",
"homepage": "https://strapi.io",
"bugs": {

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/email",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Easily configure your Strapi application to send emails.",
"repository": {
"type": "git",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/permissions",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Strapi's permission layer.",
"repository": {
"type": "git",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/review-workflows",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Review workflows for your content",
"repository": {
"type": "git",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/strapi",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MySQL, MariaDB, PostgreSQL, SQLite",
"keywords": [
"strapi",
@ -134,7 +134,7 @@
"browserslist": "^4.23.0",
"browserslist-to-esbuild": "1.2.0",
"chalk": "4.1.2",
"chokidar": "3.5.3",
"chokidar": "3.6.0",
"ci-info": "3.8.0",
"cli-progress": "3.12.0",
"cli-table3": "0.6.5",
@ -159,14 +159,14 @@
"ora": "5.4.1",
"outdent": "0.8.0",
"pkg-up": "3.1.0",
"prettier": "3.2.5",
"prettier": "3.3.3",
"react-refresh": "0.14.0",
"read-pkg-up": "7.0.1",
"resolve-from": "5.0.0",
"semver": "7.5.4",
"style-loader": "3.3.4",
"typescript": "5.3.2",
"vite": "5.1.7",
"vite": "5.2.14",
"webpack": "^5.90.3",
"webpack-bundle-analyzer": "^4.10.1",
"webpack-dev-middleware": "6.1.2",

View File

@ -23,7 +23,6 @@ const watch = async (ctx: BuildContext): Promise<WebpackWatcher> => {
const devMiddleware = webpackDevMiddleware(compiler);
// @ts-expect-error incompatible types between hotMiddleware and webpack
const hotMiddleware = webpackHotMiddleware(compiler, {
log: false,
path: '/__webpack_hmr',

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/types",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Shared typescript types for Strapi internal use",
"keywords": [
"strapi"

View File

@ -12,6 +12,9 @@ import { BulkMoveButton } from './BulkMoveButton';
export const BulkActions = ({ selected, onSuccess, currentFolder }) => {
const { formatMessage } = useIntl();
const numberAssets = selected.reduce(function (_this, val) {
return val?.type === 'folder' ? _this + val.files.count : _this + 1;
}, 0);
return (
<Flex gap={2} paddingBottom={5}>
@ -24,7 +27,7 @@ export const BulkActions = ({ selected, onSuccess, currentFolder }) => {
},
{
numberFolders: selected.filter(({ type }) => type === 'folder').length,
numberAssets: selected.filter(({ type }) => type === 'asset').length,
numberAssets,
}
)}
</Typography>

View File

@ -58,7 +58,11 @@ describe('BulkActions', () => {
onSuccess: jest.fn(),
selected: [
...[...Array(ASSET_COUNT).keys()].map((index) => ({ id: index, type: 'asset' })),
...[...Array(FOLDER_COUNT).keys()].map((index) => ({ id: index, type: 'folder' })),
...[...Array(FOLDER_COUNT).keys()].map((index) => ({
id: index,
type: 'folder',
files: { count: 0 },
})),
],
});

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/upload",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Makes it easy to upload images and files to your Strapi Application.",
"license": "SEE LICENSE IN LICENSE",
"author": {

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/utils",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Shared utilities for the Strapi packages",
"keywords": [
"strapi",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/generators",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Interactive API generator.",
"keywords": [
"strapi",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/plugin-cloud",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Instructions to deploy your local project to Strapi Cloud",
"license": "MIT",
"author": {
@ -49,7 +49,7 @@
"react-router-dom": "6.22.3",
"styled-components": "6.1.8",
"tsconfig": "workspace:*",
"typescript": "5.2.2"
"typescript": "5.3.2"
},
"peerDependencies": {
"@strapi/strapi": "^5.0.0 || ^5.0.0-beta || ^5.0.0-alpha || ^5.0.0-rc",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/plugin-color-picker",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Strapi maintained Custom Fields",
"repository": {
"type": "git",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/plugin-documentation",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Create an OpenAPI Document and visualize your API with SWAGGER UI.",
"repository": {
"type": "git",
@ -66,7 +66,7 @@
"immer": "9.0.21",
"koa-static": "^5.0.0",
"lodash": "4.17.21",
"path-to-regexp": "6.2.1",
"path-to-regexp": "6.3.0",
"react-intl": "6.6.2",
"swagger-ui-dist": "4.19.0",
"yaml": "1.10.2",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/plugin-graphql",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Adds GraphQL endpoint with default API methods.",
"repository": {
"type": "git",
@ -48,7 +48,7 @@
"watch": "strapi-plugin watch"
},
"dependencies": {
"@apollo/server": "4.10.0",
"@apollo/server": "4.11.0",
"@as-integrations/koa": "1.1.1",
"@graphql-tools/schema": "10.0.3",
"@graphql-tools/utils": "^10.1.3",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/i18n",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Create read and update content in different languages, both from the Admin Panel and from the API",
"repository": {
"type": "git",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/plugin-sentry",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Send Strapi error events to Sentry",
"repository": {
"type": "git",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/plugin-users-permissions",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Protect your API with a full-authentication process based on JWT",
"repository": {
"type": "git",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/provider-email-amazon-ses",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Amazon SES provider for strapi email",
"keywords": [
"email",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/provider-email-mailgun",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Mailgun provider for strapi email plugin",
"keywords": [
"email",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/provider-email-nodemailer",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Nodemailer provider for Strapi 3",
"keywords": [
"strapi",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/provider-email-sendgrid",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Sendgrid provider for strapi email",
"keywords": [
"email",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/provider-email-sendmail",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Sendmail provider for strapi email",
"keywords": [
"email",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/provider-upload-aws-s3",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "AWS S3 provider for strapi upload",
"keywords": [
"upload",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/provider-upload-cloudinary",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Cloudinary provider for strapi upload",
"keywords": [
"upload",

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/provider-upload-local",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Local provider for strapi upload",
"keywords": [
"upload",

View File

@ -1,6 +1,6 @@
{
"name": "api-tests",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"private": true,
"dependencies": {
"dotenv": "16.4.5",

View File

@ -1,6 +1,6 @@
{
"name": "eslint-config-custom",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"private": true,
"main": "index.js"
}

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/logger",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Strapi's logger",
"homepage": "https://strapi.io",
"bugs": {

View File

@ -1,6 +1,6 @@
{
"name": "tsconfig",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"private": true,
"devDependencies": {
"@tsconfig/node18": "18.2.2"

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/typescript-utils",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "Typescript support for Strapi",
"keywords": [
"strapi",
@ -39,7 +39,7 @@
"cli-table3": "0.6.5",
"fs-extra": "11.2.0",
"lodash": "4.17.21",
"prettier": "3.2.5",
"prettier": "3.3.3",
"typescript": "5.3.2"
},
"devDependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "@strapi/upgrade",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"description": "CLI to upgrade Strapi applications effortless",
"keywords": [
"strapi",

View File

@ -1,6 +1,6 @@
{
"name": "scripts-front",
"version": "5.0.1",
"version": "5.0.2-beta.0",
"private": true,
"scripts": {
"test:front": "jest --config jest.config.front.js"

View File

@ -13,7 +13,7 @@
"@strapi/plugin-graphql": "^5.0.0",
"@strapi/plugin-users-permissions": "^5.0.0",
"@strapi/strapi": "^5.0.0",
"lodash.set": "^4.3.2",
"lodash": "^4.17.19",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-router-dom": "^6.0.0",

View File

@ -1,5 +1,5 @@
import fs from 'fs';
import set from 'lodash.set';
import { set } from 'lodash';
import en from '../data/en.json';
import fr from '../data/fr.json';

View File

@ -5537,9 +5537,9 @@ __metadata:
languageName: node
linkType: hard
"body-parser@npm:1.20.2":
version: 1.20.2
resolution: "body-parser@npm:1.20.2"
"body-parser@npm:1.20.3":
version: 1.20.3
resolution: "body-parser@npm:1.20.3"
dependencies:
bytes: "npm:3.1.2"
content-type: "npm:~1.0.5"
@ -5549,11 +5549,11 @@ __metadata:
http-errors: "npm:2.0.0"
iconv-lite: "npm:0.4.24"
on-finished: "npm:2.4.1"
qs: "npm:6.11.0"
qs: "npm:6.13.0"
raw-body: "npm:2.5.2"
type-is: "npm:~1.6.18"
unpipe: "npm:1.0.0"
checksum: 10c0/06f1438fff388a2e2354c96aa3ea8147b79bfcb1262dfcc2aae68ec13723d01d5781680657b74e9f83c808266d5baf52804032fbde2b7382b89bd8cdb273ace9
checksum: 10c0/0a9a93b7518f222885498dcecaad528cf010dd109b071bf471c93def4bfe30958b83e03496eb9c1ad4896db543d999bb62be1a3087294162a88cfa1b42c16310
languageName: node
linkType: hard
@ -7179,9 +7179,9 @@ __metadata:
linkType: hard
"dset@npm:^3.1.2":
version: 3.1.3
resolution: "dset@npm:3.1.3"
checksum: 10c0/b1ff68f1f42af373baa85b00b04d89094cd0d7f74f94bd11364cba575f2762ed52a0a0503bbfcc92eccd07c6d55426813c8a7a6cfa020338eaea1f4edfd332c2
version: 3.1.4
resolution: "dset@npm:3.1.4"
checksum: 10c0/b67bbd28dd8a539e90c15ffb61100eb64ef995c5270a124d4f99bbb53f4d82f55a051b731ba81f3215dd9dce2b4c8d69927dc20b3be1c5fc88bab159467aa438
languageName: node
linkType: hard
@ -7279,6 +7279,13 @@ __metadata:
languageName: node
linkType: hard
"encodeurl@npm:~2.0.0":
version: 2.0.0
resolution: "encodeurl@npm:2.0.0"
checksum: 10c0/5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb
languageName: node
linkType: hard
"encoding@npm:^0.1.13":
version: 0.1.13
resolution: "encoding@npm:0.1.13"
@ -7896,41 +7903,41 @@ __metadata:
linkType: hard
"express@npm:^4.17.1":
version: 4.19.2
resolution: "express@npm:4.19.2"
version: 4.21.0
resolution: "express@npm:4.21.0"
dependencies:
accepts: "npm:~1.3.8"
array-flatten: "npm:1.1.1"
body-parser: "npm:1.20.2"
body-parser: "npm:1.20.3"
content-disposition: "npm:0.5.4"
content-type: "npm:~1.0.4"
cookie: "npm:0.6.0"
cookie-signature: "npm:1.0.6"
debug: "npm:2.6.9"
depd: "npm:2.0.0"
encodeurl: "npm:~1.0.2"
encodeurl: "npm:~2.0.0"
escape-html: "npm:~1.0.3"
etag: "npm:~1.8.1"
finalhandler: "npm:1.2.0"
finalhandler: "npm:1.3.1"
fresh: "npm:0.5.2"
http-errors: "npm:2.0.0"
merge-descriptors: "npm:1.0.1"
merge-descriptors: "npm:1.0.3"
methods: "npm:~1.1.2"
on-finished: "npm:2.4.1"
parseurl: "npm:~1.3.3"
path-to-regexp: "npm:0.1.7"
path-to-regexp: "npm:0.1.10"
proxy-addr: "npm:~2.0.7"
qs: "npm:6.11.0"
qs: "npm:6.13.0"
range-parser: "npm:~1.2.1"
safe-buffer: "npm:5.2.1"
send: "npm:0.18.0"
serve-static: "npm:1.15.0"
send: "npm:0.19.0"
serve-static: "npm:1.16.2"
setprototypeof: "npm:1.2.0"
statuses: "npm:2.0.1"
type-is: "npm:~1.6.18"
utils-merge: "npm:1.0.1"
vary: "npm:~1.1.2"
checksum: 10c0/e82e2662ea9971c1407aea9fc3c16d6b963e55e3830cd0ef5e00b533feda8b770af4e3be630488ef8a752d7c75c4fcefb15892868eeaafe7353cb9e3e269fdcb
checksum: 10c0/4cf7ca328f3fdeb720f30ccb2ea7708bfa7d345f9cc460b64a82bf1b2c91e5b5852ba15a9a11b2a165d6089acf83457fc477dc904d59cd71ed34c7a91762c6cc
languageName: node
linkType: hard
@ -8081,18 +8088,18 @@ __metadata:
languageName: node
linkType: hard
"finalhandler@npm:1.2.0":
version: 1.2.0
resolution: "finalhandler@npm:1.2.0"
"finalhandler@npm:1.3.1":
version: 1.3.1
resolution: "finalhandler@npm:1.3.1"
dependencies:
debug: "npm:2.6.9"
encodeurl: "npm:~1.0.2"
encodeurl: "npm:~2.0.0"
escape-html: "npm:~1.0.3"
on-finished: "npm:2.4.1"
parseurl: "npm:~1.3.3"
statuses: "npm:2.0.1"
unpipe: "npm:~1.0.0"
checksum: 10c0/64b7e5ff2ad1fcb14931cd012651631b721ce657da24aedb5650ddde9378bf8e95daa451da43398123f5de161a81e79ff5affe4f9f2a6d2df4a813d6d3e254b7
checksum: 10c0/d38035831865a49b5610206a3a9a9aae4e8523cbbcd01175d0480ffbf1278c47f11d89be3ca7f617ae6d94f29cf797546a4619cd84dd109009ef33f12f69019f
languageName: node
linkType: hard
@ -10678,13 +10685,6 @@ __metadata:
languageName: node
linkType: hard
"lodash.set@npm:^4.3.2":
version: 4.3.2
resolution: "lodash.set@npm:4.3.2"
checksum: 10c0/c641d31905e51df43170dce8a1d11a1cff11356e2e2e75fe2615995408e9687d58c3e1d64c3c284c2df2bc519f79a98af737d2944d382ff82ffd244ff6075c29
languageName: node
linkType: hard
"lodash.sortby@npm:^4.7.0":
version: 4.7.0
resolution: "lodash.sortby@npm:4.7.0"
@ -11059,10 +11059,10 @@ __metadata:
languageName: node
linkType: hard
"merge-descriptors@npm:1.0.1":
version: 1.0.1
resolution: "merge-descriptors@npm:1.0.1"
checksum: 10c0/b67d07bd44cfc45cebdec349bb6e1f7b077ee2fd5beb15d1f7af073849208cb6f144fe403e29a36571baf3f4e86469ac39acf13c318381e958e186b2766f54ec
"merge-descriptors@npm:1.0.3":
version: 1.0.3
resolution: "merge-descriptors@npm:1.0.3"
checksum: 10c0/866b7094afd9293b5ea5dcd82d71f80e51514bed33b4c4e9f516795dc366612a4cbb4dc94356e943a8a6914889a914530badff27f397191b9b75cda20b6bae93
languageName: node
linkType: hard
@ -12324,17 +12324,17 @@ __metadata:
languageName: node
linkType: hard
"path-to-regexp@npm:0.1.7":
version: 0.1.7
resolution: "path-to-regexp@npm:0.1.7"
checksum: 10c0/50a1ddb1af41a9e68bd67ca8e331a705899d16fb720a1ea3a41e310480948387daf603abb14d7b0826c58f10146d49050a1291ba6a82b78a382d1c02c0b8f905
"path-to-regexp@npm:0.1.10":
version: 0.1.10
resolution: "path-to-regexp@npm:0.1.10"
checksum: 10c0/34196775b9113ca6df88e94c8d83ba82c0e1a2063dd33bfe2803a980da8d49b91db8104f49d5191b44ea780d46b8670ce2b7f4a5e349b0c48c6779b653f1afe4
languageName: node
linkType: hard
"path-to-regexp@npm:^6.2.0, path-to-regexp@npm:^6.2.1":
version: 6.2.2
resolution: "path-to-regexp@npm:6.2.2"
checksum: 10c0/4b60852d3501fd05ca9dd08c70033d73844e5eca14e41f499f069afa8364f780f15c5098002f93bd42af8b3514de62ac6e82a53b5662de881d2b08c9ef21ea6b
version: 6.3.0
resolution: "path-to-regexp@npm:6.3.0"
checksum: 10c0/73b67f4638b41cde56254e6354e46ae3a2ebc08279583f6af3d96fe4664fc75788f74ed0d18ca44fa4a98491b69434f9eee73b97bb5314bd1b5adb700f5c18d6
languageName: node
linkType: hard
@ -12745,15 +12745,6 @@ __metadata:
languageName: node
linkType: hard
"qs@npm:6.11.0":
version: 6.11.0
resolution: "qs@npm:6.11.0"
dependencies:
side-channel: "npm:^1.0.4"
checksum: 10c0/4e4875e4d7c7c31c233d07a448e7e4650f456178b9dd3766b7cfa13158fdb24ecb8c4f059fa91e820dc6ab9f2d243721d071c9c0378892dcdad86e9e9a27c68f
languageName: node
linkType: hard
"qs@npm:6.11.1":
version: 6.11.1
resolution: "qs@npm:6.11.1"
@ -12763,7 +12754,7 @@ __metadata:
languageName: node
linkType: hard
"qs@npm:^6.10.3, qs@npm:^6.11.0, qs@npm:^6.11.2, qs@npm:^6.5.2, qs@npm:^6.9.6":
"qs@npm:6.13.0, qs@npm:^6.10.3, qs@npm:^6.11.0, qs@npm:^6.11.2, qs@npm:^6.5.2, qs@npm:^6.9.6":
version: 6.13.0
resolution: "qs@npm:6.13.0"
dependencies:
@ -13848,9 +13839,9 @@ __metadata:
languageName: node
linkType: hard
"send@npm:0.18.0":
version: 0.18.0
resolution: "send@npm:0.18.0"
"send@npm:0.19.0":
version: 0.19.0
resolution: "send@npm:0.19.0"
dependencies:
debug: "npm:2.6.9"
depd: "npm:2.0.0"
@ -13865,7 +13856,7 @@ __metadata:
on-finished: "npm:2.4.1"
range-parser: "npm:~1.2.1"
statuses: "npm:2.0.1"
checksum: 10c0/0eb134d6a51fc13bbcb976a1f4214ea1e33f242fae046efc311e80aff66c7a43603e26a79d9d06670283a13000e51be6e0a2cb80ff0942eaf9f1cd30b7ae736a
checksum: 10c0/ea3f8a67a8f0be3d6bf9080f0baed6d2c51d11d4f7b4470de96a5029c598a7011c497511ccc28968b70ef05508675cebff27da9151dd2ceadd60be4e6cf845e3
languageName: node
linkType: hard
@ -13907,15 +13898,15 @@ __metadata:
languageName: node
linkType: hard
"serve-static@npm:1.15.0":
version: 1.15.0
resolution: "serve-static@npm:1.15.0"
"serve-static@npm:1.16.2":
version: 1.16.2
resolution: "serve-static@npm:1.16.2"
dependencies:
encodeurl: "npm:~1.0.2"
encodeurl: "npm:~2.0.0"
escape-html: "npm:~1.0.3"
parseurl: "npm:~1.3.3"
send: "npm:0.18.0"
checksum: 10c0/fa9f0e21a540a28f301258dfe1e57bb4f81cd460d28f0e973860477dd4acef946a1f41748b5bd41c73b621bea2029569c935faa38578fd34cd42a9b4947088ba
send: "npm:0.19.0"
checksum: 10c0/528fff6f5e12d0c5a391229ad893910709bc51b5705962b09404a1d813857578149b8815f35d3ee5752f44cd378d0f31669d4b1d7e2d11f41e08283d5134bd1f
languageName: node
linkType: hard
@ -15889,7 +15880,7 @@ __metadata:
"@strapi/plugin-graphql": "npm:^5.0.0"
"@strapi/plugin-users-permissions": "npm:^5.0.0"
"@strapi/strapi": "npm:^5.0.0"
lodash.set: "npm:^4.3.2"
lodash: "npm:^4.17.19"
react: "npm:^18.0.0"
react-dom: "npm:^18.0.0"
react-router-dom: "npm:^6.0.0"

View File

@ -138,7 +138,7 @@ describe('Relation permissions', () => {
const shopEntry = await createEntry(
'api::shop.shop',
{ name: 'Shop', products: [product.id] },
{ name: 'Shop', products: [product.documentId] },
populateShop
);
shop = shopEntry.data;

View File

@ -23,7 +23,7 @@ const getRelations = async (modelName, field, id) => {
url: `/content-manager/relations/api::${modelName}.${modelName}/${id}/${field}`,
});
return res.body.data;
return res.body;
};
const deleteFixtures = async () => {
@ -48,8 +48,7 @@ const deleteFixtures = async () => {
}
};
// TODO: Fix relations
describe.skip('Relations', () => {
describe('Relations', () => {
beforeAll(async () => {
await builder
.addContentTypes(
@ -329,7 +328,8 @@ describe.skip('Relations', () => {
expect(tags.length).toBe(0);
});
test('Delete all articles should remove the association in each tags related to them', async () => {
test('Delete all articles should remove the association from all tags related to them', async () => {
// Create a tag
const {
body: { data: createdTag },
} = await rq({
@ -340,6 +340,7 @@ describe.skip('Relations', () => {
},
});
// Create the first article associated with the created tag
const {
body: { data: article12 },
} = await rq({
@ -352,6 +353,7 @@ describe.skip('Relations', () => {
},
});
// Retrieve the updated tag to ensure it has the association
const {
body: { data: updatedTag },
} = await rq({
@ -359,6 +361,7 @@ describe.skip('Relations', () => {
method: 'GET',
});
// Create a second article associated with the same tag
const {
body: { data: article13 },
} = await rq({
@ -371,6 +374,7 @@ describe.skip('Relations', () => {
},
});
// Retrieve the tag again to validate that it is associated with both articles
const {
body: { data: foundTag },
} = await rq({
@ -378,38 +382,75 @@ describe.skip('Relations', () => {
method: 'GET',
});
// Assert that the tag is linked to both articles
expect(foundTag.articles.count).toBe(2);
await rq({
// Bulk delete both articles using their document IDs
const bulkDelete = await rq({
url: '/content-manager/collection-types/api::article.article/actions/bulkDelete',
method: 'POST',
body: {
ids: [article12.documentId, article13.documentId],
documentIds: [article12.documentId, article13.documentId],
},
});
expect(bulkDelete.status).toBe(200);
// Retrieve the tag again to check that no articles are associated with it
const {
body: { data: foundTag2 },
} = await rq({
url: `/content-manager/collection-types/api::tag.tag/${createdTag.documentId}`,
url: `/content-manager/collection-types/api::tag.tag/${createdTag.documentId}?populate=articles`,
method: 'GET',
});
// Expect that the tag has no associated articles after the bulk delete
expect(foundTag2.articles.count).toBe(0);
});
test('Bulk delete of unknown or already deleted entries should succeed', async () => {
const res = await rq({
url: '/content-manager/collection-types/api::article.article/actions/bulkDelete',
test('Bulk delete with some known and some unknown should delete and return SUCCESS', async () => {
const {
body: { data: article12 },
} = await rq({
url: '/content-manager/collection-types/api::article.article',
method: 'POST',
body: {
ids: [9999999],
title: 'article12',
content: 'Content',
tags: [],
},
});
expect(res.body).toEqual({
count: 0,
const bulkDelete = await rq({
url: '/content-manager/collection-types/api::article.article/actions/bulkDelete',
method: 'POST',
body: {
documentIds: [article12.documentId, 9999999],
},
});
expect(bulkDelete.status).toBe(200);
expect(bulkDelete.body.count).toBe(1);
// article has been deleted
const article = await rq({
url: `/content-manager/collection-types/api::article.article/${article12.documentId}`,
method: 'GET',
});
expect(article.status).toBe(404);
});
test('Bulk delete of entirely unknown entries should return a NOT FOUND', async () => {
const bulkDelete = await rq({
url: '/content-manager/collection-types/api::article.article/actions/bulkDelete',
method: 'POST',
body: {
documentIds: [9999999],
},
});
expect(bulkDelete.status).toBe(404);
});
});
@ -562,8 +603,9 @@ describe.skip('Relations', () => {
const tags = (await getRelations('article', 'tags', body.data.documentId)).results;
expect(tags.length).toBe(0);
const category = (await getRelations('article', 'category', body.data.documentId)).data;
expect(category.name).toBe(data.categories[0].name);
const categories = (await getRelations('article', 'category', body.data.documentId)).results;
expect(categories.length).toBe(1);
expect(categories[0].name).toBe(data.categories[0].name);
});
test('Update article1 with cat2', async () => {
@ -592,8 +634,9 @@ describe.skip('Relations', () => {
const tags = (await getRelations('article', 'tags', body.data.documentId)).results;
expect(tags.length).toBe(0);
const category = (await getRelations('article', 'category', body.data.documentId)).data;
expect(category.name).toBe(data.categories[1].name);
const categories = (await getRelations('article', 'category', body.data.documentId)).results;
expect(categories.length).toBe(1);
expect(categories[0].name).toBe(data.categories[1].name);
});
test('Create article2', async () => {
@ -652,8 +695,9 @@ describe.skip('Relations', () => {
const tags = (await getRelations('article', 'tags', body.data.documentId)).results;
expect(tags.length).toBe(0);
const category = (await getRelations('article', 'category', body.data.documentId)).data;
expect(category.name).toBe(data.categories[1].name);
const categories = (await getRelations('article', 'category', body.data.documentId)).results;
expect(categories.length).toBe(1);
expect(categories[0].name).toBe(data.categories[1].name);
});
test('Update cat1 with article1', async () => {
@ -717,7 +761,7 @@ describe.skip('Relations', () => {
method: 'GET',
});
expect(body.data).toMatchObject({ data: { name: 'cat3' } });
expect(body).toMatchObject({ results: [{ name: 'cat3' }] });
});
test('Get article2 with cat2', async () => {
@ -726,7 +770,7 @@ describe.skip('Relations', () => {
method: 'GET',
});
expect(body.data).toMatchObject({ data: { name: 'cat2' } });
expect(body).toMatchObject({ results: [{ name: 'cat2' }] });
});
test('Get cat1 without relations', async () => {
@ -735,7 +779,7 @@ describe.skip('Relations', () => {
method: 'GET',
});
expect(body.data).toMatchObject({
expect(body).toMatchObject({
results: [],
pagination: {
total: 0,
@ -752,7 +796,7 @@ describe.skip('Relations', () => {
method: 'GET',
});
expect(body.data).toMatchObject({
expect(body).toMatchObject({
pagination: { page: 1, pageCount: 1, pageSize: 10, total: 1 },
results: [{ title: 'Article 2' }],
});
@ -764,7 +808,7 @@ describe.skip('Relations', () => {
method: 'GET',
});
expect(body.data).toMatchObject({
expect(body).toMatchObject({
pagination: { page: 1, pageCount: 1, pageSize: 10, total: 1 },
results: [{ title: 'Article 1' }],
});
@ -857,8 +901,8 @@ describe.skip('Relations', () => {
username: null,
});
const reference = (await getRelations('article', 'reference', body.data.documentId)).data;
expect(reference.documentId).toBe(data.references[0].documentId);
const references = await getRelations('article', 'reference', body.data.documentId);
expect(references.results[0].documentId).toBe(data.references[0].documentId);
});
test('Create article2 with ref1', async () => {
@ -887,14 +931,16 @@ describe.skip('Relations', () => {
id: 1,
username: null,
});
const reference = (await getRelations('article', 'reference', body.data.documentId)).data;
expect(reference.documentId).toBe(data.references[0].documentId);
const references = await getRelations('article', 'reference', body.data.documentId);
expect(references.results[0].documentId).toBe(data.references[0].documentId);
});
});
describe('Test oneWay relation (reference - tag) with Content Manager', () => {
test('Attach Tag to a Reference', async () => {
const { body: createdTag } = await rq({
const {
body: { data: createdTag },
} = await rq({
url: '/content-manager/collection-types/api::tag.tag',
method: 'POST',
body: {
@ -902,7 +948,9 @@ describe.skip('Relations', () => {
},
});
const { body: createdReference } = await rq({
const {
body: { data: createdReference },
} = await rq({
url: '/content-manager/collection-types/api::reference.reference',
method: 'POST',
body: {
@ -913,8 +961,8 @@ describe.skip('Relations', () => {
expect(createdReference.documentId).toBeDefined();
const tag = (await getRelations('reference', 'tag', createdReference.documentId)).data;
expect(tag.documentId).toBe(createdTag.documentId);
const tags = await getRelations('reference', 'tag', createdReference.documentId);
expect(tags.results[0].documentId).toBe(createdTag.documentId);
});
test('Detach Tag to a Reference', async () => {
@ -939,8 +987,8 @@ describe.skip('Relations', () => {
},
});
let tag = (await getRelations('reference', 'tag', createdReference.documentId)).data;
expect(tag.documentId).toBe(createdTag.documentId);
let tags = await getRelations('reference', 'tag', createdReference.documentId);
expect(tags.results[0].documentId).toBe(createdTag.documentId);
const {
body: { data: referenceToUpdate },
@ -952,8 +1000,8 @@ describe.skip('Relations', () => {
},
});
tag = (await getRelations('reference', 'tag', referenceToUpdate.documentId)).results;
expect(isEmpty(tag)).toBe(true);
tags = await getRelations('reference', 'tag', referenceToUpdate.documentId);
expect(isEmpty(tags.results)).toBe(true);
});
test('Delete Tag so the relation in the Reference side should be removed', async () => {

4629
yarn.lock

File diff suppressed because it is too large Load Diff