Skip to content

Commit

Permalink
Start encapsulating Redis into a service
Browse files Browse the repository at this point in the history
There's a name collision between the Effect service tag and the IoRedis export; we've decided to alias the latter for the moment, but the collision might become encapsulated.

Refs #1834
  • Loading branch information
thewilkybarkid committed Sep 4, 2024
1 parent 42f6830 commit 26b47df
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 78 deletions.
150 changes: 76 additions & 74 deletions src/ExpressServer.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import KeyvRedis from '@keyv/redis'
import { SystemClock } from 'clock-ts'
import { Effect } from 'effect'
import { Context, Effect } from 'effect'
import * as C from 'fp-ts/lib/Console.js'
import { pipe } from 'fp-ts/lib/function.js'
import type { Redis } from 'ioredis'
import type { Redis as IoRedis } from 'ioredis'
import Keyv from 'keyv'
import * as L from 'logger-fp-ts'
import fetch from 'make-fetch-happen'
Expand All @@ -12,7 +12,11 @@ import { P, match } from 'ts-pattern'
import { app } from './app.js'
import { decodeEnv } from './env.js'

export const expressServer = (redis: Redis) => {
export class Redis extends Context.Tag('Redis')<Redis, IoRedis>() {}

export const expressServer = Effect.gen(function* () {
const redis = yield* Redis

const env = decodeEnv(process)()
const loggerEnv: L.LoggerEnv = {
clock: SystemClock,
Expand All @@ -32,76 +36,74 @@ export const expressServer = (redis: Redis) => {
.exhaustive()
const createKeyvStore = () => new KeyvRedis(redis)

return Effect.succeed(
app({
...loggerEnv,
allowSiteCrawlers: env.ALLOW_SITE_CRAWLERS,
authorInviteStore: new Keyv({ namespace: 'author-invite', store: createKeyvStore() }),
avatarStore: new Keyv({ namespace: 'avatar-store', store: createKeyvStore() }),
canConnectOrcidProfile: () => true,
canRequestReviews: () => true,
canSeeGatesLogo: true,
canUploadAvatar: () => true,
canUseSearchQueries: () => true,
cloudinaryApi: { cloudName: 'prereview', key: env.CLOUDINARY_API_KEY, secret: env.CLOUDINARY_API_SECRET },
coarNotifyToken: env.COAR_NOTIFY_TOKEN,
coarNotifyUrl: env.COAR_NOTIFY_URL,
contactEmailAddressStore: new Keyv({ namespace: 'contact-email-address', store: createKeyvStore() }),
environmentLabel: env.ENVIRONMENT_LABEL,
fathomId: env.FATHOM_SITE_ID,
fetch: fetch.defaults({
cachePath: 'data/cache',
headers: {
'User-Agent': `PREreview (${env.PUBLIC_URL.href}; mailto:[email protected])`,
},
}),
formStore: new Keyv({ namespace: 'forms', store: createKeyvStore() }),
careerStageStore: new Keyv({ namespace: 'career-stage', store: createKeyvStore() }),
ghostApi: {
key: env.GHOST_API_KEY,
},
isOpenForRequestsStore: new Keyv({ namespace: 'is-open-for-requests', store: createKeyvStore() }),
isUserBlocked: user => env.BLOCKED_USERS.includes(user),
legacyPrereviewApi: {
app: env.LEGACY_PREREVIEW_API_APP,
key: env.LEGACY_PREREVIEW_API_KEY,
url: env.LEGACY_PREREVIEW_URL,
update: env.LEGACY_PREREVIEW_UPDATE,
},
languagesStore: new Keyv({ namespace: 'languages', store: createKeyvStore() }),
locationStore: new Keyv({ namespace: 'location', store: createKeyvStore() }),
...sendMailEnv,
orcidApiUrl: env.ORCID_API_URL,
orcidApiToken: env.ORCID_API_READ_PUBLIC_TOKEN,
orcidOauth: {
authorizeUrl: new URL(`${env.ORCID_URL.origin}/oauth/authorize`),
clientId: env.ORCID_CLIENT_ID,
clientSecret: env.ORCID_CLIENT_SECRET,
revokeUrl: new URL(`${env.ORCID_URL.origin}/oauth/revoke`),
tokenUrl: new URL(`${env.ORCID_URL.origin}/oauth/token`),
},
orcidTokenStore: new Keyv({ namespace: 'orcid-token', store: createKeyvStore() }),
publicUrl: env.PUBLIC_URL,
redis,
researchInterestsStore: new Keyv({ namespace: 'research-interests', store: createKeyvStore() }),
reviewRequestStore: new Keyv({ namespace: 'review-request', store: createKeyvStore() }),
scietyListToken: env.SCIETY_LIST_TOKEN,
secret: env.SECRET,
sessionCookie: 'session',
sessionStore: new Keyv({ namespace: 'sessions', store: createKeyvStore(), ttl: 1000 * 60 * 60 * 24 * 30 }),
slackOauth: {
authorizeUrl: new URL('https://slack.com/oauth/v2/authorize'),
clientId: env.SLACK_CLIENT_ID,
clientSecret: env.SLACK_CLIENT_SECRET,
tokenUrl: new URL('https://slack.com/api/oauth.v2.access'),
return app({
...loggerEnv,
allowSiteCrawlers: env.ALLOW_SITE_CRAWLERS,
authorInviteStore: new Keyv({ namespace: 'author-invite', store: createKeyvStore() }),
avatarStore: new Keyv({ namespace: 'avatar-store', store: createKeyvStore() }),
canConnectOrcidProfile: () => true,
canRequestReviews: () => true,
canSeeGatesLogo: true,
canUploadAvatar: () => true,
canUseSearchQueries: () => true,
cloudinaryApi: { cloudName: 'prereview', key: env.CLOUDINARY_API_KEY, secret: env.CLOUDINARY_API_SECRET },
coarNotifyToken: env.COAR_NOTIFY_TOKEN,
coarNotifyUrl: env.COAR_NOTIFY_URL,
contactEmailAddressStore: new Keyv({ namespace: 'contact-email-address', store: createKeyvStore() }),
environmentLabel: env.ENVIRONMENT_LABEL,
fathomId: env.FATHOM_SITE_ID,
fetch: fetch.defaults({
cachePath: 'data/cache',
headers: {
'User-Agent': `PREreview (${env.PUBLIC_URL.href}; mailto:[email protected])`,
},
slackApiToken: env.SLACK_API_TOKEN,
slackApiUpdate: env.SLACK_UPDATE,
slackUserIdStore: new Keyv({ namespace: 'slack-user-id', store: createKeyvStore() }),
userOnboardingStore: new Keyv({ namespace: 'user-onboarding', store: createKeyvStore() }),
wasPrereviewRemoved: id => env.REMOVED_PREREVIEWS.includes(id),
zenodoApiKey: env.ZENODO_API_KEY,
zenodoUrl: env.ZENODO_URL,
}),
)
}
formStore: new Keyv({ namespace: 'forms', store: createKeyvStore() }),
careerStageStore: new Keyv({ namespace: 'career-stage', store: createKeyvStore() }),
ghostApi: {
key: env.GHOST_API_KEY,
},
isOpenForRequestsStore: new Keyv({ namespace: 'is-open-for-requests', store: createKeyvStore() }),
isUserBlocked: user => env.BLOCKED_USERS.includes(user),
legacyPrereviewApi: {
app: env.LEGACY_PREREVIEW_API_APP,
key: env.LEGACY_PREREVIEW_API_KEY,
url: env.LEGACY_PREREVIEW_URL,
update: env.LEGACY_PREREVIEW_UPDATE,
},
languagesStore: new Keyv({ namespace: 'languages', store: createKeyvStore() }),
locationStore: new Keyv({ namespace: 'location', store: createKeyvStore() }),
...sendMailEnv,
orcidApiUrl: env.ORCID_API_URL,
orcidApiToken: env.ORCID_API_READ_PUBLIC_TOKEN,
orcidOauth: {
authorizeUrl: new URL(`${env.ORCID_URL.origin}/oauth/authorize`),
clientId: env.ORCID_CLIENT_ID,
clientSecret: env.ORCID_CLIENT_SECRET,
revokeUrl: new URL(`${env.ORCID_URL.origin}/oauth/revoke`),
tokenUrl: new URL(`${env.ORCID_URL.origin}/oauth/token`),
},
orcidTokenStore: new Keyv({ namespace: 'orcid-token', store: createKeyvStore() }),
publicUrl: env.PUBLIC_URL,
redis,
researchInterestsStore: new Keyv({ namespace: 'research-interests', store: createKeyvStore() }),
reviewRequestStore: new Keyv({ namespace: 'review-request', store: createKeyvStore() }),
scietyListToken: env.SCIETY_LIST_TOKEN,
secret: env.SECRET,
sessionCookie: 'session',
sessionStore: new Keyv({ namespace: 'sessions', store: createKeyvStore(), ttl: 1000 * 60 * 60 * 24 * 30 }),
slackOauth: {
authorizeUrl: new URL('https://slack.com/oauth/v2/authorize'),
clientId: env.SLACK_CLIENT_ID,
clientSecret: env.SLACK_CLIENT_SECRET,
tokenUrl: new URL('https://slack.com/api/oauth.v2.access'),
},
slackApiToken: env.SLACK_API_TOKEN,
slackApiUpdate: env.SLACK_UPDATE,
slackUserIdStore: new Keyv({ namespace: 'slack-user-id', store: createKeyvStore() }),
userOnboardingStore: new Keyv({ namespace: 'user-onboarding', store: createKeyvStore() }),
wasPrereviewRemoved: id => env.REMOVED_PREREVIEWS.includes(id),
zenodoApiKey: env.ZENODO_API_KEY,
zenodoUrl: env.ZENODO_URL,
})
})
9 changes: 5 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import * as C from 'fp-ts/lib/Console.js'
import * as E from 'fp-ts/lib/Either.js'
import { pipe } from 'fp-ts/lib/function.js'
import type { JsonRecord } from 'fp-ts/lib/Json.js'
import { Redis } from 'ioredis'
import { Redis as IoRedis } from 'ioredis'
import * as L from 'logger-fp-ts'
import type { app } from './app.js'
import { decodeEnv } from './env.js'
import { expressServer } from './ExpressServer.js'
import { expressServer, Redis } from './ExpressServer.js'

const env = decodeEnv(process)()

Expand All @@ -20,7 +20,7 @@ const loggerEnv: L.LoggerEnv = {
logger: pipe(C.log, L.withShow(env.LOG_FORMAT === 'json' ? L.JsonShowLogEntry : L.getColoredShow(L.ShowLogEntry))),
}

const redis = new Redis(env.REDIS_URI.href, { commandTimeout: 2 * 1000, enableAutoPipelining: true })
const redis = new IoRedis(env.REDIS_URI.href, { commandTimeout: 2 * 1000, enableAutoPipelining: true })

redis.on('connect', () => L.debug('Redis connected')(loggerEnv)())
redis.on('close', () => L.debug('Redis connection closed')(loggerEnv)())
Expand Down Expand Up @@ -67,6 +67,7 @@ pipe(
expressServerLifecycle,
Layer.scopedDiscard,
Layer.launch,
Effect.provideServiceEffect(Express, expressServer(redis)),
Effect.provideServiceEffect(Express, expressServer),
Effect.provideServiceEffect(Redis, Effect.succeed(redis)),
NodeRuntime.runMain,
)

0 comments on commit 26b47df

Please sign in to comment.