mirror of
https://github.com/strapi/strapi.git
synced 2025-09-25 08:19:07 +00:00
Merge branch 'develop' into features/media-lib
This commit is contained in:
commit
243085dae1
@ -27,7 +27,7 @@ To create a project head over to the Strapi [listing on the marketplace](https:/
|
||||
|
||||
### Step 3: Visit your app
|
||||
|
||||
Please note that it may take anywhere from 30 seconds to a few minutes for the droplet to startup, when it does you should see it in your [droplets list](https://cloud.digitalocean.com/droplets).
|
||||
Please note that it may take anywhere from 30 seconds to a few minutes for the droplet to startup, when it does you should see it in your [droplets list](https://cloud.digitalocean.com/droplets). After the droplet has started, it will take a few more minutes to finish the Strapi installation.
|
||||
|
||||
From here you will see the public ipv4 address that you can use to visit your Strapi application, just open that in a browser and it should ask you to create your first administrator!
|
||||
|
||||
@ -101,7 +101,7 @@ upstream strapi {
|
||||
|
||||
### Strapi
|
||||
|
||||
In the DigitalOcean one-click application a service user is used in which it's home directory is located at `/srv/strapi`. Likewise the actual Strapi application is located within this home directory at `/srv/strapi/strapi`.
|
||||
In the DigitalOcean one-click application a service user is used in which it's home directory is located at `/srv/strapi`. Likewise the actual Strapi application is located within this home directory at `/srv/strapi/strapi-development`.
|
||||
|
||||
Please note that with this application it is intially created and ran in the `development` environment to allow for creating models. **You should not use this directly in production**, it is recommended that you configure a private git repository to commit changes into and create a new application directory within the service user's home (Example: `/srv/strapi/strapi-production`). To run the new `production` or `staging` environments you can refer to the [PM2 Documentation](https://pm2.keymetrics.io/docs/usage/quick-start/#managing-processes).
|
||||
|
||||
@ -137,8 +137,6 @@ Strapi will automatically start if the virtual machine is rebooted, you can also
|
||||
|
||||
## Changing the PostgreSQL Password
|
||||
|
||||
Because of how the virtual machine is created, your database is setup with a long and random password, however for security you should change this password before moving into a production-like setting.
|
||||
|
||||
Use the following steps to change the PostgreSQL password and update Strapi's config:
|
||||
|
||||
- Make sure you are logged into the `strapi` service user
|
||||
|
@ -661,9 +661,10 @@ module.exports = ({ models, target }, ctx) => {
|
||||
|
||||
await createComponentJoinTables({ definition, ORM });
|
||||
} catch (err) {
|
||||
strapi.log.error(`Impossible to register the '${model}' model.`);
|
||||
strapi.log.error(err);
|
||||
strapi.stop();
|
||||
if (err instanceof TypeError || err instanceof ReferenceError) {
|
||||
strapi.stopWithError(err, `Impossible to register the '${model}' model.`);
|
||||
}
|
||||
strapi.stopWithError(err);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -18,9 +18,9 @@ const uploadImg = () => {
|
||||
describe.each([
|
||||
[
|
||||
'CONTENT MANAGER',
|
||||
'/content-manager/explorer/application::withdynamiczone.withdynamiczone',
|
||||
'/content-manager/explorer/application::withdynamiczonemedia.withdynamiczonemedia',
|
||||
],
|
||||
['GENERATED API', '/withdynamiczones'],
|
||||
['GENERATED API', '/withdynamiczonemedias'],
|
||||
])('[%s] => Not required dynamiczone', (_, path) => {
|
||||
beforeAll(async () => {
|
||||
const token = await registerAndLogin();
|
||||
@ -61,17 +61,9 @@ describe.each([
|
||||
},
|
||||
});
|
||||
|
||||
await modelsUtils.createContentTypeWithType(
|
||||
'withdynamiczone',
|
||||
'dynamiczone',
|
||||
{
|
||||
components: [
|
||||
'default.single-media',
|
||||
'default.multiple-media',
|
||||
'default.with-nested',
|
||||
],
|
||||
}
|
||||
);
|
||||
await modelsUtils.createContentTypeWithType('withdynamiczonemedia', 'dynamiczone', {
|
||||
components: ['default.single-media', 'default.multiple-media', 'default.with-nested'],
|
||||
});
|
||||
|
||||
rq = authRq.defaults({
|
||||
baseUrl: `http://localhost:1337${path}`,
|
||||
@ -82,7 +74,7 @@ describe.each([
|
||||
await modelsUtils.deleteComponent('default.with-nested');
|
||||
await modelsUtils.deleteComponent('default.single-media');
|
||||
await modelsUtils.deleteComponent('default.multiple-media');
|
||||
await modelsUtils.deleteContentType('withdynamiczone');
|
||||
await modelsUtils.deleteContentType('withdynamiczonemedia');
|
||||
}, 60000);
|
||||
|
||||
describe('Contains components with medias', () => {
|
||||
|
@ -43,7 +43,6 @@ const Wrapper = styled.tr`
|
||||
}}
|
||||
p {
|
||||
font-weight: 500;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
}
|
||||
td:last-child {
|
||||
|
@ -35,5 +35,6 @@ module.exports = (obj, validNatures) => {
|
||||
.test(isValidName)
|
||||
.nullable(),
|
||||
targetColumnName: yup.string().nullable(),
|
||||
private: yup.boolean().nullable(),
|
||||
};
|
||||
};
|
||||
|
@ -115,12 +115,14 @@ function createSchemaBuilder({ components, contentTypes }) {
|
||||
columnName,
|
||||
dominant,
|
||||
autoPopulate,
|
||||
private: isPrivate,
|
||||
} = attribute;
|
||||
|
||||
const attr = {
|
||||
unique: unique === true ? true : undefined,
|
||||
columnName: columnName || undefined,
|
||||
configurable: configurable === false ? false : undefined,
|
||||
private: isPrivate === true ? true : undefined,
|
||||
autoPopulate,
|
||||
};
|
||||
|
||||
|
@ -243,7 +243,7 @@ module.exports = {
|
||||
|
||||
const inputs = `
|
||||
input ${inputName} {
|
||||
|
||||
|
||||
${Object.keys(model.attributes)
|
||||
.map(attributeName => {
|
||||
return `${attributeName}: ${this.convertType({
|
||||
@ -271,6 +271,7 @@ module.exports = {
|
||||
.join('\n')}
|
||||
}
|
||||
`;
|
||||
|
||||
return inputs;
|
||||
},
|
||||
|
||||
|
@ -55,6 +55,7 @@ const buildTypeDefObj = model => {
|
||||
// Change field definition for collection relations
|
||||
associations
|
||||
.filter(association => association.type === 'collection')
|
||||
.filter(association => attributes[association.alias].private !== true)
|
||||
.forEach(association => {
|
||||
typeDef[`${association.alias}(sort: String, limit: Int, start: Int, where: JSON)`] =
|
||||
typeDef[association.alias];
|
||||
|
@ -44,6 +44,41 @@ const labelModel = {
|
||||
collectionName: '',
|
||||
};
|
||||
|
||||
const carModel = {
|
||||
attributes: {
|
||||
name: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
connection: 'default',
|
||||
name: 'car',
|
||||
description: '',
|
||||
collectionName: '',
|
||||
};
|
||||
|
||||
const personModel = {
|
||||
attributes: {
|
||||
name: {
|
||||
type: 'text',
|
||||
},
|
||||
privateName: {
|
||||
type: 'text',
|
||||
private: true,
|
||||
},
|
||||
privateCars: {
|
||||
nature: 'oneToMany',
|
||||
target: 'application::car.car',
|
||||
dominant: false,
|
||||
targetAttribute: 'person',
|
||||
private: true,
|
||||
},
|
||||
},
|
||||
connection: 'default',
|
||||
name: 'person',
|
||||
description: '',
|
||||
collectionName: '',
|
||||
};
|
||||
|
||||
describe('Test Graphql Relations API End to End', () => {
|
||||
beforeAll(async () => {
|
||||
const token = await registerAndLogin();
|
||||
@ -59,15 +94,17 @@ describe('Test Graphql Relations API End to End', () => {
|
||||
|
||||
modelsUtils = createModelsUtils({ rq });
|
||||
|
||||
await modelsUtils.createContentTypes([documentModel, labelModel]);
|
||||
await modelsUtils.createContentTypes([documentModel, labelModel, carModel, personModel]);
|
||||
}, 60000);
|
||||
|
||||
afterAll(() => modelsUtils.deleteContentTypes(['document', 'label']), 60000);
|
||||
afterAll(() => modelsUtils.deleteContentTypes(['document', 'label', 'car', 'person']), 60000);
|
||||
|
||||
describe('Test relations features', () => {
|
||||
let data = {
|
||||
labels: [],
|
||||
documents: [],
|
||||
people: [],
|
||||
cars: [],
|
||||
};
|
||||
const labelsPayload = [{ name: 'label 1' }, { name: 'label 2' }];
|
||||
const documentsPayload = [{ name: 'document 1' }, { name: 'document 2' }];
|
||||
@ -127,49 +164,46 @@ describe('Test Graphql Relations API End to End', () => {
|
||||
data.labels = res.body.data.labels;
|
||||
});
|
||||
|
||||
test.each(documentsPayload)(
|
||||
'Create document linked to every labels %o',
|
||||
async document => {
|
||||
const res = await graphqlQuery({
|
||||
query: /* GraphQL */ `
|
||||
mutation createDocument($input: createDocumentInput) {
|
||||
createDocument(input: $input) {
|
||||
document {
|
||||
test.each(documentsPayload)('Create document linked to every labels %o', async document => {
|
||||
const res = await graphqlQuery({
|
||||
query: /* GraphQL */ `
|
||||
mutation createDocument($input: createDocumentInput) {
|
||||
createDocument(input: $input) {
|
||||
document {
|
||||
name
|
||||
labels {
|
||||
id
|
||||
name
|
||||
labels {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
data: {
|
||||
...document,
|
||||
labels: data.labels.map(t => t.id),
|
||||
},
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
data: {
|
||||
...document,
|
||||
labels: data.labels.map(t => t.id),
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const { body } = res;
|
||||
const { body } = res;
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.statusCode).toBe(200);
|
||||
|
||||
expect(body).toMatchObject({
|
||||
data: {
|
||||
createDocument: {
|
||||
document: {
|
||||
...selectFields(document),
|
||||
labels: expect.arrayContaining(data.labels.map(selectFields)),
|
||||
},
|
||||
expect(body).toMatchObject({
|
||||
data: {
|
||||
createDocument: {
|
||||
document: {
|
||||
...selectFields(document),
|
||||
labels: expect.arrayContaining(data.labels.map(selectFields)),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('List documents with labels', async () => {
|
||||
const res = await graphqlQuery({
|
||||
@ -229,9 +263,7 @@ describe('Test Graphql Relations API End to End', () => {
|
||||
labels: expect.arrayContaining(
|
||||
data.labels.map(label => ({
|
||||
...selectFields(label),
|
||||
documents: expect.arrayContaining(
|
||||
data.documents.map(selectFields)
|
||||
),
|
||||
documents: expect.arrayContaining(data.documents.map(selectFields)),
|
||||
}))
|
||||
),
|
||||
},
|
||||
@ -405,5 +437,184 @@ describe('Test Graphql Relations API End to End', () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('Create person', async () => {
|
||||
const person = {
|
||||
name: 'Chuck Norris',
|
||||
privateName: 'Jean-Eude',
|
||||
};
|
||||
const res = await graphqlQuery({
|
||||
query: /* GraphQL */ `
|
||||
mutation createPerson($input: createPersonInput) {
|
||||
createPerson(input: $input) {
|
||||
person {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
data: person,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toEqual({
|
||||
data: {
|
||||
createPerson: {
|
||||
person: {
|
||||
id: expect.anything(),
|
||||
name: person.name,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
data.people.push(res.body.data.createPerson.person);
|
||||
});
|
||||
|
||||
test("Can't list a private field", async () => {
|
||||
const res = await graphqlQuery({
|
||||
query: /* GraphQL */ `
|
||||
{
|
||||
people {
|
||||
name
|
||||
privateName
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body).toMatchObject({
|
||||
errors: [
|
||||
{
|
||||
message: 'Cannot query field "privateName" on type "Person".',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('Create a car linked to a person (oneToMany)', async () => {
|
||||
const car = {
|
||||
name: 'Peugeot 508',
|
||||
person: data.people[0].id,
|
||||
};
|
||||
const res = await graphqlQuery({
|
||||
query: /* GraphQL */ `
|
||||
mutation createCar($input: createCarInput) {
|
||||
createCar(input: $input) {
|
||||
car {
|
||||
id
|
||||
name
|
||||
person {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
data: {
|
||||
...car,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body).toMatchObject({
|
||||
data: {
|
||||
createCar: {
|
||||
car: {
|
||||
id: expect.anything(),
|
||||
name: car.name,
|
||||
person: data.people[0],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
data.cars.push({ id: res.body.data.createCar.car.id });
|
||||
});
|
||||
|
||||
test("Can't list a private oneToMany relation", async () => {
|
||||
const res = await graphqlQuery({
|
||||
query: /* GraphQL */ `
|
||||
{
|
||||
people {
|
||||
name
|
||||
privateCars
|
||||
}
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
expect(res.body).toMatchObject({
|
||||
errors: [
|
||||
{
|
||||
message: 'Cannot query field "privateCars" on type "Person".',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('Edit person/cars relations removes correctly a car', async () => {
|
||||
const newPerson = {
|
||||
name: 'Check Norris Junior',
|
||||
privateCars: [],
|
||||
};
|
||||
|
||||
const mutationRes = await graphqlQuery({
|
||||
query: /* GraphQL */ `
|
||||
mutation updatePerson($input: updatePersonInput) {
|
||||
updatePerson(input: $input) {
|
||||
person {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
input: {
|
||||
where: {
|
||||
id: data.people[0].id,
|
||||
},
|
||||
data: {
|
||||
...newPerson,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mutationRes.statusCode).toBe(200);
|
||||
|
||||
const queryRes = await graphqlQuery({
|
||||
query: /* GraphQL */ `
|
||||
query($id: ID!) {
|
||||
car(id: $id) {
|
||||
person {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: data.cars[0].id,
|
||||
},
|
||||
});
|
||||
expect(queryRes.statusCode).toBe(200);
|
||||
expect(queryRes.body).toEqual({
|
||||
data: {
|
||||
car: {
|
||||
person: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -273,8 +273,11 @@ class Strapi extends EventEmitter {
|
||||
};
|
||||
}
|
||||
|
||||
stopWithError(err) {
|
||||
stopWithError(err, customMessage) {
|
||||
this.log.debug(`⛔️ Server wasn't able to start properly.`);
|
||||
if (customMessage) {
|
||||
this.log.error(customMessage);
|
||||
}
|
||||
this.log.error(err);
|
||||
return this.stop();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user