Skip to content
Merged
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
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,20 @@ registrations and copilot assignments take effect without visiting the forum,
including before its first topic exists. The challenge watch routes return
`{ challengeId, memberId, watching }` for the authenticated human member; PUT and
DELETE persist explicit preferences. DELETE stores an opt-out so later reads or
resource changes cannot silently resubscribe the member. Explicit topic watches
remain independent. Challenge-wide watchers receive new topic and post emails,
resource changes cannot silently resubscribe the member. Topic list/detail reads
inherit this preference when there is no explicit topic choice. Topic PUT stores
`watching: true`; challenge-topic DELETE stores `watching: false` so a later forum
re-watch cannot resubscribe that topic. Explicit topic watches survive forum
opt-out, and explicit topic opt-outs suppress emails even while the forum is
watched. Announcements follow the same rules. New challenge topics, including
those created by the member, inherit the current forum preference. Challenge-wide watchers receive new topic and post emails,
subject to the same topic visibility, ban, author exclusion, and deduplication
rules as topic watchers. Deploy the `20261016000000_challenge_forum_watch` database
migration before the API and the Opportunities watch control.
migration and `20261020000000_topic_watch_preferences` before the API and the
Opportunities watch control. The latter preserves existing topic watch rows as
opt-ins; historical rows have no provenance distinguishing manual from author
watches. New challenge-topic creation no longer seeds an author watch. General
forum author watches and unwatch behavior are unchanged.

Recipient resolution expands watches from the created content topic through all
ancestor topics using `TopicClosure`, dedupes by member id, excludes the
Expand Down Expand Up @@ -307,4 +316,4 @@ The forums Prisma schema defines `Topic`, `Post`, `PostReaction`, `TopicClosure`

The production image includes the Prisma schema, migration history, configuration, and CLI. Its startup script runs `prisma migrate deploy` before starting NestJS, so a deployment cannot begin serving requests against an older forums schema. Migration failure stops the container instead of leaving feature-dependent reads or writes partially available.

Topic creation is transactional: it creates the topic, starter post, closure rows, and, for human authors, author watch and read-state rows together. M2M topic creation uses the system author and skips member watch/read-state side effects. Topic deletion is soft deletion, and post deletion preserves the post row while setting content to null for placeholder reads. Topic summary reads use side-effect-free raw queries for topic lock state, visible-post counts, nullable latest visible activity, and unread derivation, then apply centralized forums policy filtering before pagination.
Topic creation is transactional: it creates the topic, starter post, closure rows, and, for human authors, read-state rows together. General topics also seed an author watch; challenge topics inherit the forum preference. M2M topic creation uses the system author and skips member watch/read-state side effects. Topic deletion is soft deletion, and post deletion preserves the post row while setting content to null for placeholder reads. Topic summary reads use side-effect-free raw queries for topic lock state, visible-post counts, nullable latest visible activity, and unread derivation, then apply centralized forums policy filtering before pagination.
7 changes: 4 additions & 3 deletions packages/forums-prisma-client/edge.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/forums-prisma-client/index-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ exports.Prisma.TopicClosureScalarFieldEnum = {
exports.Prisma.TopicWatchScalarFieldEnum = {
topicId: 'topicId',
memberId: 'memberId',
watching: 'watching',
createdAt: 'createdAt'
};

Expand Down
38 changes: 37 additions & 1 deletion packages/forums-prisma-client/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8752,18 +8752,21 @@ export namespace Prisma {
export type TopicWatchMinAggregateOutputType = {
topicId: string | null
memberId: string | null
watching: boolean | null
createdAt: Date | null
}

export type TopicWatchMaxAggregateOutputType = {
topicId: string | null
memberId: string | null
watching: boolean | null
createdAt: Date | null
}

export type TopicWatchCountAggregateOutputType = {
topicId: number
memberId: number
watching: number
createdAt: number
_all: number
}
Expand All @@ -8772,18 +8775,21 @@ export namespace Prisma {
export type TopicWatchMinAggregateInputType = {
topicId?: true
memberId?: true
watching?: true
createdAt?: true
}

export type TopicWatchMaxAggregateInputType = {
topicId?: true
memberId?: true
watching?: true
createdAt?: true
}

export type TopicWatchCountAggregateInputType = {
topicId?: true
memberId?: true
watching?: true
createdAt?: true
_all?: true
}
Expand Down Expand Up @@ -8863,6 +8869,7 @@ export namespace Prisma {
export type TopicWatchGroupByOutputType = {
topicId: string
memberId: string
watching: boolean
createdAt: Date
_count: TopicWatchCountAggregateOutputType | null
_min: TopicWatchMinAggregateOutputType | null
Expand All @@ -8886,31 +8893,35 @@ export namespace Prisma {
export type TopicWatchSelect<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = $Extensions.GetSelect<{
topicId?: boolean
memberId?: boolean
watching?: boolean
createdAt?: boolean
topic?: boolean | TopicDefaultArgs<ExtArgs>
}, ExtArgs["result"]["topicWatch"]>

export type TopicWatchSelectCreateManyAndReturn<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = $Extensions.GetSelect<{
topicId?: boolean
memberId?: boolean
watching?: boolean
createdAt?: boolean
topic?: boolean | TopicDefaultArgs<ExtArgs>
}, ExtArgs["result"]["topicWatch"]>

export type TopicWatchSelectUpdateManyAndReturn<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = $Extensions.GetSelect<{
topicId?: boolean
memberId?: boolean
watching?: boolean
createdAt?: boolean
topic?: boolean | TopicDefaultArgs<ExtArgs>
}, ExtArgs["result"]["topicWatch"]>

export type TopicWatchSelectScalar = {
topicId?: boolean
memberId?: boolean
watching?: boolean
createdAt?: boolean
}

export type TopicWatchOmit<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = $Extensions.GetOmit<"topicId" | "memberId" | "createdAt", ExtArgs["result"]["topicWatch"]>
export type TopicWatchOmit<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = $Extensions.GetOmit<"topicId" | "memberId" | "watching" | "createdAt", ExtArgs["result"]["topicWatch"]>
export type TopicWatchInclude<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = {
topic?: boolean | TopicDefaultArgs<ExtArgs>
}
Expand All @@ -8929,6 +8940,7 @@ export namespace Prisma {
scalars: $Extensions.GetPayloadResult<{
topicId: string
memberId: string
watching: boolean
createdAt: Date
}, ExtArgs["result"]["topicWatch"]>
composites: {}
Expand Down Expand Up @@ -9356,6 +9368,7 @@ export namespace Prisma {
interface TopicWatchFieldRefs {
readonly topicId: FieldRef<"TopicWatch", 'String'>
readonly memberId: FieldRef<"TopicWatch", 'String'>
readonly watching: FieldRef<"TopicWatch", 'Boolean'>
readonly createdAt: FieldRef<"TopicWatch", 'DateTime'>
}

Expand Down Expand Up @@ -14986,6 +14999,7 @@ export namespace Prisma {
export const TopicWatchScalarFieldEnum: {
topicId: 'topicId',
memberId: 'memberId',
watching: 'watching',
createdAt: 'createdAt'
};

Expand Down Expand Up @@ -15626,13 +15640,15 @@ export namespace Prisma {
NOT?: TopicWatchWhereInput | TopicWatchWhereInput[]
topicId?: StringFilter<"TopicWatch"> | string
memberId?: StringFilter<"TopicWatch"> | string
watching?: BoolFilter<"TopicWatch"> | boolean
createdAt?: DateTimeFilter<"TopicWatch"> | Date | string
topic?: XOR<TopicScalarRelationFilter, TopicWhereInput>
}

export type TopicWatchOrderByWithRelationInput = {
topicId?: SortOrder
memberId?: SortOrder
watching?: SortOrder
createdAt?: SortOrder
topic?: TopicOrderByWithRelationInput
}
Expand All @@ -15644,13 +15660,15 @@ export namespace Prisma {
NOT?: TopicWatchWhereInput | TopicWatchWhereInput[]
topicId?: StringFilter<"TopicWatch"> | string
memberId?: StringFilter<"TopicWatch"> | string
watching?: BoolFilter<"TopicWatch"> | boolean
createdAt?: DateTimeFilter<"TopicWatch"> | Date | string
topic?: XOR<TopicScalarRelationFilter, TopicWhereInput>
}, "topicId_memberId">

export type TopicWatchOrderByWithAggregationInput = {
topicId?: SortOrder
memberId?: SortOrder
watching?: SortOrder
createdAt?: SortOrder
_count?: TopicWatchCountOrderByAggregateInput
_max?: TopicWatchMaxOrderByAggregateInput
Expand All @@ -15663,6 +15681,7 @@ export namespace Prisma {
NOT?: TopicWatchScalarWhereWithAggregatesInput | TopicWatchScalarWhereWithAggregatesInput[]
topicId?: StringWithAggregatesFilter<"TopicWatch"> | string
memberId?: StringWithAggregatesFilter<"TopicWatch"> | string
watching?: BoolWithAggregatesFilter<"TopicWatch"> | boolean
createdAt?: DateTimeWithAggregatesFilter<"TopicWatch"> | Date | string
}

Expand Down Expand Up @@ -16419,42 +16438,49 @@ export namespace Prisma {

export type TopicWatchCreateInput = {
memberId: string
watching?: boolean
createdAt?: Date | string
topic: TopicCreateNestedOneWithoutWatchesInput
}

export type TopicWatchUncheckedCreateInput = {
topicId: string
memberId: string
watching?: boolean
createdAt?: Date | string
}

export type TopicWatchUpdateInput = {
memberId?: StringFieldUpdateOperationsInput | string
watching?: BoolFieldUpdateOperationsInput | boolean
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
topic?: TopicUpdateOneRequiredWithoutWatchesNestedInput
}

export type TopicWatchUncheckedUpdateInput = {
topicId?: StringFieldUpdateOperationsInput | string
memberId?: StringFieldUpdateOperationsInput | string
watching?: BoolFieldUpdateOperationsInput | boolean
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
}

export type TopicWatchCreateManyInput = {
topicId: string
memberId: string
watching?: boolean
createdAt?: Date | string
}

export type TopicWatchUpdateManyMutationInput = {
memberId?: StringFieldUpdateOperationsInput | string
watching?: BoolFieldUpdateOperationsInput | boolean
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
}

export type TopicWatchUncheckedUpdateManyInput = {
topicId?: StringFieldUpdateOperationsInput | string
memberId?: StringFieldUpdateOperationsInput | string
watching?: BoolFieldUpdateOperationsInput | boolean
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
}

Expand Down Expand Up @@ -17222,18 +17248,21 @@ export namespace Prisma {
export type TopicWatchCountOrderByAggregateInput = {
topicId?: SortOrder
memberId?: SortOrder
watching?: SortOrder
createdAt?: SortOrder
}

export type TopicWatchMaxOrderByAggregateInput = {
topicId?: SortOrder
memberId?: SortOrder
watching?: SortOrder
createdAt?: SortOrder
}

export type TopicWatchMinOrderByAggregateInput = {
topicId?: SortOrder
memberId?: SortOrder
watching?: SortOrder
createdAt?: SortOrder
}

Expand Down Expand Up @@ -18301,11 +18330,13 @@ export namespace Prisma {

export type TopicWatchCreateWithoutTopicInput = {
memberId: string
watching?: boolean
createdAt?: Date | string
}

export type TopicWatchUncheckedCreateWithoutTopicInput = {
memberId: string
watching?: boolean
createdAt?: Date | string
}

Expand Down Expand Up @@ -18531,6 +18562,7 @@ export namespace Prisma {
NOT?: TopicWatchScalarWhereInput | TopicWatchScalarWhereInput[]
topicId?: StringFilter<"TopicWatch"> | string
memberId?: StringFilter<"TopicWatch"> | string
watching?: BoolFilter<"TopicWatch"> | boolean
createdAt?: DateTimeFilter<"TopicWatch"> | Date | string
}

Expand Down Expand Up @@ -19265,6 +19297,7 @@ export namespace Prisma {

export type TopicWatchCreateManyTopicInput = {
memberId: string
watching?: boolean
createdAt?: Date | string
}

Expand Down Expand Up @@ -19410,16 +19443,19 @@ export namespace Prisma {

export type TopicWatchUpdateWithoutTopicInput = {
memberId?: StringFieldUpdateOperationsInput | string
watching?: BoolFieldUpdateOperationsInput | boolean
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
}

export type TopicWatchUncheckedUpdateWithoutTopicInput = {
memberId?: StringFieldUpdateOperationsInput | string
watching?: BoolFieldUpdateOperationsInput | boolean
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
}

export type TopicWatchUncheckedUpdateManyWithoutTopicInput = {
memberId?: StringFieldUpdateOperationsInput | string
watching?: BoolFieldUpdateOperationsInput | boolean
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
}

Expand Down
Loading
Loading