Repository navigation
Expand file tree
/
Copy pathservices.ts
More file actions
130 lines (125 loc) · 5.66 KB
/
Copy pathservices.ts
File metadata and controls
130 lines (125 loc) · 5.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/**
* Services plugin.
*
* Loads in-memory state from the public store, builds the FTS engine,
* and decorates fastify with service instances that route handlers use.
*
* Depends on the store plugin (for fastify.store).
*/
import type { FastifyInstance } from 'fastify';
import fp from 'fastify-plugin';
import { loadInMemoryState } from '../store/memory/loader.js';
import { invalidateFacets } from '../store/memory/facets.js';
import { buildFtsEngine, type FtsEngine } from '../store/fts.js';
import type { InMemoryState } from '../store/memory/state.js';
import { ProjectService } from '../services/project.js';
import { PersonService } from '../services/person.js';
import { TagService } from '../services/tag.js';
import { ProjectUpdateService } from '../services/project-update.js';
import { ProjectBuzzService } from '../services/project-buzz.js';
import { BlogPostService } from '../services/blog-post.js';
import { HelpWantedService } from '../services/help-wanted.js';
import { ProjectWriteService } from '../services/project.write.js';
import { ProjectMembershipWriteService } from '../services/project-membership.write.js';
import { ProjectUpdateWriteService } from '../services/project-update.write.js';
import { ProjectBuzzWriteService } from '../services/project-buzz.write.js';
import { HelpWantedWriteService } from '../services/help-wanted.write.js';
import { PersonWriteService } from '../services/person.write.js';
import { TagWriteService } from '../services/tag.write.js';
import { GitHubAccountService } from '../services/github-account.js';
import { AccountClaimService } from '../services/account-claim.js';
import { ModerationService, ModerationWriteService } from '../services/moderation.js';
import { LoggingNotifier, type Notifier } from '../notify/index.js';
import { EmailNotifier } from '../notify/email-notifier.js';
import { PostmarkTransport } from '../notify/postmark-transport.js';
import { ServerClient } from 'postmark';
declare module 'fastify' {
interface FastifyInstance {
services: {
projects: ProjectService;
people: PersonService;
tags: TagService;
projectUpdates: ProjectUpdateService;
projectBuzz: ProjectBuzzService;
blogPosts: BlogPostService;
helpWanted: HelpWantedService;
// Write services
projectsWrite: ProjectWriteService;
projectMembershipsWrite: ProjectMembershipWriteService;
projectUpdatesWrite: ProjectUpdateWriteService;
projectBuzzWrite: ProjectBuzzWriteService;
helpWantedWrite: HelpWantedWriteService;
peopleWrite: PersonWriteService;
tagsWrite: TagWriteService;
githubAccount: GitHubAccountService;
accountClaim: AccountClaimService;
moderation: ModerationService;
moderationWrite: ModerationWriteService;
};
/** Shared in-memory state — write routes call StateApply.apply against this. */
inMemoryState: InMemoryState;
fts: FtsEngine;
notifier: Notifier;
}
}
async function servicesPlugin(fastify: FastifyInstance): Promise<void> {
const publicStore = fastify.store.public;
const state = await loadInMemoryState(publicStore);
// Reset module-level facet cache so a fresh boot reflects current state
// (relevant in tests where multiple buildApp() runs share the module).
invalidateFacets();
const fts = buildFtsEngine(state);
// Email notifier when POSTMARK_SERVER_TOKEN is configured; otherwise fall
// back to the no-op LoggingNotifier so tests + dev runs work without a
// real token.
// Slack DM is deferred (#95) — when it lands it'll compose alongside email
// here or via a CompoundNotifier wrapper.
const notifier: Notifier = fastify.config.POSTMARK_SERVER_TOKEN
? new EmailNotifier({
transport: new PostmarkTransport({
client: new ServerClient(fastify.config.POSTMARK_SERVER_TOKEN),
messageStream: fastify.config.POSTMARK_MESSAGE_STREAM,
}),
fromAddress: fastify.config.CFP_NOTIFICATION_FROM,
siteHost: fastify.config.CFP_SITE_HOST,
logger: fastify.log,
})
: new LoggingNotifier(fastify.log);
fastify.decorate('inMemoryState', state);
fastify.decorate('fts', fts);
fastify.decorate('notifier', notifier);
const githubAccount = new GitHubAccountService(state);
fastify.decorate('services', {
projects: new ProjectService(state, fts),
people: new PersonService(state, fts, fastify.store.private),
tags: new TagService(state),
projectUpdates: new ProjectUpdateService(state),
projectBuzz: new ProjectBuzzService(state),
blogPosts: new BlogPostService(state),
helpWanted: new HelpWantedService(state, fts),
projectsWrite: new ProjectWriteService(state),
projectMembershipsWrite: new ProjectMembershipWriteService(state),
projectUpdatesWrite: new ProjectUpdateWriteService(state),
projectBuzzWrite: new ProjectBuzzWriteService(state),
helpWantedWrite: new HelpWantedWriteService(state),
peopleWrite: new PersonWriteService(state, fastify.store.private),
tagsWrite: new TagWriteService(state),
githubAccount,
accountClaim: new AccountClaimService(state, fastify.store.private, githubAccount),
moderation: new ModerationService(state, fastify.store.private, (personId) => {
// Newest sign-in from session metadata; the auth plugin decorates it
// after this one registers, so resolve lazily per call.
let latest: string | null = null;
for (const m of fastify.sessionMetadata?.getAll(personId) ?? []) {
if (!latest || m.issuedAt > latest) latest = m.issuedAt;
}
return latest;
}),
moderationWrite: new ModerationWriteService(state),
});
}
export default fp(servicesPlugin, {
name: 'services',
fastify: '5.x',
dependencies: ['store'],
});