Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
236 changes: 236 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
[![CI](https://github.com/AssetPortal/assets-api/actions/workflows/ci.yaml/badge.svg)](https://github.com/AssetPortal/assets-api/actions/workflows/ci.yaml)[![Staging](https://github.com/AssetPortal/assets-api/actions/workflows/staging.yaml/badge.svg)](https://github.com/AssetPortal/assets-api/actions/workflows/staging.yaml)[![Production](https://github.com/AssetPortal/assets-api/actions/workflows/production.yaml/badge.svg)](https://github.com/AssetPortal/assets-api/actions/workflows/production.yaml)
# Assets API

## Overview

The Assets API provides endpoints to manage assets. This document describes the available endpoints, their expected input, and output.

---

## Features

- Health check endpoint
- Authentication nonce retrieval
- Image upload
- Asset creation, retrieval, and management

---

## Authentication

The endpoints that require authentication needs to have three headers in their request:

- `"X-Message"`: A valid nonce obtained from the `GET /nonce` endpoint. This nonce is used to prevent replay attacks.
- `"X-Signature"`: The message signed with the private key of the account. This signature is used to verify the authenticity of the request.
- `"X-Address"`: The address of the account used to sign the message. This address is used to identify the user making the request.

### Errors

- **400 Bad Request**: The message was not generated with the `GET /nonce`.
- **401 Unauthorized**: Invalid or expired token.
- **500 internal server error**: Internal error.

## API Endpoints

### **GET /health**

#### Description
Checks if the API server is running.

#### Response
- **200 OK**: Indicates the server is operational.

#### Example Response
Empty

### **GET /nonce**

#### Description
Retrieves a nonce for authentication. It must be signed and set to the `MESSAGE` header in the endpoints that require authentication.

#### Response
- **200 OK**: It returns the nonce.

#### Example Response

```json
{
"ok": true,
"data": {
"id": 9,
"token": "db1dce7837b0976fe042cda63c8129e4ea396407158cc494c4b1250f368d58a8",
"created_at": "2025-02-02T18:52:04.3747-03:00",
"expires_at": "2025-02-02T18:57:04.3747-03:00",
"used": false
}
}
```

### **GET /assets**

#### Description
Retrieves a list of assets.

### Query arguments
- **address**: string. It must be a valid polkadot address.
- **id**: string
- **order**: string. It orders the results using a field. It must be `id`, `address` or `created_at`.
- **ascending**: bool. It defines if the order is ascending or descendingl It's used along with `order`.
- **limit**: int. The maximum number of items to return. If not specified or greater than the maximum limit, it defaults to 100.
- **offset**: int. The number of items to skip before starting to collect the result set. Defaults to 0 if not specified.

#### Response
- **200 OK** with the list of assets.
- **400 Bad Request** if the input is invalid.

#### Example Response

```json
{

"ok": true,
"data": [
{
"id": "asset_id",
"description": "asset_description",
"address": "owner",
"image": "asset_image_url",
"social": {
"twitter": "twitter_handle",
"facebook": "facebook_handle"
}
}
]
}
```

### **GET /assets/{id}**

#### Description
Retrieves an asset by its ID.

#### Response
- **200 OK** with the asset details.
- **404 Not Found** if the asset is not found.

#### Example Response
```json
{
"ok": true,
"data": {
"id": "asset_id",
"description": "asset_description",
"image": "asset_image_url",
"social": {
"twitter": "twitter_handle",
"facebook": "facebook_handle"
}
}
}
```
### **POST /assets**

#### Description
Creates a new asset. It requires authentication.

#### Request Body
```json
{
"ok": true,
"data": {
"id": "asset_id",
"description": "asset_description",
"image": "asset_image_url",
"social": {
"twitter": "twitter_handle",
"facebook": "facebook_handle"
}
}
}
```

#### Response
- **201 Created** with the created asset details.
- **400 Bad Request** if the input is invalid.
- **401 Unauthorized** if the authentication fails.

#### Example Response
```json
{
"ok": true,
"data": {
"id": "asset_id",
"description": "asset_description",
"image": "asset_image_url",
"social": {
"twitter": "twitter_handle",
"facebook": "facebook_handle"
}
}
}
```
### **PUT /assets/{id}**

#### Description
It updates an asset. Only its owner can do it. It requires authentication.

#### Request Body
```json
{
"ok": true
}

```
Note: at least one field is required.

#### Response
- **200 OK**
- **400 Bad Request** if the input is invalid.
- **401 Unauthorized** if the authentication fails.
- **404 Not Found** if the combination of `id` and `address` does not exist.

#### Example Response
```json
{
"ok": true
}
```
### **DELETE /assets/{id}**

#### Description
It deletes an asset. Only its owner can do it. It requires authentication.

#### Response
- **200 OK**
- **400 Bad Request** if the input is invalid.
- **401 Unauthorized** if the authentication fails.
- **404 Not Found** if the combination of `id` and `address` does not exist.

#### Example Response
```json
{
"ok": true
}
```

## Prerequisites
- Go 1.16 or later
- A running instance of the database
- Environment variables configured

## Testing
To run the tests, use the following command:

``` bash
go test ./...
```

## Contributing
Contributions are welcome! Please follow these steps to contribute:

1. Fork the repository.
2. Create a new branch (git checkout -b feature-branch).
3. Make your changes.
4. Commit your changes (git commit -am 'Add new feature').
5. Push to the branch (git push origin feature-branch).
6. Create a new Pull Request.
16 changes: 10 additions & 6 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZb
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pierrec/xxHash v0.1.5/go.mod h1:w2waW5Zoa/Wc4Yqe0wgrIYAGKqRMf7czn2HNKXmuL+I=
github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
Expand All @@ -24,26 +20,34 @@ github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITn
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
Expand Down
13 changes: 6 additions & 7 deletions packages/api/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"log"
"net/http"
"time"

"database/sql"

Expand Down Expand Up @@ -39,7 +38,7 @@ func main() {
logger.SetLevel(lvl)

// Open a database connection
sqlDB, err := sql.Open("postgres", cfg.RWDBURL)
sqlDB, err := sql.Open("postgres", cfg.DatabaseConfiguration.URL)
if err != nil {
log.Fatalf("failed to connect to PostgreSQL: %v", err)
}
Expand All @@ -51,10 +50,10 @@ func main() {
}

// Set connection pool settings
sqlDB.SetMaxOpenConns(10) // Maximum open connections
sqlDB.SetMaxIdleConns(5) // Maximum idle connections
sqlDB.SetConnMaxIdleTime(5 * time.Minute) // Idle timeout
sqlDB.SetConnMaxLifetime(30 * time.Minute) // Max lifetime of a connection
sqlDB.SetMaxOpenConns(cfg.DatabaseConfiguration.MaxOpenConns) // Maximum open connections
sqlDB.SetMaxIdleConns(cfg.DatabaseConfiguration.MaxIdleConns) // Maximum idle connections
sqlDB.SetConnMaxIdleTime(cfg.DatabaseConfiguration.ConnMaxIdleTime) // Idle timeout
sqlDB.SetConnMaxLifetime(cfg.DatabaseConfiguration.ConnMaxLifetime) // Max lifetime of a connection

// Initialize Bun with database/sql and PostgreSQL dialect
db := bun.NewDB(sqlDB, pgdialect.New())
Expand All @@ -64,7 +63,7 @@ func main() {
Timeout: cfg.AuthConfiguration.HTTPTimeout,
}
authClient := auth.NewPolkadotClient(cfg.AuthConfiguration.APIURL, httpClient)
authMiddleware := middleware.NewPolkadotAuth(tokensRepository, authClient)
authMiddleware := middleware.NewPolkadotAuth(tokensRepository, authClient, cfg.AuthConfiguration.Enabled)

staticCreds := credentials.NewStaticCredentialsProvider(
cfg.BucketConfiguration.AccessKey,
Expand Down
11 changes: 6 additions & 5 deletions packages/api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ module github.com/AssetPortal/assets-api
go 1.23.3

require (
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/aws/aws-sdk-go-v2 v1.33.0
github.com/aws/aws-sdk-go-v2/config v1.29.1
github.com/aws/aws-sdk-go-v2/credentials v1.17.54
github.com/aws/aws-sdk-go-v2/service/s3 v1.73.2
github.com/caarlos0/env/v11 v11.2.2
github.com/ggicci/httpin v0.19.0
github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/httprate v0.14.1
github.com/go-chi/render v1.0.3
github.com/lib/pq v1.10.9
github.com/rs/cors v1.11.1
Expand All @@ -37,6 +37,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.10 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.9 // indirect
github.com/aws/smithy-go v1.22.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ggicci/owl v0.8.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
Expand All @@ -52,9 +53,9 @@ require (
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading