Skip to content
This repository has been archived by the owner on Oct 9, 2024. It is now read-only.

Commit

Permalink
HYP-2: introducing certificates controller
Browse files Browse the repository at this point in the history
  • Loading branch information
n3op2 committed Nov 24, 2023
1 parent 5ddd661 commit 96d21b6
Show file tree
Hide file tree
Showing 24 changed files with 307 additions and 222 deletions.
28 changes: 11 additions & 17 deletions src/controllers/v1/attachment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@ import { Readable } from 'node:stream'

import { logger } from '../../../lib/logger'
import Database, { AttachmentRow } from '../../../lib/db'
import type { Attachment } from '../../../models'
import type * as Attachment from '../../../models'
import { BadRequest, NotFound } from '../../../lib/error-handler'
import type { UUID, DATE } from '../../../models/strings'
import Ipfs from '../../../lib/ipfs'
import env from '../../../env'
import { parseDateParam } from '../../../lib/utils/queryParams'

const parseAccept = (acceptHeader: string) =>
acceptHeader
Expand Down Expand Up @@ -87,14 +86,14 @@ export class attachment extends Controller {

@Get('/')
@SuccessResponse(200, 'returns all attachment')
public async get(@Query() updated_since?: DATE): Promise<Attachment[]> {
public async getAll(@Query() createdAt?: DATE): Promise<Attachment.Response> {
this.log.debug('retrieving all attachment')
const where: Record<string, number | string | DATE> = {}
if (createdAt) where.created_at = createdAt

const attachments: AttachmentRow[] = await this.db.get(
'attachment',
updated_since ? { created_at: parseDateParam(updated_since) } : {}
)
return attachments.map(({ ipfs_hash, ...rest }): Attachment => ({ ...rest, createdAt: rest.created_at }))
const attachments = await this.db.get('attachment', where)

return { message: 'ok', attachments }
}

@Post('/')
Expand All @@ -103,7 +102,7 @@ export class attachment extends Controller {
public async create(
@Request() req: express.Request,
@UploadedFile() file?: Express.Multer.File
): Promise<Attachment> {
): Promise<Attachment.Response | string> {
this.log.debug(`creating an attachment filename: ${file?.originalname || 'json'}`)

if (!req.body && !file) throw new BadRequest('nothing to upload')
Expand All @@ -112,26 +111,21 @@ export class attachment extends Controller {
const fileBlob = new Blob([Buffer.from(file?.buffer || JSON.stringify(req.body))])
const ipfsHash = await this.ipfs.addFile({ blob: fileBlob, filename })

const [{ id, created_at }] = await this.db.insert('attachment', {
const result: AttachmentRow | string = await this.db.insert('attachment', {
filename,
ipfs_hash: ipfsHash,
size: fileBlob.size,
})

return {
id,
filename,
size: fileBlob.size,
createdAt: created_at,
}
return result
}

@Get('/{id}')
@Response<NotFound>(404)
@Produces('application/json')
@Produces('application/octet-stream')
@SuccessResponse(200)
public async getById(@Request() req: express.Request, @Path() id: UUID): Promise<unknown | Readable> {
public async getById(@Request() req: express.Request, @Path() id: UUID): Promise<Attachment.Response> {
this.log.debug(`attempting to retrieve ${id} attachment`)
const [attachment] = await this.db.get('attachment', { id })
if (!attachment) throw new NotFound('attachment')
Expand Down
115 changes: 115 additions & 0 deletions src/controllers/v1/certificate/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {
ValidateError,
Controller,
Post,
Get,
Route,
Response,
Body,
SuccessResponse,
Example,
Tags,
Security,
Path,
Query,
} from 'tsoa'
import type { Logger } from 'pino'
import { injectable } from 'tsyringe'

import { logger } from '../../../lib/logger'
import Database from '../../../lib/db'
import { BadRequest, NotFound } from '../../../lib/error-handler/index'
import Identity from '../../../lib/services/identity'
import * as Certificate from '../../../models/certificate'
import { DATE, UUID } from '../../../models/strings'
// import { TransactionResponse, TransactionType } from '../../../models/transaction'
import ChainNode from '../../../lib/chainNode'
import env from '../../../env'
import { camelToSnake } from '../../../lib/utils/shared'

@Route('v1/certificate')
@injectable()
@Tags('certificate')
@Security('BearerAuth')
export class CertificateController extends Controller {
log: Logger
db: Database
node: ChainNode

constructor(private identity: Identity) {
super()
this.log = logger.child({ controller: '/certificate' })
this.db = new Database()
this.node = new ChainNode({
host: env.NODE_HOST,
port: env.NODE_PORT,
logger,
userUri: env.USER_URI,
})
this.identity = identity
}

/**
* @summary insert a certificate for initialisation
*/
@Example<Certificate.Payload>({
id: '52907745-7672-470e-a803-a2f8feb52944',
capacity: 10,
co2e: 1000,
})
@Post()
@Response<BadRequest>(400, 'Request was invalid')
@Response<ValidateError>(422, 'Validation Failed')
@SuccessResponse('201')
public async post(@Body() body: Certificate.Request): Promise<Certificate.Response> {
this.log.info({ identity: this.identity, body })

const formatted = Object.keys(body).reduce(
(out, key) => ({
[camelToSnake(key)]: body[key],
...out,
}),
{}
)

return {
message: 'ok',
result: await this.db.insert('certificate', formatted),
}
}

/**
*
* @summary Lists all local certificates
* TODO - combine where so with all possible options e.g.
* columns so it's not a default and will be rendered in swagger
*/

@Get('/')
public async getAll(@Query() createdAt?: DATE): Promise<Certificate.Response> {
const where: Record<string, number | string | DATE> = {}
if (createdAt) where.created_at = createdAt

return {
message: 'ok',
certificates: await this.db.get('certificate', where),
}
}

/**
* @summary returns certificate by id
* @example id "52907745-7672-470e-a803-a2f8feb52944"
*/
@Response<ValidateError>(422, 'Validation Failed')
@Response<NotFound>(404, '<item> not found')
@Response<BadRequest>(400, 'ID must be supplied in UUID format')
@Get('{id}')
public async getById(@Path() id: UUID): Promise<Certificate.Response> {
if (!id) throw new BadRequest()

return {
message: 'ok',
certificate: await this.db.get('certificate', { id }),
}
}
}
86 changes: 0 additions & 86 deletions src/controllers/v1/example/index.ts

This file was deleted.

23 changes: 15 additions & 8 deletions src/lib/db/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import knex, { Knex } from 'knex'

import { pgConfig } from './knexfile'
import { HEX, UUID } from '../../models/strings'
import { DATE, HEX, UUID } from '../../models/strings'
import { TransactionApiType, TransactionState, TransactionType } from '../../models/transaction'
import { NotFound } from '../error-handler'

const tablesList = ['attachment', 'example', 'transaction', 'processed_blocks'] as const
const tablesList = ['attachment', 'certificate', 'transaction', 'processed_blocks'] as const
type TABLES_TUPLE = typeof tablesList
type TABLE = TABLES_TUPLE[number]

Expand All @@ -21,7 +21,7 @@ export interface AttachmentRow {
filename: string | null
size: number | null
ipfs_hash: string
created_at: Date
created_at: DATE
}

export interface TransactionRow {
Expand All @@ -34,12 +34,12 @@ export interface TransactionRow {
updated_at: Date
}

export interface ExampleRow {
export interface CertificateRow {
id: UUID
createdAt: Date
}

export type Entities = ExampleRow | TransactionRow | AttachmentRow
export type Entities = CertificateRow | TransactionRow | AttachmentRow

function restore0x(input: ProcessedBlockTrimmed): ProcessedBlock {
return {
Expand Down Expand Up @@ -73,13 +73,18 @@ export default class Database {
}

// generics methods
insert = async (model: keyof Models<() => QueryBuilder>, record: Record<string, string | number>) => {
insert = async (
model: keyof Models<() => QueryBuilder>,
record: Record<string, string | number>
): Promise<keyof Entities> => {
const query = this.db()[model]

// TODO address indexer (create a backlog item)
if (model == 'processed_blocks') {
return query().insert(trim0x(record as ProcessedBlock))
}

return query().insert(record).returning('*')
return query().insert(record)
}

delete = async (model: keyof Models<() => QueryBuilder>, where: Record<string, string | number>) => {
Expand Down Expand Up @@ -115,7 +120,9 @@ export default class Database {

// TODO some methods could be generic as well, e.g. insert/get for event processor indexer
findLocalIdForToken = async (tokenId: number): Promise<UUID | null> => {
const result = (await Promise.all([this.db().example().select(['id']).where({ latest_token_id: tokenId })])) as {
const result = (await Promise.all([
this.db().certificate().select(['id']).where({ latest_token_id: tokenId }),
])) as {
id: UUID
}[][]
const flatten = result.reduce((acc, set) => [...acc, ...set], [])
Expand Down
Loading

0 comments on commit 96d21b6

Please sign in to comment.