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

hicommonwealth / commonwealth / 14722035488

29 Apr 2025 02:26AM UTC coverage: 45.422% (-0.4%) from 45.775%
14722035488

Pull #11922

github

web-flow
Merge c9f61c0d9 into 3e1b8e436
Pull Request #11922: Thread Rankings

1646 of 4010 branches covered (41.05%)

Branch coverage included in aggregate %.

17 of 83 new or added lines in 15 files covered. (20.48%)

3 existing lines in 2 files now uncovered.

3022 of 6267 relevant lines covered (48.22%)

39.67 hits per line

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

91.67
/libs/model/src/models/thread.ts
1
import { CacheNamespaces, cache } from '@hicommonwealth/core';
2
import { Thread } from '@hicommonwealth/schemas';
3
import {
4
  CountAggregatorKeys,
5
  MAX_TRUNCATED_CONTENT_LENGTH,
6
  getDecodedString,
7
} from '@hicommonwealth/shared';
8
import Sequelize from 'sequelize';
9
import { z } from 'zod';
10
import { emitEvent, getThreadContestManagers } from '../utils/utils';
11
import { AddressAttributes } from './address';
12
import type { CommunityAttributes } from './community';
13
import type { ThreadSubscriptionAttributes } from './thread_subscriptions';
14
import type { ModelInstance } from './types';
15
import { beforeValidateBodyHook } from './utils';
16

17
export type ThreadAttributes = z.infer<typeof Thread> & {
18
  // associations
19
  Community?: CommunityAttributes;
20
  subscriptions?: ThreadSubscriptionAttributes[];
21
};
22
export type ThreadInstance = ModelInstance<ThreadAttributes>;
23

24
export default (
25
  sequelize: Sequelize.Sequelize,
26
): Sequelize.ModelStatic<ThreadInstance> =>
27
  sequelize.define<ThreadInstance>(
107✔
28
    'Thread',
29
    {
30
      id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true },
31
      address_id: { type: Sequelize.INTEGER, allowNull: true },
32
      created_by: { type: Sequelize.STRING, allowNull: true },
33
      title: { type: Sequelize.TEXT, allowNull: false },
34
      body: {
35
        type: Sequelize.STRING(MAX_TRUNCATED_CONTENT_LENGTH),
36
        allowNull: false,
37
      },
38
      kind: { type: Sequelize.STRING, allowNull: false },
39
      stage: {
40
        type: Sequelize.TEXT,
41
        allowNull: false,
42
        defaultValue: 'discussion',
43
      },
44
      url: { type: Sequelize.TEXT, allowNull: true },
45
      topic_id: { type: Sequelize.INTEGER, allowNull: false },
46
      pinned: {
47
        type: Sequelize.BOOLEAN,
48
        defaultValue: false,
49
        allowNull: false,
50
      },
51
      community_id: { type: Sequelize.STRING, allowNull: false },
52
      view_count: {
53
        type: Sequelize.INTEGER,
54
        allowNull: false,
55
        defaultValue: 0,
56
      },
57
      read_only: {
58
        type: Sequelize.BOOLEAN,
59
        allowNull: false,
60
        defaultValue: false,
61
      },
62
      links: { type: Sequelize.JSONB, allowNull: true },
63
      discord_meta: { type: Sequelize.JSONB, allowNull: true },
64
      has_poll: { type: Sequelize.BOOLEAN, allowNull: true },
65

66
      // canvas-related columns
67
      canvas_signed_data: { type: Sequelize.JSONB, allowNull: true },
68
      canvas_msg_id: { type: Sequelize.STRING, allowNull: true },
69
      // timestamps
70
      created_at: { type: Sequelize.DATE, allowNull: false },
71
      updated_at: { type: Sequelize.DATE, allowNull: false },
72
      last_edited: { type: Sequelize.DATE, allowNull: true },
73
      deleted_at: { type: Sequelize.DATE, allowNull: true },
74
      last_commented_on: { type: Sequelize.DATE, allowNull: true },
75
      marked_as_spam_at: { type: Sequelize.DATE, allowNull: true },
76
      archived_at: { type: Sequelize.DATE, allowNull: true },
77
      locked_at: {
78
        type: Sequelize.DATE,
79
        allowNull: true,
80
      },
81
      user_tier_at_creation: { type: Sequelize.INTEGER, allowNull: true },
82

83
      //counts
84
      reaction_count: {
85
        type: Sequelize.INTEGER,
86
        allowNull: false,
87
        defaultValue: 0,
88
      },
89
      reaction_weights_sum: {
90
        type: Sequelize.DECIMAL(78, 0),
91
        allowNull: false,
92
        defaultValue: 0,
93
      },
94
      comment_count: {
95
        type: Sequelize.INTEGER,
96
        allowNull: false,
97
        defaultValue: 0,
98
      },
99
      activity_rank_date: {
100
        type: Sequelize.DATE,
101
        allowNull: true,
102
        defaultValue: new Date(),
103
      },
104
      search: {
105
        type: Sequelize.TSVECTOR,
106
        allowNull: true,
107
      },
108
      content_url: { type: Sequelize.STRING, allowNull: true },
109
      is_linking_token: {
110
        type: Sequelize.BOOLEAN,
111
        defaultValue: false,
112
        allowNull: false,
113
      },
114
      launchpad_token_address: { type: Sequelize.STRING, allowNull: true },
115
    },
116
    {
117
      timestamps: true,
118
      createdAt: 'created_at',
119
      updatedAt: 'updated_at',
120
      deletedAt: 'deleted_at',
121
      underscored: true,
122
      tableName: 'Threads',
123
      paranoid: true,
124
      indexes: [
125
        { fields: ['address_id'] },
126
        { fields: ['community_id'] },
127
        { fields: ['community_id', 'created_at'] },
128
        { fields: ['community_id', 'updated_at'] },
129
        { fields: ['community_id', 'pinned'] },
130
        { fields: ['community_id', 'has_poll'] },
131
        { fields: ['canvas_msg_id'] },
132
      ],
133
      hooks: {
134
        beforeValidate(instance: ThreadInstance) {
135
          beforeValidateBodyHook(instance);
134✔
136
        },
137
        afterCreate: async (
138
          thread: ThreadInstance,
139
          options: Sequelize.CreateOptions<ThreadAttributes>,
140
        ) => {
141
          const { Outbox, Address } = sequelize.models;
41✔
142

143
          await cache().addToSet(
41✔
144
            CacheNamespaces.CountAggregator,
145
            CountAggregatorKeys.CommunityThreadCount,
146
            thread.community_id,
147
          );
148

149
          const { topic_id, community_id } = thread.get({
41✔
150
            plain: true,
151
          });
152
          const contestManagers = !topic_id
41✔
153
            ? []
154
            : await getThreadContestManagers(sequelize, topic_id, community_id);
155

156
          const address = (await Address.findByPk(
41✔
157
            thread.address_id,
158
          )) as AddressAttributes | null;
159

160
          await emitEvent(
41✔
161
            Outbox,
162
            [
163
              {
164
                event_name: 'ThreadCreated',
165
                event_payload: {
166
                  ...thread.get({ plain: true }),
167
                  address: address!.address,
168
                  contestManagers,
169
                },
170
              },
171
            ],
172
            options.transaction,
173
          );
174
        },
175
        afterDestroy: async (thread: ThreadInstance) => {
NEW
176
          await cache().addToSet(
×
177
            CacheNamespaces.CountAggregator,
178
            CountAggregatorKeys.CommunityThreadCount,
179
            thread.community_id,
180
          );
181
        },
182
      },
183
    },
184
  );
185

186
export function getThreadSearchVector(title: string, body: string) {
187
  return Sequelize.fn(
18✔
188
    'to_tsvector',
189
    'english',
190
    getDecodedString(title) + ' ' + getDecodedString(body),
191
  );
192
}
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