Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Refactor Voter application layout #619

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,14 @@
*/

import {factories} from '@strapi/strapi';
import {restrictPopulate, restrictFilters} from '../../../util/acl';
import {restrictFilters} from '../../../util/acl';

export default factories.createCoreRouter('api::app-customization.app-customization', {
only: ['find'],
config: {
find: {
policies: [
// Disable populate by default to avoid accidentally leaking data through relations
restrictPopulate([
'translationOverrides',
'candidateAppFAQ',
'publisherLogo',
'publisherLogoDark',
'poster',
'posterDark',
'candPoster',
'candPosterDark',
]),
// No populate restrictions for appCustomization are needed
// Disable filters by default to avoid accidentally leaking data of relations
restrictFilters([])
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@
"repeatable": false,
"component": "settings.entities",
"required": true
},
"headerStyle": {
"type": "component",
"repeatable": false,
"component": "settings.header-style"
}
}
}
24 changes: 14 additions & 10 deletions backend/vaa-strapi/src/api/app-setting/controllers/app-setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,27 @@ import {getCardContentsFromStrapi} from '../../../functions/utils/appSettings';
export default factories.createCoreController(API.AppSettings, () => ({
async findOne(ctx) {
const result = await super.find(ctx);

for (const [i, val] of result.data.entries()) {
const cardContents = await getCardContentsFromStrapi(val.id);
result.data[i].attributes.results.cardContents = cardContents;
if (result?.data) {
for (const [i, val] of result.data.entries()) {
const cardContents = await getCardContentsFromStrapi(val.id);
if (!cardContents) continue;
result.data[i].attributes.results ??= {};
result.data[i].attributes.results.cardContents = cardContents;
}
}

return result;
},

async find(ctx) {
const {data, meta} = await super.find(ctx);

for (const [i, val] of data.entries()) {
const cardContents = await getCardContentsFromStrapi(val.id);
data[i].attributes.results.cardContents = cardContents;
if (data) {
for (const [i, val] of data.entries()) {
const cardContents = await getCardContentsFromStrapi(val.id);
if (!cardContents) continue;
data[i].attributes.results ??= {};
data[i].attributes.results.cardContents = cardContents;
}
}

return {data, meta};
}
}));
36 changes: 3 additions & 33 deletions backend/vaa-strapi/src/api/app-setting/routes/app-setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,21 @@
*/

import {factories} from '@strapi/strapi';
import {restrictPopulate, restrictFilters} from '../../../util/acl';
import {restrictFilters} from '../../../util/acl';

export default factories.createCoreRouter('api::app-setting.app-setting', {
only: ['find', 'findOne'], // Explicitly disabled create and delete
config: {
find: {
policies: [
// Disable populate by default to avoid accidentally leaking data through relations
restrictPopulate([
'entities',
'header',
'matching',
'survey',
'entityDetails',
'contents',
'showMissingElectionSymbol',
'showMissingAnswers',
'questions',
'categoryIntros',
'questionsIntro',
'results',
'cardContents'
]),
// No populate restrictions for appSettings are needed
// Disable filters by default to avoid accidentally leaking data of relations
restrictFilters([])
]
},
findOne: {
policies: [
// Disable populate by default to avoid accidentally leaking data through relations
restrictPopulate([
'entities',
'header',
'matching',
'survey',
'entityDetails',
'contents',
'showMissingElectionSymbol',
'showMissingAnswers',
'questions',
'categoryIntros',
'questionsIntro',
'results',
'cardContents'
]),
// No populate restrictions for appSettings are needed
// Disable filters by default to avoid accidentally leaking data of relations
restrictFilters([])
]
Expand Down
15 changes: 15 additions & 0 deletions backend/vaa-strapi/src/components/settings/header-style-dark.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"collectionName": "components_settings_header_style_darks",
"info": {
"displayName": "Header Style - Dark"
},
"options": {},
"attributes": {
"bgColor": {
"type": "string"
},
"overImgBgColor": {
"type": "string"
}
}
}
15 changes: 15 additions & 0 deletions backend/vaa-strapi/src/components/settings/header-style-light.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"collectionName": "components_settings_header_style_lights",
"info": {
"displayName": "Header Style - Light"
},
"options": {},
"attributes": {
"bgColor": {
"type": "string"
},
"overImgBgColor": {
"type": "string"
}
}
}
27 changes: 27 additions & 0 deletions backend/vaa-strapi/src/components/settings/header-style.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"collectionName": "components_settings_header_styles",
"info": {
"displayName": "Header Style"
},
"options": {},
"attributes": {
"dark": {
"displayName": "Header Style - Dark",
"type": "component",
"repeatable": false,
"component": "settings.header-style-dark"
},
"light": {
"displayName": "Header Style - Light",
"type": "component",
"repeatable": false,
"component": "settings.header-style-light"
},
"imgSize": {
"type": "string"
},
"imgPosition": {
"type": "string"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
},
"x-generation-date": "2024-10-08T10:22:54.399Z"
"x-generation-date": "2024-10-20T07:50:50.713Z"
},
"x-strapi-config": {
"path": "/documentation",
Expand Down Expand Up @@ -3948,6 +3948,9 @@
"entities": {
"$ref": "#/components/schemas/SettingsEntitiesComponent"
},
"headerStyle": {
"$ref": "#/components/schemas/SettingsHeaderStyleComponent"
},
"createdAt": {
"type": "string",
"format": "date-time"
Expand Down Expand Up @@ -4497,6 +4500,48 @@
}
}
},
"SettingsHeaderStyleComponent": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"dark": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"bgColor": {
"type": "string"
},
"overImgBgColor": {
"type": "string"
}
}
},
"light": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"bgColor": {
"type": "string"
},
"overImgBgColor": {
"type": "string"
}
}
},
"imgSize": {
"type": "string"
},
"imgPosition": {
"type": "string"
}
}
},
"CandidateRequest": {
"type": "object",
"required": [
Expand Down
49 changes: 41 additions & 8 deletions backend/vaa-strapi/src/functions/utils/appSettings.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,36 @@
import {type DynamicSettings, dynamicSettings} from 'vaa-shared';
import {type DynamicSettings, dynamicSettings, QuestionInCardContent} from 'vaa-shared';
import {API} from './api';

/**
* Gets `results.cardContents` from `dynamicSettings.ts` and returns them in format used in Strapi
*/
export function getCardContentsFromFile() {
export function getCardContentsFromFile(): {
candidateCardContents: Array<
| {
content: 'submatches';
}
| {
content: 'question';
question_id: QuestionInCardContent['question'];
question_hideLabel?: QuestionInCardContent['hideLabel'];
question_format?: QuestionInCardContent['format'];
}
>;
partyCardContents: Array<
| {
content: 'submatches';
}
| {
content: 'candidates';
}
| {
content: 'question';
question_id: QuestionInCardContent['question'];
question_hideLabel?: QuestionInCardContent['hideLabel'];
question_format?: QuestionInCardContent['format'];
}
>;
} {
const candidateCardContents = [];
dynamicSettings.results.cardContents.candidate.forEach((item) => {
if (item === 'submatches') {
Expand Down Expand Up @@ -36,15 +62,21 @@ export function getCardContentsFromFile() {
}

/**
* Gets `cardContents` from Strapi and returns them in format used in `DynamicSettings`
* Gets `cardContents` from Strapi and returns them in format used in `DynamicSettings`. The returned values are `null` if not defined.
*/
export async function getCardContentsFromStrapi(id: number) {
export async function getCardContentsFromStrapi(id: number): Promise<{
candidate: DynamicSettings['results']['cardContents']['candidate'] | null;
party: DynamicSettings['results']['cardContents']['party'] | null;
} | null> {
const appSettings = await strapi.entityService.findOne(API.AppSettings, id, {
populate: ['results', 'results.candidateCardContents', 'results.partyCardContents']
});

const candidateCardContents: DynamicSettings['results']['cardContents']['candidate'] = [];
appSettings.results.candidateCardContents.forEach((item) => {
if (!appSettings?.results) return null;

let candidateCardContents: DynamicSettings['results']['cardContents']['candidate'] | null = null;
appSettings.results?.candidateCardContents?.forEach((item) => {
candidateCardContents ??= [];
if (item.content === 'submatches') {
candidateCardContents.push(item.content);
} else {
Expand All @@ -56,8 +88,9 @@ export async function getCardContentsFromStrapi(id: number) {
}
});

const partyCardContents: DynamicSettings['results']['cardContents']['party'] = [];
appSettings.results.partyCardContents.forEach((item) => {
let partyCardContents: DynamicSettings['results']['cardContents']['party'] | null = null;
appSettings.results?.partyCardContents?.forEach((item) => {
partyCardContents ??= [];
if (item.content === 'submatches' || item.content === 'candidates') {
partyCardContents.push(item.content);
} else {
Expand Down
9 changes: 4 additions & 5 deletions docs/settings-and-customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@ In case of dynamic settings:
3. Edit the settings components in Strapi:
1. If the new setting is a top-level one, create a new component for the setting and add it to the `App Settings` content-type.
2. If the new setting is a subsetting of a top-level item, edit that setting.
4. Edit the populate restrictions for the [app-settings route](backend/vaa-strapi/src/api/app-setting/routes/app-setting.ts) so that the new component is allowed to be populated both for `find` and `findOne`.
5. Update the Strapi data types for `StrapiAppSettingsData` in [strapiDataProvider.type.ts](../frontend/src/lib/api/dataProvider/strapi/strapiDataProvider.type.ts)
6. Add the necessary `populate` query params to the `getAppSettings` method in [strapiDataProvider.ts](../frontend/src/lib/api/dataProvider/strapi/strapiDataProvider.ts), because components need to be explicitly populated.
7. If the data type for the setting does not match the one in the `DynamicSettings` type:
4. Update the Strapi data types for `StrapiAppSettingsData` in [strapiDataProvider.type.ts](../frontend/src/lib/api/dataProvider/strapi/strapiDataProvider.type.ts)
5. Add the necessary `populate` query params to the `getAppSettings` method in [strapiDataProvider.ts](../frontend/src/lib/api/dataProvider/strapi/strapiDataProvider.ts), because components need to be explicitly populated. Note that if the components have subcomponents, you need to explicitly populate all the way down.
6. If the data type for the setting does not match the one in the `DynamicSettings` type:
1. Edit the `getAppSettings` method in [strapiDataProvider.ts](../frontend/src/lib/api/dataProvider/strapi/strapiDataProvider.ts) so that it returns the setting in the correct format.
2. Edit the [loadDefaultAppSettings](backend/vaa-strapi/src/functions/loadDefaultAppSettings.ts) utility so that it converts the default settings to a format suitable for Strapi.
8. Repeat steps 3–5 for all other `DataProvider` implementations that support `getAppSettings`.
7. Repeat applicable steps for all other `DataProvider` implementations that support `getAppSettings`.

# App Customization

Expand Down
17 changes: 10 additions & 7 deletions frontend/src/lib/api/dataProvider/strapi/strapiDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,13 @@ function getData<T extends object>(
return fetch(url)
.then((response) => {
return response.json().then((parsed: StrapiResponse<T> | StrapiError) => {
if ('error' in parsed) throw new Error(`Error with getData: ${parsed?.error?.message}`);
if ('error' in parsed)
throw new Error(`Error with getData: ${parsed?.error?.message} • ${url}`);
return parsed.data;
});
})
.catch((e) => {
throw new Error(`Error with getData: ${e?.message}`);
throw new Error(`Error with getData: ${e?.message} • ${url}`);
});
}

Expand All @@ -85,16 +86,18 @@ function getData<T extends object>(
*/
function getAppSettings(): Promise<Partial<AppSettings> | undefined> {
const params = new URLSearchParams({
'populate[header]': 'true',
'populate[matching]': 'true',
'populate[survey]': 'true',
'populate[entities][populate][hideIfMissingAnswers]': 'true',
'populate[entityDetails][populate][contents]': 'true',
'populate[entityDetails][populate][showMissingElectionSymbol]': 'true',
'populate[entityDetails][populate][showMissingAnswers]': 'true',
'populate[entityDetails][populate][showMissingElectionSymbol]': 'true',
'populate[header]': 'true',
'populate[headerStyle][populate][dark]': 'true',
'populate[headerStyle][populate][light]': 'true',
'populate[matching]': 'true',
'populate[questions][populate][categoryIntros]': 'true',
'populate[questions][populate][questionsIntro]': 'true',
'populate[results][populate][cardContents]': 'true'
'populate[results][populate][cardContents]': 'true',
'populate[survey]': 'true'
});
return getData<StrapiAppSettingsData[]>('api/app-settings', params)
.then((result) => {
Expand Down
Loading
Loading