Merge branch 'develop' into features/media-lib

This commit is contained in:
Alexandre Bodin 2020-03-13 15:40:11 +01:00
commit 243085dae1
10 changed files with 270 additions and 61 deletions

View File

@ -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

View File

@ -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);
}
});

View File

@ -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', () => {

View File

@ -43,7 +43,6 @@ const Wrapper = styled.tr`
}}
p {
font-weight: 500;
text-transform: capitalize;
}
}
td:last-child {

View File

@ -35,5 +35,6 @@ module.exports = (obj, validNatures) => {
.test(isValidName)
.nullable(),
targetColumnName: yup.string().nullable(),
private: yup.boolean().nullable(),
};
};

View File

@ -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,
};

View File

@ -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;
},

View File

@ -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];

View File

@ -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,
},
},
});
});
});
});

View File

@ -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();
}