Skip to content

Commit

Permalink
fix: remove reliance on non-standard predicate in type indexes
Browse files Browse the repository at this point in the history
See also:
solid/type-indexes#29
solidcouch/solidcouch#135

And somehow also switch to ESM, change tsconfig, and switch to vitest
  • Loading branch information
mrkvon committed Jan 29, 2025
1 parent 4597e88 commit 34dd594
Show file tree
Hide file tree
Showing 27 changed files with 794 additions and 359 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
dist
apidocs
vitest.config.ts
4 changes: 0 additions & 4 deletions .mocharc.yml

This file was deleted.

15 changes: 8 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
{
"name": "simple-email-notifications",
"version": "0.0.1",
"type": "module",
"main": "dist/index.js",
"scripts": {
"start": "yarn build && node dist/index.js",
"build": "rm -rf dist && tsc --project tsconfig.production.json && yarn copy-hbs",
"copy-hbs": "cp src/templates/*.hbs dist/templates && cp src/templates/*.css dist/templates",
"format": "prettier 'src/**/*.ts' '**/*.{md,yml,yaml,json}' --write",
"lint": "eslint . --ext .ts",
"test": "mocha",
"test": "NODE_ENV=vitest vitest",
"generate-key": "openssl ecparam -name prime256v1 -genkey -noout -out ecdsa-p256-private.pem",
"generate-api-docs": "tsc && node dist/generate-api-docs.js"
},
Expand All @@ -17,7 +18,6 @@
"@solid/community-server": "^7.0.2",
"@types/chai": "^4.3.5",
"@types/maildev": "^0.0.7",
"@types/mocha": "^10.0.1",
"@types/sinon": "^10.0.15",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.19.0",
Expand All @@ -30,19 +30,19 @@
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.0.0",
"maildev": "^2.1.0",
"mocha": "^10.2.0",
"msw": "^2.0.14",
"prettier": "^3.0.0",
"puppeteer": "^22.2.0",
"sinon": "^15.2.0",
"swagger-autogen": "^2.23.6",
"ts-node": "^10.9.1"
"ts-node": "^10.9.1",
"vitest": "^3.0.4"
},
"dependencies": {
"@koa/bodyparser": "^5.0.0",
"@koa/cors": "^4.0.0",
"@koa/router": "^12.0.0",
"@ldhop/core": "^0.0.0-alpha.1",
"@ldhop/core": "^0.1.0",
"@solid/access-token-verifier": "^2.0.5",
"@types/co-body": "^6.1.0",
"@types/fs-extra": "^11.0.4",
Expand All @@ -53,7 +53,7 @@
"@types/koa__router": "^12.0.0",
"@types/lodash": "^4.14.196",
"@types/n3": "^1.16.0",
"@types/node": "^20.4.2",
"@types/node": "^22.12.0",
"@types/nodemailer": "^6.4.9",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
Expand All @@ -71,5 +71,6 @@
"nodemailer": "^6.9.4",
"rdf-namespaces": "^1.11.0",
"typescript": "^5.1.6"
}
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
18 changes: 9 additions & 9 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import bodyParser from '@koa/bodyparser'
import { bodyParser } from '@koa/bodyparser'
import cors from '@koa/cors'
import Router from '@koa/router'
import Koa from 'koa'
import helmet from 'koa-helmet'
import serve from 'koa-static'
import { allowedGroups, isBehindProxy } from './config'
import { allowedGroups, isBehindProxy } from './config/index.js'
import {
checkVerificationLink,
finishIntegration,
initializeIntegration,
} from './controllers/integration'
import { notification } from './controllers/notification'
import { getStatus } from './controllers/status'
} from './controllers/integration.js'
import { notification } from './controllers/notification.js'
import { getStatus } from './controllers/status.js'
import {
authorizeGroups,
checkGroupMembership,
} from './middlewares/authorizeGroup'
import { solidAuth } from './middlewares/solidAuth'
import { validateBody } from './middlewares/validate'
import * as schema from './schema'
} from './middlewares/authorizeGroup.js'
import { solidAuth } from './middlewares/solidAuth.js'
import { validateBody } from './middlewares/validate.js'
import * as schema from './schema.js'

const app = new Koa()
app.proxy = isBehindProxy
Expand Down
10 changes: 7 additions & 3 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import SMTPTransport from 'nodemailer/lib/smtp-transport'
// either via .env file, or via environment variables directly (depends on your setup)

// server base url, e.g. to construct correct email verification links
export const baseUrl = process.env.BASE_URL ?? 'http://localhost:3005'
// export const baseUrl = process.env.BASE_URL ?? 'http://localhost:3005'
export const port: number = +(process.env.PORT ?? 3005)

export const baseUrl =
process.env.NODE_ENV === 'vitest' || !process.env.BASE_URL
? `http://localhost:${port}`
: process.env.BASE_URL

export const appName = process.env.APP_NAME ?? 'Tired.bike'

Expand Down Expand Up @@ -51,8 +57,6 @@ export const smtpTransportOptions: SMTPTransport.Options = {
export const emailSender =
process.env.EMAIL_SENDER ?? 'noreply@notifications.tired.bike'

export const port: number = +(process.env.PORT ?? 3005)

// email verification expiration in seconds (1 hour)
export const emailVerificationExpiration = 3600

Expand Down
15 changes: 8 additions & 7 deletions src/controllers/integration.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { readFile } from 'fs-extra'
import * as jsonwebtoken from 'jsonwebtoken'
import { JsonWebTokenError, TokenExpiredError } from 'jsonwebtoken'
import { Middleware } from 'koa'
import { pick } from 'lodash'
import * as config from '../config'
import { sendMail } from '../services/mailerService'
import { generateHtmlMessage } from '../templates/generateMessage'
import { findWritableSettings, getBotFetch } from '../utils'
import pick from 'lodash/pick.js'
import { readFile } from 'node:fs/promises'
import * as config from '../config/index.js'
import { sendMail } from '../services/mailerService.js'
import { generateHtmlMessage } from '../templates/generateMessage.js'
import { findWritableSettings, getBotFetch } from '../utils.js'

const { JsonWebTokenError, TokenExpiredError } = jsonwebtoken

export const initializeIntegration: Middleware<{
user: string
Expand Down
8 changes: 4 additions & 4 deletions src/controllers/notification.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { DefaultContext, Middleware } from 'koa'
import { appName, emailSender } from '../config'
import { sendMail } from '../services/mailerService'
import { generateHtmlMessage } from '../templates/generateMessage'
import { getVerifiedEmails } from './status'
import { appName, emailSender } from '../config/index.js'
import { sendMail } from '../services/mailerService.js'
import { generateHtmlMessage } from '../templates/generateMessage.js'
import { getVerifiedEmails } from './status.js'

export type GoodBody = {
'@context': 'https://www.w3.org/ns/activitystreams'
Expand Down
10 changes: 5 additions & 5 deletions src/controllers/status.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { readFile } from 'fs-extra'
import { verify } from 'jsonwebtoken'
import * as jsonwebtoken from 'jsonwebtoken'
import type { DefaultContext, DefaultState, Middleware } from 'koa'
import * as config from '../config'
import { findEmailVerificationTokens } from '../utils'
import { readFile } from 'node:fs/promises'
import * as config from '../config/index.js'
import { findEmailVerificationTokens } from '../utils.js'

export const getVerifiedEmails = async (webId: string) => {
const tokens = await findEmailVerificationTokens(webId)
Expand All @@ -12,7 +12,7 @@ export const getVerifiedEmails = async (webId: string) => {
const verifiedEmails = tokens
.map(token => {
try {
return verify(token, pem) as {
return jsonwebtoken.verify(token, pem) as {
webId: string
email: string
emailVerified: boolean
Expand Down
2 changes: 1 addition & 1 deletion src/generate-api-docs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// https://swagger-autogen.github.io/docs/getting-started/advanced-usage#openapi-3x
import swaggerAutogen from 'swagger-autogen'
import { init, notification } from './schema'
import { init, notification } from './schema.js'

const doc = {
info: {
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import app from './app'
import { port } from './config'
import app from './app.js'
import { port } from './config/index.js'

app.listen(port, () => {
// eslint-disable-next-line no-console
Expand Down
2 changes: 1 addition & 1 deletion src/middlewares/authorizeGroup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Middleware } from 'koa'
import { get } from 'lodash'
import get from 'lodash/get.js'
import { Parser } from 'n3'
import { vcard } from 'rdf-namespaces'

Expand Down
6 changes: 3 additions & 3 deletions src/middlewares/validate.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import addFormats from 'ajv-formats'
import Ajv2020 from 'ajv/dist/2020'
import { default as Ajv2020 } from 'ajv/dist/2020.js'
import type { Middleware } from 'koa'

const ajv = new Ajv2020({ allErrors: true })
addFormats(ajv)
const ajv = new Ajv2020.default({ allErrors: true })
addFormats.default(ajv)

/**
* This middleware generator accepts json-schema and returns Middleware
Expand Down
2 changes: 1 addition & 1 deletion src/services/mailerService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as nodemailer from 'nodemailer'
import Mail from 'nodemailer/lib/mailer'
import * as path from 'path'
import { appLogo, smtpTransportOptions } from '../config'
import { appLogo, smtpTransportOptions } from '../config/index.js'

export const sendMail = async (options: Mail.Options) => {
const smtpTransport = nodemailer.createTransport(smtpTransportOptions)
Expand Down
2 changes: 1 addition & 1 deletion src/templates/generateMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as fs from 'fs-extra'
import Handlebars from 'handlebars'
import juice from 'juice'
import path from 'path'
import * as config from '../config'
import * as config from '../config/index.js'

Handlebars.registerHelper('encodeURIComponent', encodeURIComponent)

Expand Down
34 changes: 19 additions & 15 deletions src/test/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { expect } from 'chai'
import * as cheerio from 'cheerio'
import { createAccount } from 'css-authn/dist/7.x'
import { createAccount } from 'css-authn/dist/7.x.js'
import * as puppeteer from 'puppeteer'
import { createSandbox } from 'sinon'
import { v4 as uuidv4 } from 'uuid'
import * as config from '../../config'
import * as mailerService from '../../services/mailerService'
import { setupEmailSettings } from './setupPod'
import { Person } from './types'
import { expect, vi } from 'vitest'
import * as config from '../../config/index.js'
import * as mailerService from '../../services/mailerService.js'
import { setupEmailSettings } from './setupPod.js'
import { Person } from './types.js'

export const createRandomAccount = ({
solidServer,
Expand All @@ -29,8 +28,7 @@ export const initIntegration = async ({
email: string
authenticatedFetch: typeof fetch
}) => {
const sandbox = createSandbox()
const sendMailSpy = sandbox.spy(mailerService, 'sendMail')
const sendMailSpy = vi.spyOn(mailerService, 'sendMail')
const initResponse = await authenticatedFetch(`${config.baseUrl}/init`, {
method: 'post',
headers: { 'content-type': 'application/json' },
Expand All @@ -39,11 +37,12 @@ export const initIntegration = async ({

expect(initResponse.status).to.equal(200)
// email was sent
const emailMessage = sendMailSpy.firstCall.firstArg.html
const $ = cheerio.load(emailMessage)
const verificationLink = $('a').first().attr('href') as string
const emailMessage = sendMailSpy.mock.calls[0][0].html as string
expect(emailMessage).toBeDefined()
const $ = cheerio.load(emailMessage as string)
const verificationLink = $('a').first().attr('href')
expect(verificationLink).to.not.be.null
sandbox.restore()
vi.restoreAllMocks()

return { verificationLink }
}
Expand Down Expand Up @@ -77,13 +76,18 @@ export const verifyEmail = async ({
authenticatedFetch,
})

const { token } = await finishIntegration(verificationLink)
expect(verificationLink).toBeDefined()

const { token } = await finishIntegration(verificationLink!)

return token
}

export const takeScreenshot = async (email: { html: string }, name: string) => {
const browser = await puppeteer.launch()
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox'],
})

const page = await browser.newPage()
await page.setContent(email.html)
Expand Down
6 changes: 3 additions & 3 deletions src/test/helpers/setupPod.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect } from 'chai'
import { foaf, solid, space } from 'rdf-namespaces'
import * as config from '../../config'
import { Person } from './types'
import { expect } from 'vitest'
import * as config from '../../config/index.js'
import { Person } from './types.js'

const createFile = async ({
url,
Expand Down
27 changes: 12 additions & 15 deletions src/test/integration-finish.spec.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,31 @@
import { expect } from 'chai'
import fetch from 'cross-fetch'
import * as jsonwebtoken from 'jsonwebtoken'
import { describe } from 'mocha'
import { SinonSandbox, createSandbox } from 'sinon'
import * as config from '../config'
import { fetchRdf } from '../utils'
import { initIntegration, takeScreenshot } from './helpers'
import { setupEmailSettings } from './helpers/setupPod'
import { authenticatedFetch, person } from './testSetup.spec'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import * as config from '../config/index.js'
import { fetchRdf } from '../utils.js'
import { initIntegration, takeScreenshot } from './helpers/index.js'
import { setupEmailSettings } from './helpers/setupPod.js'
import { authenticatedFetch, person } from './setup.js'

describe('email verification via /verify-email?token=jwt', () => {
let verificationLink: string
let sandbox: SinonSandbox
let settings: string

beforeEach(() => {
sandbox = createSandbox()
sandbox.useFakeTimers({ now: Date.now(), toFake: ['Date'] })
vi.useFakeTimers({ now: Date.now(), toFake: ['Date'] })
})

afterEach(() => {
sandbox.restore()
vi.useRealTimers()
})

beforeEach(async () => {
// initialize the integration
;({ verificationLink } = await initIntegration({
const initIntegrationResponse = await initIntegration({
email: 'email@example.com',
authenticatedFetch,
}))
})
verificationLink = initIntegrationResponse.verificationLink!
})

beforeEach(async () => {
Expand Down Expand Up @@ -114,7 +111,7 @@ describe('email verification via /verify-email?token=jwt', () => {
})

it('[expired token] should respond with 400', async () => {
sandbox.clock.tick(3601 * 1000)
vi.advanceTimersByTime(3601 * 1000)
const response = await fetch(verificationLink)
// wait one hour and one second
expect(response.status).to.equal(400)
Expand Down
Loading

0 comments on commit 34dd594

Please sign in to comment.