Skip to content

Commit

Permalink
/related now works with package addresses and uses slugs
Browse files Browse the repository at this point in the history
  • Loading branch information
fariedt committed Apr 2, 2024
1 parent 37d3b31 commit 7887171
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 85 deletions.
118 changes: 73 additions & 45 deletions backend/backend/backend-src/lib/aws.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import fs from 'fs'
import {
DeleteItemCommand,
DynamoDBClient,
GetItemCommand,
PutItemCommand,
Expand All @@ -9,11 +10,14 @@ import {
TransactGetItemsCommand,
TransactWriteItem,
TransactWriteItemsCommand,
UpdateItemCommand,
} from '@aws-sdk/client-dynamodb'
import {marshall, unmarshall} from '@aws-sdk/util-dynamodb'
import {DeleteObjectCommand, PutObjectCommand, S3Client} from '@aws-sdk/client-s3'
import {getSignedUrl} from '@aws-sdk/s3-request-presigner'

import slug from 'slug'

import * as IFace from './interfaces'

const BUCKET = 's3m-contracts-dev'
Expand Down Expand Up @@ -368,83 +372,107 @@ export async function getAddressEventsDB(address: string) {
else return response.Items?.map(item => unmarshall(item)) ?? []
}

export async function getRelatedDB(address: string): Promise<Record<string, string>[]> {
export async function listRelatedDB(pkgaddress: string) {
const command = new QueryCommand({
TableName: RELATED_TABLE,
KeyConditionExpression: 'address = :address',
ExpressionAttributeValues: {
':address': {S: pkgaddress},
},
ProjectionExpression: 'slug, label, wallet_address',
})

const response = await dbclient.send(command)

if (response.Items === undefined) return []
else return response.Items?.map(item => unmarshall(item)) ?? []
}

export async function getRelatedDB(pkgaddress: string, slug: string) {
const command = new GetItemCommand({
TableName: RELATED_TABLE,
Key: {
address: {S: address},
address: {S: pkgaddress},
slug: {S: slug},
},
ProjectionExpression: 'related',
})

const response = await dbclient.send(command)
if (response.Item === undefined) return []
if (response.Item === undefined) return null

return (unmarshall(response.Item) ?? {}).related
return unmarshall(response.Item)
}

export async function createRelatedDB(address: string, data: IFace.IRelatedCreate) {
// get existing item, if any
const existing: Record<string, string>[] = await getRelatedDB(address)

const already = existing.filter(item => item.label == data.label)
if (already.length != 0) {
return {error: `label '${data.label}' already exists for address: ${already[0].address}`}
}
const label_slug = slug(data.label)
const existing = await getRelatedDB(address, label_slug)
if (existing !== null) return {error: `label '${data.label}' already exists for address: ${existing.address}`}

const command = new PutItemCommand({
TableName: RELATED_TABLE,
Item: marshall({
address: address,
related: existing.concat({label: data.label, address: data.address}),
slug: label_slug,
label: data.label,
wallet_address: data.address,
}),
})

return await dbclient.send(command)
}

export async function deleteRelatedDB(address: string, data: IFace.IRelatedDelete) {
export async function deleteRelatedDB(address: string, slug: string) {
// get existing item, if any
const existing = await getRelatedDB(address)
if (existing.length == 0) return

const filtered = existing.filter(item => item.label != data.label)
const existing = await getRelatedDB(address, slug)
if (existing === null) return

const command = new PutItemCommand({
const command = new DeleteItemCommand({
TableName: RELATED_TABLE,
Item: marshall({
address: address,
related: filtered,
}),
Key: {
address: {S: address},
slug: {S: slug},
},
})

return await dbclient.send(command)
}

export async function modifyRelatedDB(address: string, data: IFace.IRelatedModify) {
// get existing item, if any
const existing = await getRelatedDB(address)
if (existing.length == 0) return

let item_address = ''
let item_idx = 0
for (let idx = 0; idx < existing.length; idx++) {
if (existing[idx].label == data.label) {
item_address = existing[idx].address
item_idx = idx
} else if (existing[idx].label == data.new_label) {
return {error: `label '${data.new_label}' already exists for address: ${existing[idx].address}`}
}
}
existing[item_idx].label = data.new_label
export async function modifyRelatedDB(address: string, existing_slug: string, data: IFace.IRelatedModify) {
// check if the new label already exists
const new_slug = slug(data.label)
const maybe = await getRelatedDB(address, new_slug)
if (maybe !== null) return {error: `label '${data.label}' already exists for address: ${address}`}

const command = new PutItemCommand({
TableName: RELATED_TABLE,
Item: marshall({
address: address,
related: existing,
}),
const existing = await getRelatedDB(address, existing_slug)
if (existing == null) return {error: `entry '${existing_slug}' does not exist for address: ${address}`}

let items: TransactWriteItem[] = [
{
Delete: {
TableName: RELATED_TABLE,
Key: {
address: {S: address},
slug: {S: existing_slug},
},
},
},
{
Put: {
TableName: RELATED_TABLE,
Item: marshall({
address: address,
slug: new_slug,
label: data.label,
wallet_address: existing.wallet_address,
}),
},
},
]

const txWriteCommand = new TransactWriteItemsCommand({
TransactItems: items,
})

return await dbclient.send(command)
await dbclient.send(txWriteCommand)
}
16 changes: 3 additions & 13 deletions backend/backend/backend-src/lib/checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,19 +268,9 @@ export async function validRelatedDelete(data: IFace.IRelatedDelete): Promise<IF
export async function validRelatedModify(data: IFace.IRelatedModify): Promise<IFace.IValid> {
const stringFields = ['label', 'new_label']

for (const field of stringFields) {
if (!(field in data)) {
console.log(`missing field: ${field}`)
return {error: `missing field: ${field}`}
}
}

if (data.label.trim() == '' || data.new_label.trim() == '') {
console.log('missing field')
return {error: 'missing field'}
} else {
data.label = data.label.trim()
data.new_label = data.new_label.trim()
if (!('label' in data) || data.label.trim() == '') {
console.log(`missing field: label`)
return {error: `missing field: label`}
}

return {error: '', data: data}
Expand Down
1 change: 0 additions & 1 deletion backend/backend/backend-src/lib/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ export interface IRelatedDelete {

export interface IRelatedModify {
label: string
new_label: string
}

export function reqToCreated(data: ICreatePackageRequest, s3key: string | undefined): IPackageCreated {
Expand Down
46 changes: 20 additions & 26 deletions backend/backend/backend-src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,26 +145,26 @@ app.get('/address-events/:address', async (req, res) => {
}
})

app.get('/related/:address', async (req, res) => {
const {address} = req.params
if (Checks.isValidAddress(address)) {
app.get('/related/:pkgaddress', async (req, res) => {
const {pkgaddress} = req.params
if (Checks.isValidPackage(pkgaddress)) {
res.status(200).json({
status: 'ok',
related: await AWS.getRelatedDB(address),
related: await AWS.listRelatedDB(pkgaddress),
})
} else {
res.status(400).json({
status: 'error',
message: `invalid address: ${address}`,
message: `invalid package address: ${pkgaddress}`,
})
}
})

app.post('/related/:address', async (req, res) => {
const {address} = req.params
app.post('/related/:pkgaddress', async (req, res) => {
const {pkgaddress} = req.params
const v = await Checks.validRelatedCreate(req.body)
if (Checks.isValidAddress(address) && v.error === '') {
const ret = await AWS.createRelatedDB(address, v.data as IFace.IRelatedCreate)
if (Checks.isValidPackage(pkgaddress) && v.error === '') {
const ret = await AWS.createRelatedDB(pkgaddress, v.data as IFace.IRelatedCreate)
if (ret !== undefined && 'error' in ret) {
res.status(400).json({
status: 'error',
Expand All @@ -183,37 +183,31 @@ app.post('/related/:address', async (req, res) => {
} else {
res.status(400).json({
status: 'error',
message: `invalid address: ${address}`,
message: `invalid address: ${pkgaddress}`,
})
}
})

app.post('/related/:address/delete', async (req, res) => {
const {address} = req.params
const v = await Checks.validRelatedDelete(req.body)
if (Checks.isValidAddress(address) && v.error === '') {
await AWS.deleteRelatedDB(address, v.data as IFace.IRelatedDelete)
app.delete('/related/:pkgaddress/:slug', async (req, res) => {
const {pkgaddress, slug} = req.params
if (Checks.isValidPackage(pkgaddress) && slug.trim() != '') {
await AWS.deleteRelatedDB(pkgaddress, slug)
res.status(200).json({
status: 'ok',
})
} else if (v.error != '') {
res.status(400).json({
status: 'error',
message: v.error,
})
} else {
res.status(400).json({
status: 'error',
message: `invalid address: ${address}`,
message: `invalid address: ${pkgaddress}`,
})
}
})

app.post('/related/:address/modify', async (req, res) => {
const {address} = req.params
app.patch('/related/:pkgaddress/:slug', async (req, res) => {
const {pkgaddress, slug} = req.params
const v = await Checks.validRelatedModify(req.body)
if (Checks.isValidAddress(address) && v.error === '') {
const ret = await AWS.modifyRelatedDB(address, v.data as IFace.IRelatedModify)
if (Checks.isValidPackage(pkgaddress) && v.error === '') {
const ret = await AWS.modifyRelatedDB(pkgaddress, slug, v.data as IFace.IRelatedModify)
if (ret !== undefined && 'error' in ret) {
res.status(400).json({
status: 'error',
Expand All @@ -232,7 +226,7 @@ app.post('/related/:address/modify', async (req, res) => {
} else {
res.status(400).json({
status: 'error',
message: `invalid address: ${address}`,
message: `invalid address: ${pkgaddress}`,
})
}
})
Expand Down
2 changes: 2 additions & 0 deletions backend/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"cors": "^2.8.5",
"ejs": "^3.1.9",
"express": "^4.18.2",
"slug": "^9.0.0",
"zip-lib": "^1.0.3"
},
"devDependencies": {
Expand All @@ -29,6 +30,7 @@
"@types/ejs": "^3.1.5",
"@types/express": "^4.17.21",
"@types/node": "^20.11.17",
"@types/slug": "^5.0.8",
"nodemon": "^3.0.3",
"prettier": "^3.2.5",
"ts-loader": "^9.5.1",
Expand Down
15 changes: 15 additions & 0 deletions backend/backend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7887171

Please sign in to comment.