• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hicommonwealth / commonwealth / 13788382186

11 Mar 2025 12:47PM UTC coverage: 44.952% (-0.8%) from 45.755%
13788382186

Pull #11259

github

web-flow
Merge 3caab1019 into 21da2f698
Pull Request #11259: Added in async count cache

1333 of 3320 branches covered (40.15%)

Branch coverage included in aggregate %.

4 of 13 new or added lines in 4 files covered. (30.77%)

172 existing lines in 18 files now uncovered.

2554 of 5327 relevant lines covered (47.94%)

37.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.95
/libs/model/src/quest/UpdateQuest.command.ts
1
import { Command, InvalidInput } from '@hicommonwealth/core';
2
import * as schemas from '@hicommonwealth/schemas';
3
import { AllChannelQuestActionNames } from '@hicommonwealth/schemas';
4
import z from 'zod';
5
import { models } from '../database';
6
import { isSuperAdmin } from '../middleware';
7
import {
8
  mustBeValidDateRange,
9
  mustExist,
10
  mustNotBeStarted,
11
  mustNotExist,
12
} from '../middleware/guards';
13
import {
14
  GraphileTaskNames,
15
  removeJob,
16
  rescheduleJobs,
17
  scheduleTask,
18
} from '../services/graphileWorker';
19
import { getDelta } from '../utils';
20

21
// TODO: use `tweetExists` util to check if `twitter_url` is valid
22
//  once it is added on the client
23
export function UpdateQuest(): Command<typeof schemas.UpdateQuest> {
24
  return {
11✔
25
    ...schemas.UpdateQuest,
26
    auth: [isSuperAdmin],
27
    secure: true,
28
    body: async ({ payload }) => {
29
      const {
30
        quest_id,
31
        name,
32
        description,
33
        community_id,
34
        image_url,
35
        start_date,
36
        end_date,
37
        max_xp_to_end,
38
        action_metas,
39
      } = payload;
11✔
40

41
      const quest = await models.Quest.scope('withPrivateData').findOne({
11✔
42
        where: { id: quest_id },
43
      });
44
      mustExist(`Quest with id "${quest_id}`, quest);
11✔
45

46
      if (name) {
11✔
47
        const existingName = await models.Quest.findOne({
1✔
48
          where: { community_id: community_id ?? null, name },
1!
49
          attributes: ['id'],
50
        });
51
        mustNotExist(
1✔
52
          `Quest named "${name}" in community "${community_id}"`,
53
          existingName,
54
        );
55
      }
56

57
      mustNotBeStarted(start_date ?? quest.start_date);
10✔
58
      mustBeValidDateRange(
9✔
59
        start_date ?? quest.start_date,
18✔
60
        end_date ?? quest.end_date,
17✔
61
      );
62

63
      let channelActionMeta:
64
        | Omit<z.infer<typeof schemas.QuestActionMeta>, 'quest_id'>
65
        | undefined;
66
      if (action_metas) {
9!
67
        if (quest.quest_type === 'channel') {
9!
UNCOV
68
          if (action_metas.length > 1) {
×
UNCOV
69
            throw new InvalidInput(
×
70
              'Cannot have more than one action per channel quest',
71
            );
72
          }
UNCOV
73
          channelActionMeta = action_metas[0];
×
74

UNCOV
75
          if (
×
76
            channelActionMeta &&
×
77
            !AllChannelQuestActionNames.some(
UNCOV
78
              (e) => e === channelActionMeta!.event_name,
×
79
            )
80
          ) {
UNCOV
81
            throw new InvalidInput(
×
82
              `Invalid action "${channelActionMeta.event_name}" for channel quest`,
83
            );
84
          }
85
        }
86

87
        const c_id = community_id || quest.community_id;
9✔
88
        await Promise.all(
9✔
89
          action_metas.map(async (action_meta) => {
90
            if (action_meta.content_id) {
17✔
91
              // make sure content_id exists
92
              const [content, id] = action_meta.content_id.split(':'); // this has been validated by the schema
1✔
93
              if (content === 'topic') {
1!
UNCOV
94
                const topic = await models.Topic.findOne({
×
95
                  where: c_id ? { id: +id, community_id: c_id } : { id: +id },
×
96
                });
UNCOV
97
                mustExist(`Topic with id "${id}"`, topic);
×
98
              } else if (content === 'thread') {
1!
UNCOV
99
                const thread = await models.Thread.findOne({
×
100
                  where: c_id ? { id: +id, community_id: c_id } : { id: +id },
×
101
                });
UNCOV
102
                mustExist(`Thread with id "${id}"`, thread);
×
103
              } else if (content === 'comment') {
1!
104
                const comment = await models.Comment.findOne({
1✔
105
                  where: { id: +id },
106
                  include: c_id
1!
107
                    ? [
108
                        {
109
                          model: models.Thread,
110
                          attributes: ['community_id'],
111
                          required: true,
112
                          where: { community_id: c_id },
113
                        },
114
                      ]
115
                    : [],
116
                });
117
                mustExist(`Comment with id "${id}"`, comment);
1✔
118
              }
119
            }
120
          }),
121
        );
122
      }
123

124
      await models.sequelize.transaction(async (transaction) => {
8✔
125
        // Add scheduled job for new TwitterMetrics action
126
        if (
8!
127
          quest.quest_type === 'channel' &&
8!
128
          channelActionMeta?.event_name === 'TwitterMetrics'
129
        ) {
UNCOV
130
          const job = await scheduleTask(
×
131
            GraphileTaskNames.AwardTwitterQuestXp,
132
            {
133
              quest_id: quest.id!,
134
              quest_end_date: quest.end_date,
135
            },
136
            {
137
              transaction,
138
            },
139
          );
140

UNCOV
141
          quest.scheduled_job_id = job.id;
×
UNCOV
142
          await quest.save({ transaction });
×
143
        }
144

145
        if (action_metas?.length) {
8!
146
          const existingTwitterMetricsAction =
147
            await models.QuestActionMeta.findOne({
8✔
148
              where: {
149
                quest_id,
150
                event_name: 'TwitterMetrics',
151
              },
152
              transaction,
153
            });
154
          if (
8!
155
            existingTwitterMetricsAction &&
8!
156
            !channelActionMeta &&
157
            quest.scheduled_job_id
158
          ) {
UNCOV
159
            await removeJob({
×
160
              jobId: quest.scheduled_job_id,
161
              transaction,
162
            });
163
          }
164

165
          // clean existing action_metas
166
          await models.QuestActionMeta.destroy({
8✔
167
            where: { quest_id },
168
            transaction,
169
          });
170
          // create new action_metas
171
          await models.QuestActionMeta.bulkCreate(
8✔
172
            action_metas.map((action_meta) => ({
16✔
173
              ...action_meta,
174
              quest_id,
175
            })),
176
          );
177
        }
178

179
        const delta = getDelta(quest, {
8✔
180
          name,
181
          description,
182
          community_id,
183
          image_url,
184
          start_date,
185
          end_date,
186
          max_xp_to_end,
187
        });
188
        if (Object.keys(delta).length) {
8✔
189
          await models.Quest.update(delta, {
1✔
190
            where: { id: quest_id },
191
            transaction,
192
          });
193

194
          // reschedule the quest job if end date is updated on a TwitterMetrics quest
195
          if (
1!
196
            delta.end_date &&
3!
197
            delta.end_date > quest.end_date &&
198
            quest.quest_type === 'channel' &&
199
            channelActionMeta?.event_name === 'TwitterMetrics' &&
200
            quest.scheduled_job_id
201
          ) {
UNCOV
202
            await rescheduleJobs({
×
203
              jobIds: [quest.scheduled_job_id],
204
              options: {
205
                runAt: delta.end_date,
206
              },
207
              transaction,
208
            });
209
          }
210
        }
211
      });
212

213
      const updated = await models.Quest.findOne({
8✔
214
        where: { id: quest_id },
215
        include: { model: models.QuestActionMeta, as: 'action_metas' },
216
      });
217
      return updated!.toJSON();
8✔
218
    },
219
  };
220
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc