Skip to content

Commit 1d1631c

Browse files
authored
fix(condo): DOMA-6453 The publishedAt field was added (#3493)
* fix(condo): DOMA-6453 The `publishedAt` field was added Also throw an error on trying to publish news item without any scopes * fix(condo): DOMA-6453 Fix roles permissions according to requirements
1 parent da9ce71 commit 1d1631c

File tree

11 files changed

+216
-13
lines changed

11 files changed

+216
-13
lines changed

apps/condo/domains/news/constants/errors.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const EDIT_DENIED_ALREADY_SENT = 'EDIT_DENIED_ALREADY_SENT'
55
const EDIT_DENIED_PUBLISHED = 'EDIT_DENIED_PUBLISHED'
66
const PROFANITY_DETECTED_MOT_ERF_KER = 'PROFANITY_DETECTED_MOT_ERF_KER'
77
const WRONG_SEND_DATE = 'WRONG_SEND_DATE'
8+
const NO_NEWS_ITEM_SCOPES = 'NO_NEWS_ITEM_SCOPES'
89

910
module.exports = {
1011
EMPTY_VALID_BEFORE_DATE,
@@ -14,4 +15,5 @@ module.exports = {
1415
EDIT_DENIED_PUBLISHED,
1516
PROFANITY_DETECTED_MOT_ERF_KER,
1617
WRONG_SEND_DATE,
18+
NO_NEWS_ITEM_SCOPES,
1719
}

apps/condo/domains/news/gql.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const { ADDRESS_META_SUBFIELDS_TABLE_LIST } = require('@condo/domains/property/s
1212

1313
const COMMON_FIELDS = 'id dv sender { dv fingerprint } v deletedAt newId createdBy { id name } updatedBy { id name } createdAt updatedAt'
1414

15-
const NEWS_ITEM_FIELDS = `{ organization { id } number title body type validBefore sendAt sentAt isPublished ${COMMON_FIELDS} }`
15+
const NEWS_ITEM_FIELDS = `{ organization { id } number title body type validBefore sendAt sentAt isPublished publishedAt ${COMMON_FIELDS} }`
1616
const NewsItem = generateGqlQueries('NewsItem', NEWS_ITEM_FIELDS)
1717

1818
const NEWS_ITEM_SCOPE_FIELDS = `{ type newsItem { id organization { id } number isPublished } property { id address addressMeta { ${ADDRESS_META_SUBFIELDS_TABLE_LIST} } } unitType unitName ${COMMON_FIELDS} }`

apps/condo/domains/news/schema/NewsItem.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ const {
2222
EDIT_DENIED_PUBLISHED,
2323
PROFANITY_DETECTED_MOT_ERF_KER,
2424
WRONG_SEND_DATE,
25+
NO_NEWS_ITEM_SCOPES,
2526
} = require('@condo/domains/news/constants/errors')
2627
const { NEWS_TYPES, NEWS_TYPE_EMERGENCY, NEWS_TYPE_COMMON } = require('@condo/domains/news/constants/newsTypes')
2728
const { notifyResidentsAboutNewsItem } = require('@condo/domains/news/tasks')
29+
const { NewsItemScope } = require('@condo/domains/news/utils/serverSchema')
2830

2931
const badWords = new BadWordsNext()
3032
badWords.add(badWordsRu)
@@ -72,6 +74,12 @@ const ERRORS = {
7274
message: 'Wrong send date',
7375
messageForUser: 'api.newsItem.WRONG_SEND_DATE',
7476
},
77+
NO_NEWS_ITEM_SCOPES: {
78+
code: BAD_USER_INPUT,
79+
type: NO_NEWS_ITEM_SCOPES,
80+
message: 'The news item without scopes publishing is forbidden',
81+
messageForUser: 'api.newsItem.NO_NEWS_ITEM_SCOPES',
82+
},
7583
}
7684

7785
const readOnlyFieldsWhenPublished = ['organization', 'title', 'body', 'type', 'sendAt']
@@ -138,7 +146,7 @@ const NewsItem = new GQLListSchema('NewsItem', {
138146
},
139147

140148
sentAt: {
141-
schemaDoc: 'The date when newsItem was sent to residents',
149+
schemaDoc: 'The date when newsItem was sent to residents. This is an internal field used to detect was the message has already been sent or not.',
142150
type: 'DateTimeUtc',
143151
},
144152

@@ -148,6 +156,21 @@ const NewsItem = new GQLListSchema('NewsItem', {
148156
defaultValue: false,
149157
},
150158

159+
publishedAt: {
160+
schemaDoc: 'The date when the news item was published. It is an auto-Calculated field.',
161+
type: 'DateTimeUtc',
162+
hooks: {
163+
resolveInput: ({ resolvedData, fieldPath }) => (
164+
resolvedData['isPublished'] ? dayjs().toISOString() : resolvedData[fieldPath]
165+
),
166+
},
167+
access: {
168+
read: true,
169+
create: false,
170+
update: false,
171+
},
172+
},
173+
151174
},
152175
hooks: {
153176
resolveInput: async (args) => {
@@ -166,11 +189,18 @@ const NewsItem = new GQLListSchema('NewsItem', {
166189

167190
const sendAt = get(resolvedData, 'sendAt')
168191
const sentAt = get(existingItem, 'sentAt')
169-
const validBefore = get(resolvedData, 'validBefore')
170192
const resultSendAt = get(resultItemData, 'sendAt')
171193
const resultValidBefore = get(resultItemData, 'validBefore')
172194
const isPublished = get(existingItem, 'isPublished')
173195
const type = get(resultItemData, 'type')
196+
const resolvedIsPublished = get(resolvedData, 'isPublished')
197+
198+
if (resolvedIsPublished) {
199+
const scopesCount = existingItem ? await NewsItemScope.count(context, { newsItem: { id: existingItem.id } }) : 0
200+
if (scopesCount === 0) {
201+
throw new GQLError(ERRORS.NO_NEWS_ITEM_SCOPES, context)
202+
}
203+
}
174204

175205
if (operation === 'update') {
176206
if (sentAt) {

apps/condo/domains/news/schema/NewsItem.test.js

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const {
1818
expectToThrowAuthenticationErrorToObjects,
1919
expectToThrowAccessDeniedErrorToObj,
2020
expectToThrowAccessDeniedToFieldError,
21+
catchErrorFrom,
2122
} = require('@open-condo/keystone/test.utils')
2223

2324
const { SENDING_DELAY_SEC } = require('@condo/domains/news/constants/common')
@@ -153,6 +154,16 @@ describe('NewsItems', () => {
153154
expect(obj13.number).toEqual(3)
154155
expect(obj21.number).toEqual(1)
155156
})
157+
158+
test('The publishedAt field must be not empty after publishing', async () => {
159+
const [newsItem] = await createTestNewsItem(adminClient, dummyO10n)
160+
await createTestNewsItemScope(adminClient, newsItem)
161+
162+
const [newsItemPublished] = await publishTestNewsItem(adminClient, newsItem.id)
163+
164+
expect(newsItem.publishedAt).toBeNull()
165+
expect(newsItemPublished.publishedAt).not.toBeNull()
166+
})
156167
})
157168

158169
describe('update', () => {
@@ -238,6 +249,7 @@ describe('NewsItems', () => {
238249

239250
test('user can successfully un-publish news item', async () => {
240251
const [objCreated] = await createTestNewsItem(adminClient, dummyO10n)
252+
await createTestNewsItemScope(adminClient, objCreated)
241253
await publishTestNewsItem(adminClient, objCreated.id)
242254
const [objUnpublished] = await updateTestNewsItem(adminClient, objCreated.id, { isPublished: false })
243255

@@ -811,7 +823,9 @@ describe('NewsItems', () => {
811823
})
812824

813825
test('must throw an error on trying to edit the published news item', async () => {
814-
const [sentNewsItem] = await createTestNewsItem(adminClient, dummyO10n, { isPublished: true })
826+
const [sentNewsItem] = await createTestNewsItem(adminClient, dummyO10n)
827+
await createTestNewsItemScope(adminClient, sentNewsItem)
828+
await publishTestNewsItem(adminClient, sentNewsItem.id)
815829
await expectToThrowGQLError(
816830
async () => await updateTestNewsItem(adminClient, sentNewsItem.id, { title: faker.lorem.words(3) }),
817831
{
@@ -867,6 +881,26 @@ describe('NewsItems', () => {
867881
},
868882
)
869883
})
884+
885+
test('must throw an error on trying to publish news item without scopes', async () => {
886+
const errorFieldsToCheck = {
887+
code: 'BAD_USER_INPUT',
888+
type: 'NO_NEWS_ITEM_SCOPES',
889+
message: 'The news item without scopes publishing is forbidden',
890+
messageForUser: 'api.newsItem.NO_NEWS_ITEM_SCOPES',
891+
}
892+
893+
await expectToThrowGQLError(
894+
async () => await createTestNewsItem(adminClient, dummyO10n, { isPublished: true }),
895+
errorFieldsToCheck,
896+
)
897+
898+
const [newsItem] = await createTestNewsItem(adminClient, dummyO10n)
899+
await expectToThrowGQLError(
900+
async () => await publishTestNewsItem(adminClient, newsItem.id),
901+
errorFieldsToCheck,
902+
)
903+
})
870904
})
871905

872906
describe('Delayed news items', () => {
@@ -1013,5 +1047,28 @@ describe('NewsItems', () => {
10131047
}, { delay: (SENDING_DELAY_SEC + 3) * 1000 })
10141048
})
10151049
})
1050+
1051+
describe('access', () => {
1052+
test('error on trying to edit publishedAt field', async () => {
1053+
const [newsItem] = await createTestNewsItem(adminClient, dummyO10n)
1054+
await createTestNewsItemScope(adminClient, newsItem)
1055+
await catchErrorFrom(
1056+
async () => await updateTestNewsItem(adminClient, newsItem.id, { publishedAt: dayjs().toISOString() }),
1057+
(caught) => {
1058+
expect(caught).toMatchObject({
1059+
name: 'TestClientResponseError',
1060+
message: expect.stringContaining('Test client caught GraphQL response with not empty errors body!'),
1061+
errors: expect.arrayContaining([
1062+
expect.objectContaining({
1063+
name: 'UserInputError',
1064+
message: expect.stringContaining('Field "publishedAt" is not defined by type "NewsItemUpdateInput"'),
1065+
extensions: expect.objectContaining({ code: 'BAD_USER_INPUT' }),
1066+
}),
1067+
]),
1068+
})
1069+
},
1070+
)
1071+
})
1072+
})
10161073
})
10171074

apps/condo/domains/news/schema/NewsItemScope.test.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ const {
2727
createTestNewsItemScope,
2828
updateTestNewsItemScope,
2929
createTestNewsItem,
30+
publishTestNewsItem,
31+
updateTestNewsItem,
3032
} = require('@condo/domains/news/utils/testSchema')
3133
const {
3234
createTestOrganizationEmployeeRole,
@@ -284,7 +286,9 @@ describe('NewsItemScope', () => {
284286
})
285287

286288
test('must throw an error on trying to create/edit scope for published news item', async () => {
287-
const [newsItem] = await createTestNewsItem(adminClient, dummyO10n, { isPublished: true })
289+
const [newsItem] = await createTestNewsItem(adminClient, dummyO10n)
290+
await createTestNewsItemScope(adminClient, newsItem)
291+
await publishTestNewsItem(adminClient, newsItem.id)
288292

289293
await expectToThrowGQLError(
290294
async () => await createTestNewsItemScope(adminClient, newsItem, { property: { connect: { id: dummyProperty.id } } }),
@@ -298,10 +302,12 @@ describe('NewsItemScope', () => {
298302
})
299303

300304
test('must throw an error on trying to create/edit scope for sent news item', async () => {
301-
const [newsItem] = await createTestNewsItem(adminClient, dummyO10n, {
302-
isPublished: true,
303-
sentAt: dayjs().toISOString(),
304-
})
305+
const [newsItem] = await createTestNewsItem(adminClient, dummyO10n)
306+
await createTestNewsItemScope(adminClient, newsItem)
307+
await publishTestNewsItem(adminClient, newsItem.id)
308+
309+
// Emulate sending
310+
await updateTestNewsItem(adminClient, newsItem.id, { sentAt: dayjs().toISOString() })
305311

306312
await expectToThrowGQLError(
307313
async () => await createTestNewsItemScope(adminClient, newsItem, { property: { connect: { id: dummyProperty.id } } }),

apps/condo/domains/organization/constants/common.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,6 @@ const DEFAULT_ROLES = {
8383
'canManageBankTransactions': false,
8484
'canManageBankAccountReports': false,
8585
'ticketVisibilityType': PROPERTY_TICKET_VISIBILITY,
86-
'canManageNewsItems': true,
87-
'canManageNewsItemTemplates': true,
8886
},
8987
'Manager': {
9088
'name': 'employee.role.Manager.name',

apps/condo/lang/en/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1716,6 +1716,7 @@
17161716
"api.newsItem.VALIDITY_DATE_LESS_THAN_SEND_DATE": "The validity date is less than send date",
17171717
"api.newsItem.PROFANITY_DETECTED_MOT_ERF_KER": "Profanity detected",
17181718
"api.newsItem.WRONG_SEND_DATE": "Wrong send date",
1719+
"api.newsItem.NO_NEWS_ITEM_SCOPES": "Scope-less news item publishing is forbidden",
17191720
"api.ticket.QUALITY_CONTROL_ADDITIONAL_OPTION_DOES_NOT_MATCH_QUALITY_CONTROL_VALUE": "Additional quality control parameters must match the specified scoring",
17201721
"api.ticket.QUALITY_CONTROL_VALUE_MUST_BE_SPECIFIED": "When specifying a comment or additional evaluation parameters, you must specify the evaluation itself",
17211722
"api.billing.sendNewReceiptMessagesToResidentScopes.INVALID_PERIOD_PROVIDED": "Please provide proper period not before start of previous and not later than start of current month, valid format is YYYY-MM-DD",

apps/condo/lang/ru/ru.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1716,6 +1716,7 @@
17161716
"api.newsItem.VALIDITY_DATE_LESS_THAN_SEND_DATE": "Дата актуальности меньше чем дата отправки",
17171717
"api.newsItem.PROFANITY_DETECTED_MOT_ERF_KER": "Обнаружена ненормативная лексика",
17181718
"api.newsItem.WRONG_SEND_DATE": "Неверная дата отправки",
1719+
"api.newsItem.NO_NEWS_ITEM_SCOPES": "Нельзя публиковать новость без получателей",
17191720
"api.ticket.QUALITY_CONTROL_ADDITIONAL_OPTION_DOES_NOT_MATCH_QUALITY_CONTROL_VALUE": "Дополнительные параметры контроля качества должны соответсоввать указанной оценке",
17201721
"api.ticket.QUALITY_CONTROL_VALUE_MUST_BE_SPECIFIED": "При указании комментария или дополнительных параметров оценки нужно указать саму оценку",
17211722
"api.billing.sendNewReceiptMessagesToResidentScopes.INVALID_PERIOD_PROVIDED": "Необходимо передать период не ранее начала прошлого месяца и не позднее начала текущего месяца, валидный формат - YYYY-MM-DD",
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// auto generated by kmigrator
2+
// KMIGRATOR:0287_newsitem_publishedat_and_more:IyBHZW5lcmF0ZWQgYnkgRGphbmdvIDQuMC4xIG9uIDIwMjMtMDYtMjIgMDQ6MzkKCmZyb20gZGphbmdvLmRiIGltcG9ydCBtaWdyYXRpb25zLCBtb2RlbHMKCgpjbGFzcyBNaWdyYXRpb24obWlncmF0aW9ucy5NaWdyYXRpb24pOgoKICAgIGRlcGVuZGVuY2llcyA9IFsKICAgICAgICAoJ19kamFuZ29fc2NoZW1hJywgJzAyODZfbmV3c2l0ZW1zY29wZV90eXBlX25ld3NpdGVtc2NvcGVoaXN0b3J5cmVjb3JkX3R5cGUnKSwKICAgIF0KCiAgICBvcGVyYXRpb25zID0gWwogICAgICAgIG1pZ3JhdGlvbnMuQWRkRmllbGQoCiAgICAgICAgICAgIG1vZGVsX25hbWU9J25ld3NpdGVtJywKICAgICAgICAgICAgbmFtZT0ncHVibGlzaGVkQXQnLAogICAgICAgICAgICBmaWVsZD1tb2RlbHMuRGF0ZVRpbWVGaWVsZChibGFuaz1UcnVlLCBudWxsPVRydWUpLAogICAgICAgICksCiAgICAgICAgbWlncmF0aW9ucy5BZGRGaWVsZCgKICAgICAgICAgICAgbW9kZWxfbmFtZT0nbmV3c2l0ZW1oaXN0b3J5cmVjb3JkJywKICAgICAgICAgICAgbmFtZT0ncHVibGlzaGVkQXQnLAogICAgICAgICAgICBmaWVsZD1tb2RlbHMuRGF0ZVRpbWVGaWVsZChibGFuaz1UcnVlLCBudWxsPVRydWUpLAogICAgICAgICksCiAgICBdCg==
3+
4+
exports.up = async (knex) => {
5+
await knex.raw(`
6+
BEGIN;
7+
--
8+
-- Add field publishedAt to newsitem
9+
--
10+
ALTER TABLE "NewsItem" ADD COLUMN "publishedAt" timestamp with time zone NULL;
11+
12+
13+
14+
----- MANUAL
15+
update "NewsItem" set "publishedAt"="NewsItem"."updatedAt" where "isPublished"=true and "publishedAt" is null;
16+
17+
UPDATE "OrganizationEmployeeRole"
18+
SET "canManageOrganizationNews" = false
19+
WHERE "name" in ('employee.role.Dispatcher.name');
20+
21+
UPDATE "OrganizationEmployeeRole"
22+
SET "canManageNewsItemTemplates" = false
23+
WHERE "name" in ('employee.role.Dispatcher.name');
24+
-----
25+
26+
27+
28+
--
29+
-- Add field publishedAt to newsitemhistoryrecord
30+
--
31+
ALTER TABLE "NewsItemHistoryRecord" ADD COLUMN "publishedAt" timestamp with time zone NULL;
32+
COMMIT;
33+
34+
`)
35+
}
36+
37+
exports.down = async (knex) => {
38+
await knex.raw(`
39+
BEGIN;
40+
--
41+
-- Add field publishedAt to newsitemhistoryrecord
42+
--
43+
ALTER TABLE "NewsItemHistoryRecord" DROP COLUMN "publishedAt" CASCADE;
44+
--
45+
-- Add field publishedAt to newsitem
46+
--
47+
ALTER TABLE "NewsItem" DROP COLUMN "publishedAt" CASCADE;
48+
COMMIT;
49+
50+
`)
51+
}

0 commit comments

Comments
 (0)