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 ### 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! 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 ### 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). 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 ## 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: Use the following steps to change the PostgreSQL password and update Strapi's config:
- Make sure you are logged into the `strapi` service user - 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 }); await createComponentJoinTables({ definition, ORM });
} catch (err) { } catch (err) {
strapi.log.error(`Impossible to register the '${model}' model.`); if (err instanceof TypeError || err instanceof ReferenceError) {
strapi.log.error(err); strapi.stopWithError(err, `Impossible to register the '${model}' model.`);
strapi.stop(); }
strapi.stopWithError(err);
} }
}); });

View File

@ -18,9 +18,9 @@ const uploadImg = () => {
describe.each([ describe.each([
[ [
'CONTENT MANAGER', '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) => { ])('[%s] => Not required dynamiczone', (_, path) => {
beforeAll(async () => { beforeAll(async () => {
const token = await registerAndLogin(); const token = await registerAndLogin();
@ -61,17 +61,9 @@ describe.each([
}, },
}); });
await modelsUtils.createContentTypeWithType( await modelsUtils.createContentTypeWithType('withdynamiczonemedia', 'dynamiczone', {
'withdynamiczone', components: ['default.single-media', 'default.multiple-media', 'default.with-nested'],
'dynamiczone', });
{
components: [
'default.single-media',
'default.multiple-media',
'default.with-nested',
],
}
);
rq = authRq.defaults({ rq = authRq.defaults({
baseUrl: `http://localhost:1337${path}`, baseUrl: `http://localhost:1337${path}`,
@ -82,7 +74,7 @@ describe.each([
await modelsUtils.deleteComponent('default.with-nested'); await modelsUtils.deleteComponent('default.with-nested');
await modelsUtils.deleteComponent('default.single-media'); await modelsUtils.deleteComponent('default.single-media');
await modelsUtils.deleteComponent('default.multiple-media'); await modelsUtils.deleteComponent('default.multiple-media');
await modelsUtils.deleteContentType('withdynamiczone'); await modelsUtils.deleteContentType('withdynamiczonemedia');
}, 60000); }, 60000);
describe('Contains components with medias', () => { describe('Contains components with medias', () => {

View File

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

View File

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

View File

@ -115,12 +115,14 @@ function createSchemaBuilder({ components, contentTypes }) {
columnName, columnName,
dominant, dominant,
autoPopulate, autoPopulate,
private: isPrivate,
} = attribute; } = attribute;
const attr = { const attr = {
unique: unique === true ? true : undefined, unique: unique === true ? true : undefined,
columnName: columnName || undefined, columnName: columnName || undefined,
configurable: configurable === false ? false : undefined, configurable: configurable === false ? false : undefined,
private: isPrivate === true ? true : undefined,
autoPopulate, autoPopulate,
}; };

View File

@ -271,6 +271,7 @@ module.exports = {
.join('\n')} .join('\n')}
} }
`; `;
return inputs; return inputs;
}, },

View File

@ -55,6 +55,7 @@ const buildTypeDefObj = model => {
// Change field definition for collection relations // Change field definition for collection relations
associations associations
.filter(association => association.type === 'collection') .filter(association => association.type === 'collection')
.filter(association => attributes[association.alias].private !== true)
.forEach(association => { .forEach(association => {
typeDef[`${association.alias}(sort: String, limit: Int, start: Int, where: JSON)`] = typeDef[`${association.alias}(sort: String, limit: Int, start: Int, where: JSON)`] =
typeDef[association.alias]; typeDef[association.alias];

View File

@ -44,6 +44,41 @@ const labelModel = {
collectionName: '', 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', () => { describe('Test Graphql Relations API End to End', () => {
beforeAll(async () => { beforeAll(async () => {
const token = await registerAndLogin(); const token = await registerAndLogin();
@ -59,15 +94,17 @@ describe('Test Graphql Relations API End to End', () => {
modelsUtils = createModelsUtils({ rq }); modelsUtils = createModelsUtils({ rq });
await modelsUtils.createContentTypes([documentModel, labelModel]); await modelsUtils.createContentTypes([documentModel, labelModel, carModel, personModel]);
}, 60000); }, 60000);
afterAll(() => modelsUtils.deleteContentTypes(['document', 'label']), 60000); afterAll(() => modelsUtils.deleteContentTypes(['document', 'label', 'car', 'person']), 60000);
describe('Test relations features', () => { describe('Test relations features', () => {
let data = { let data = {
labels: [], labels: [],
documents: [], documents: [],
people: [],
cars: [],
}; };
const labelsPayload = [{ name: 'label 1' }, { name: 'label 2' }]; const labelsPayload = [{ name: 'label 1' }, { name: 'label 2' }];
const documentsPayload = [{ name: 'document 1' }, { name: 'document 2' }]; const documentsPayload = [{ name: 'document 1' }, { name: 'document 2' }];
@ -127,9 +164,7 @@ describe('Test Graphql Relations API End to End', () => {
data.labels = res.body.data.labels; data.labels = res.body.data.labels;
}); });
test.each(documentsPayload)( test.each(documentsPayload)('Create document linked to every labels %o', async document => {
'Create document linked to every labels %o',
async document => {
const res = await graphqlQuery({ const res = await graphqlQuery({
query: /* GraphQL */ ` query: /* GraphQL */ `
mutation createDocument($input: createDocumentInput) { mutation createDocument($input: createDocumentInput) {
@ -168,8 +203,7 @@ describe('Test Graphql Relations API End to End', () => {
}, },
}, },
}); });
} });
);
test('List documents with labels', async () => { test('List documents with labels', async () => {
const res = await graphqlQuery({ const res = await graphqlQuery({
@ -229,9 +263,7 @@ describe('Test Graphql Relations API End to End', () => {
labels: expect.arrayContaining( labels: expect.arrayContaining(
data.labels.map(label => ({ data.labels.map(label => ({
...selectFields(label), ...selectFields(label),
documents: expect.arrayContaining( documents: expect.arrayContaining(data.documents.map(selectFields)),
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.`); this.log.debug(`⛔️ Server wasn't able to start properly.`);
if (customMessage) {
this.log.error(customMessage);
}
this.log.error(err); this.log.error(err);
return this.stop(); return this.stop();
} }