Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ added active contacts API #95

Merged
merged 14 commits into from
Jul 9, 2024
2 changes: 1 addition & 1 deletion docs/openapi.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/matrix-identity-server/src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type Collections =
| 'roomTags'
| 'userHistory'
| 'userQuotas'
| 'activeContacts'

const cleanByExpires: Collections[] = ['oneTimeTokens', 'attempts']

Expand Down
3 changes: 2 additions & 1 deletion packages/matrix-identity-server/src/db/sql/sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ const tables: Record<Collections, string> = {
roomTags:
'id varchar(64) PRIMARY KEY, authorId varchar(64), content text, roomId varchar(64)',
userHistory: 'address text PRIMARY KEY, active integer, timestamp integer',
userQuotas: 'user_id varchar(64) PRIMARY KEY, size int'
userQuotas: 'user_id varchar(64) PRIMARY KEY, size int',
activeContacts: 'userId text PRIMARY KEY, contacts text'
}

const indexes: Partial<Record<Collections, string[]>> = {
Expand Down
138 changes: 138 additions & 0 deletions packages/tom-server/src/active-contacts-api/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/* eslint-disable no-useless-return */
import type { Response, NextFunction } from 'express'
import type { TwakeDB } from '../../db'
import type {
IActiveContactsService,
IActiveContactsApiController
} from '../types'
import type { TwakeLogger } from '@twake/logger'
import ActiveContactsService from '../services'
import type { AuthRequest } from '../../types'

export default class ActiveContactsApiController
implements IActiveContactsApiController
{
ActiveContactsApiService: IActiveContactsService

/**
* the active contacts API controller constructor
*
* @param {TwakeDB} db - the twake database instance
* @param {TwakeLogger} logger - the twake logger instance
* @example
* const controller = new ActiveContactsApiController(db, logger);
*/
constructor(
private readonly db: TwakeDB,
private readonly logger: TwakeLogger
) {
this.ActiveContactsApiService = new ActiveContactsService(db, logger)
}

/**
* Save active contacts
*
* @param {AuthRequest} req - request object
* @param {Response} res - response object
* @param {NextFunction} next - next function
* @returns {Promise<void>} - promise that resolves when the operation is complete
*/
save = async (
req: AuthRequest,
res: Response,
next: NextFunction
): Promise<void> => {
try {
const { contacts } = req.body
const { userId } = req

if (userId === undefined || contacts === undefined) {
res.status(400).json({ message: 'Bad Request' })
return
}

await this.ActiveContactsApiService.save(userId, contacts)

res.status(201).send()
return
} catch (error) {
this.logger.error('An error occured while saving active contacts', {
error
})
next(error)
}
}

/**
* Retrieve active contacts
*
* @param {AuthRequest} req - request object
* @param {Response} res - response object
* @param {NextFunction} next - next function
* @returns {Promise<void>} - promise that resolves when the operation is complete
*/
get = async (
req: AuthRequest,
res: Response,
next: NextFunction
): Promise<void> => {
try {
const { userId } = req

if (userId === undefined) {
throw new Error('Missing data', {
cause: 'userId is missing'
})
}

const contacts = await this.ActiveContactsApiService.get(userId)

if (contacts === null) {
res.status(404).json({ message: 'No active contacts found' })
return
}

res.status(200).json({ contacts })
return
} catch (error) {
this.logger.error('An error occured while retrieving active contacts', {
error
})
next(error)
}
}

/**
* Delete saved active contacts
*
* @param {AuthRequest} req - request object
* @param {Response} res - response object
* @param {NextFunction} next - next function
* @returns {Promise<void>} - promise that resolves when the operation is complete
*/
delete = async (
req: AuthRequest,
res: Response,
next: NextFunction
): Promise<void> => {
try {
const { userId } = req

if (userId === undefined) {
throw new Error('Missing data', {
cause: 'userId is missing'
})
}

await this.ActiveContactsApiService.delete(userId)

res.status(200).send()
return
} catch (error) {
this.logger.error('An error occured while deleting active contacts', {
error
})
next(error)
}
}
}
1 change: 1 addition & 0 deletions packages/tom-server/src/active-contacts-api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './routes'
37 changes: 37 additions & 0 deletions packages/tom-server/src/active-contacts-api/middlewares/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { Response, NextFunction } from 'express'
import type { AuthRequest } from '../../types'
import type { IActiveContactsApiValidationMiddleware } from '../types'

export default class ActiveContactsApiValidationMiddleWare
implements IActiveContactsApiValidationMiddleware
{
/**
* Check the creation requirements of the active contacts API
*
* @param {AuthRequest} req - the request object
* @param {Response} res - the response object
* @param {NextFunction} next - the next function
* @returns {void}
* @example
* router.post('/', checkCreationRequirements, create)
*/
checkCreationRequirements = (
req: AuthRequest,
res: Response,
next: NextFunction
): void => {
try {
const { contacts } = req.body

if (contacts === undefined) {
throw new Error('Missing required fields', {
cause: 'userId or contacts is missing'
})
}

next()
} catch (error) {
res.status(400).json({ message: 'Bad Request' })
}
}
}
127 changes: 127 additions & 0 deletions packages/tom-server/src/active-contacts-api/routes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/* eslint-disable @typescript-eslint/no-misused-promises */
import {
getLogger,
type TwakeLogger,
type Config as LoggerConfig
} from '@twake/logger'
import type {
AuthenticationFunction,
Config,
IdentityServerDb
} from '../../types'
import { Router } from 'express'
import authMiddleware from '../../utils/middlewares/auth.middleware'
import ActiveContactsApiController from '../controllers'
import ActiveContactsApiValidationMiddleWare from '../middlewares'

export const PATH = '/_twake/v1/activecontacts'

export default (
db: IdentityServerDb,
config: Config,
authenticator: AuthenticationFunction,
defaultLogger?: TwakeLogger
): Router => {
const logger = defaultLogger ?? getLogger(config as unknown as LoggerConfig)
const activeContactsApiController = new ActiveContactsApiController(
db,
logger
)
const authenticate = authMiddleware(authenticator, logger)
const validationMiddleware = new ActiveContactsApiValidationMiddleWare()
const router = Router()

/**
* @openapi
* components:
* schemas:
* ActiveContacts:
* type: object
* description: the list of active contacts
* properties:
* contacts:
* type: string
* description: active contacts
* responses:
* NotFound:
* description: no active contacts found
* Unauthorized:
* description: the user is not authorized
* Created:
* description: active contacts saved
* NoContent:
* description: operation successful and no content returned
*/

/**
* @openapi
* /_twake/v1/activecontacts:
* get:
* tags:
* - Active contacts
* description: Get the list of active contacts
* responses:
* 200:
* description: Active contacts found
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ActiveContacts'
* 404:
* description: Active contacts not found
* 401:
* description: user is unauthorized
* 500:
* description: Internal error
*/
router.get(PATH, authenticate, activeContactsApiController.get)
Fixed Show fixed Hide fixed
Dismissed Show dismissed Hide dismissed

/**
* @openapi
* /_twake/v1/activecontacts:
* post:
* tags:
* - Active contacts
* description: Create or update the list of active contacts
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ActiveContacts'
* responses:
* 201:
* description: Active contacts saved
* 401:
* description: user is unauthorized
* 400:
* description: Bad request
* 500:
* description: Internal error
*/
router.post(
PATH,
authenticate,
Fixed Show fixed Hide fixed
Dismissed Show dismissed Hide dismissed
validationMiddleware.checkCreationRequirements,
activeContactsApiController.save
)

/**
* @openapi
* /_twake/v1/activecontacts:
* delete:
* tags:
* - Active contacts
* description: Delete the list of active contacts
* responses:
* 200:
* description: Active contacts deleted
* 401:
* description: user is unauthorized
* 500:
* description: Internal error/
*/
router.delete(PATH, authenticate, activeContactsApiController.delete)
Dismissed Show dismissed Hide dismissed

return router
}
Loading
Loading