Skip to content

Commit 5da659d

Browse files
committed
refactor: pr feedback and directory restructuring 👌
1 parent 9425096 commit 5da659d

14 files changed

+97
-73
lines changed

.env.example

+2
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,6 @@ ARWEAVE_GATEWAY_URL=https://arweave.net/
5252
SOUND_XYZ_KEY=asdf
5353

5454
# Seeds API config
55+
SEEDS_API_PORT=3005
56+
SEEDS_API_VERSION=1
5557
PERMITTED_ADMIN_ADDRESSES=0x1,0x2

.mocharc.json

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"bail": true,
3+
"exit": true,
4+
"extension": ["js", "ts"],
5+
"file": ["./test/pretest.ts"],
6+
"recursive": true,
7+
"require": "ts-node/register",
8+
"timeout": "15000",
9+
"slow": 1000,
10+
"watch-files": ["src/**/*.ts", "test/**/*.ts", "test/**/*.ts"]
11+
}

README.md

+11
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ In production, we recommend running the script once a minute to keep it up to da
5757
watch -n 60 "yarn start >> ./log" 2>&1
5858
```
5959

60+
If you want to allow adjustmends to the [CRDT](/crdt.md) seeds via the Seeds API, run:
61+
```
62+
yarn serve-seeds-api
63+
```
64+
6065
## Operations
6166
Sometimes things fail (eg: an offchain API is down). This is fine and expected. Things should continue as expected on the next run of the script. Most NFTs/Tracks/Platforms that cause failure/errors are moved into an error queue and retried a few times after some delay so that they don't block progress for the rest of the indexer.
6267

@@ -84,6 +89,12 @@ There are a few design goals for the system:
8489
- Allow extensions with additional metadata or data transformations being added without requiring a DB rebuild nor re-processing from the start
8590
- Allow for decentralization and even some consensus without any co-ordination
8691

92+
## Tests
93+
The Seeds API is covered by end-to-end tests. More test coverage to follow as the codebase matures. To run tests locally:
94+
```
95+
yarn test:watch
96+
```
97+
8798
## Contributing
8899
The repo is still early and not hyper-polished or perfectly documented. Contributor guidelines are not ready yet, clear development docs and style/standard expectations are not extremely well documented yet. Interfaces are not well documented yet. Keep this in mind, so if you'd like to contribute:
89100
- First, reach out on Discord and connect with the team (https://discord.gg/8YS3peb62f) so we can help guide you in contributing

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
"start-logs": "NODE_EXTRA_CA_CERTS=./db-ssl-certificate.pem ts-node src/index.ts 2>&1 | tee /tmp/debug",
1313
"cli": "NODE_EXTRA_CA_CERTS=./db-ssl-certificate.pem ts-node src/cli/cli.ts",
1414
"build": "tsc --skipLibCheck",
15-
"test": "source .env && NODE_ENV=test mocha -r ts-node/register test/**/*.ts --timeout 15000",
16-
"test:watch": "source .env && NODE_ENV=test mocha -r ts-node/register --watch --watch-files src/**/*.ts,test/**/*.ts test/**/*.ts --timeout 15000",
15+
"test": "source .env && NODE_ENV=test mocha",
16+
"test:watch": "source .env && NODE_ENV=test mocha --watch",
1717
"bootstrap-db": "NODE_EXTRA_CA_CERTS=./db-ssl-certificate.pem ts-node db/bootstrap.ts",
1818
"serve": "source .env && DEBUG=graphile-build-pg NODE_EXTRA_CA_CERTS=./db-ssl-certificate.pem npx ts-node ./src/serve.ts --cors",
1919
"serve-seeds-api": "source .env && NODE_EXTRA_CA_CERTS=./db-ssl-certificate.pem npx ts-node ./src/serve_seeds_api.ts",
File renamed without changes.

src/seeds/server.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import '../types/env';
2+
import cors from 'cors';
3+
import express from 'express';
4+
5+
import { authMiddleware } from './middleware';
6+
import { persistSeed, validateSeed } from './types';
7+
8+
const apiVersionPrefix = `/v${process.env.SEEDS_API_VERSION || '1'}`;
9+
10+
export const createSeedsAPIServer = () => {
11+
const app = express();
12+
app.use(express.json());
13+
app.use(cors({ origin: true }));
14+
app.use(authMiddleware);
15+
16+
app.post(`${apiVersionPrefix}/seeds/`, async (req, res) => {
17+
try {
18+
validateSeed(req.body)
19+
} catch (e: any) {
20+
return res.status(422).send({ error: e.message });
21+
}
22+
23+
try {
24+
await persistSeed(req.body)
25+
res.sendStatus(200);
26+
} catch (e: any) {
27+
res.status(500).send({ error: e.message });
28+
}
29+
});
30+
31+
return app;
32+
}

src/types/seeds.ts src/seeds/types.ts

+10-9
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11

22
import { Table } from '../db/db';
33
import db from '../db/sql-db'
4-
5-
import { ArtistProfileKeys } from './artist';
6-
import { getCrdtUpdateMessage, getCrdtUpsertMessage } from './message'
7-
import { NFTContractTypeName, NFTFactoryKeys, NFTStandard } from './nft';
8-
import { MusicPlatform, MusicPlatformKeys, MusicPlatformType } from './platform';
9-
import { ProcessedTrackKeys } from './track';
4+
import { getCrdtUpdateMessage, getCrdtUpsertMessage } from '../types/message'
5+
import { NFTContractTypeName, NFTStandard } from '../types/nft';
6+
import { MusicPlatform, MusicPlatformType } from '../types/platform';
107

118
enum SeedEntities {
129
'platforms',
@@ -21,25 +18,29 @@ enum PlatformsRequiredKeys {
2118
NAME = 'name',
2219
TYPE = 'type',
2320
}
21+
const PlatformKeys = ['id', 'type', 'name'];
2422

2523
enum NFTFactoriesRequiredKeys {
2624
ID = 'id',
2725
PLATFORM_ID = 'platformId',
28-
CONTRACT_TYPE = 'contractType', // validated against `NFTContractTypeName`
29-
STANDARD = 'standard', // validated against `NFTStandard`
26+
CONTRACT_TYPE = 'contractType',
27+
STANDARD = 'standard',
3028
AUTO_APPROVE = 'autoApprove',
3129
APPROVED = 'approved'
3230
}
31+
const NFTFactoryKeys = ['id', 'platformId', 'contractType', 'name', 'symbol', 'typeMetadata', 'standard', 'autoApprove', 'approved'];
3332

3433
enum ArtistProfilesRequiredKeys {
3534
ARTIST_ID = 'artistId',
3635
PLATFORM_ID = 'platformId',
3736
}
37+
const ArtistProfileKeys = ['platformInternalId', 'artistId', 'name', 'platformId', 'avatarUrl', 'websiteUrl'];
3838

3939
enum ProcessedTracksRequiredKeys {
4040
ARTIST_ID = 'artistId',
4141
PLATFORM_ID = 'platformId',
4242
}
43+
const ProcessedTrackKeys = ['platformInternalId', 'title', 'slug', 'platformId', 'description', 'websiteUrl', 'artistId', 'lossyAudioURL', 'lossyAudioIPFSHash', 'lossyArtworkURL', 'lossyArtworkIPFSHash'];
4344

4445
type SeedPayload = {
4546
entity: SeedEntity,
@@ -52,7 +53,7 @@ export const validateSeed = (payload: SeedPayload): void => {
5253
const validatorFunctions = {
5354
'platforms': [
5455
() => minimumKeysPresent(payload, Object.values(PlatformsRequiredKeys)),
55-
() => onlyValidKeysPresent(payload, MusicPlatformKeys),
56+
() => onlyValidKeysPresent(payload, PlatformKeys),
5657
() => typeValidator(payload, 'type', MusicPlatformType),
5758
],
5859
'nftFactories': [

src/serve_seeds_api.ts

+3-35
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,5 @@
1-
import 'dotenv/config';
2-
import './types/env';
3-
import cors from 'cors';
4-
import express from 'express';
1+
import { createSeedsAPIServer } from './seeds/server';
52

6-
import { persistSeed, validateSeed } from './types/seeds';
7-
import { authMiddleware } from './utils/seedsMiddleware';
8-
9-
const apiVersionPrefix = '/v1';
10-
11-
const app = express();
12-
app.use(express.json());
13-
app.use(cors({ origin: true }));
14-
app.use(authMiddleware);
15-
16-
app.post(`${apiVersionPrefix}/seeds/`, async (req, res) => {
17-
try {
18-
validateSeed(req.body)
19-
} catch (e: any) {
20-
return res.status(422).send({ error: e.message });
21-
}
22-
23-
try {
24-
await persistSeed(req.body)
25-
res.sendStatus(200);
26-
} catch (e: any) {
27-
res.status(500).send({ error: e.message });
28-
}
3+
createSeedsAPIServer().listen(process.env.SEEDS_API_PORT, () => {
4+
console.log(`Server running on port ${process.env.SEEDS_API_PORT}`);
295
});
30-
31-
if (process.env.NODE_ENV !== 'test') {
32-
app.listen(3005, () => {
33-
console.log(`Server running on port ${3005}`);
34-
});
35-
}
36-
37-
module.exports = app;

src/types/artist.ts

-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ export type ArtistProfile = TimeField & {
1616
websiteUrl?: string;
1717
}
1818

19-
export const ArtistProfileKeys = ['platformInternalId', 'artistId', 'name', 'platformId', 'avatarUrl', 'websiteUrl'];
20-
2119
export const mapArtist = (artistProfile: ArtistProfile): Artist => {
2220
return {
2321
name: artistProfile.name,

src/types/nft.ts

-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ export type NftFactory = Contract & {
6969
autoApprove: boolean; // automaticlly approve nfts generated by this facotry
7070
approved: boolean; // should index nfts generated by this factory
7171
}
72-
export const NFTFactoryKeys = ['id', 'platformId', 'contractType', 'name', 'symbol', 'typeMetadata', 'standard', 'autoApprove', 'approved'];
7372

7473
export type NFTContractType = {
7574
contractCalls: ValidContractNFTCallFunction[],

src/types/platform.ts

-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ export type MusicPlatform = {
3131
type: MusicPlatformType
3232
name: string
3333
}
34-
export const MusicPlatformKeys: Array<keyof MusicPlatform> = ['id', 'type', 'name'];
3534

3635
export type PlatformMapper = {
3736
mapNFTsToTrackIds: MapNFTsToTrackIds

src/types/track.ts

-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ export type ProcessedTrack = Record & {
2626
artistId: string;
2727
} & ProcessedTrackAudio & ProcessedTrackArtwork;
2828

29-
export const ProcessedTrackKeys = ['platformInternalId', 'title', 'slug', 'platformId', 'description', 'websiteUrl', 'artistId', 'lossyAudioURL', 'lossyAudioIPFSHash', 'lossyArtworkURL', 'lossyArtworkIPFSHash'];
30-
3129
export const mergeProcessedTracks = async (newProcessedTracks: ProcessedTrack[], dbClient: DBClient, prioritizeNew: boolean) => {
3230
const platformInternalIds = newProcessedTracks.map(t => t.platformInternalId);
3331
const existingProcessedTracks = await dbClient.getRecords<ProcessedTrack>(Table.processedTracks,

test/pretest.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export const TEST_ADMIN_WALLET = {
2+
address: '0x8eb97c37B0BDe7A09eA5b49D6D97cd57e10559ba',
3+
privateKey: '0xe07cc69757e3b261ffeb70df20f832ae74da57e11dd440a5da75377abe8caefc',
4+
}
5+
6+
console.log('-------------------------------------');
7+
console.log('configuring tests...');
8+
9+
process.env.PERMITTED_ADMIN_ADDRESSES = TEST_ADMIN_WALLET.address;
10+
11+
console.log(`setting PERMITTED_ADMIN_ADDRESSES...`);
12+
console.log('starting tests...');
13+
console.log('-------------------------------------');

test/serve_seeds_api.ts test/seeds/server.ts

+13-21
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,18 @@
11
import supertest from 'supertest'
22
import web3 from 'web3';
33

4-
import { MusicPlatformType } from '../src/types/platform';
4+
import { createSeedsAPIServer } from '../../src/seeds/server';
5+
import { TEST_ADMIN_WALLET } from '../pretest';
56

6-
describe('Seeds API', () => {
7+
const throwDBHint = (err: any) => { throw new Error(`${err.message}\nHINT: tests run against dev DB. Ensure that DB running, migrated, and working as expected`) };
8+
9+
describe('Seeds API server', () => {
710
let app: any;
811
const Web3 = new web3();
12+
const wallet = Web3.eth.accounts.privateKeyToAccount(TEST_ADMIN_WALLET.privateKey)
913

10-
// TODO: rather stub out the permittedAdminAddresses() to contain this address
11-
// instead of having to put the known address into .env
12-
const testAdminWallet = {
13-
address: '0x8eb97c37B0BDe7A09eA5b49D6D97cd57e10559ba',
14-
privateKey: '0xe07cc69757e3b261ffeb70df20f832ae74da57e11dd440a5da75377abe8caefc',
15-
}
16-
17-
const wallet = Web3.eth.accounts.privateKeyToAccount(testAdminWallet.privateKey)
18-
19-
// TODO: spin up a test DB instead of using development
2014
before(() => {
21-
app = require('../src/serve_seeds_api.ts');
15+
app = createSeedsAPIServer();
2216
});
2317

2418
describe('un-authenticated', () => {
@@ -37,7 +31,6 @@ describe('Seeds API', () => {
3731
.set('x-signature', signature)
3832
.expect(403)
3933
.end((err,res) => { if (err) throw err });
40-
4134
})
4235
})
4336

@@ -53,7 +46,6 @@ describe('Seeds API', () => {
5346
.set('x-signature', signature)
5447
.expect(422, { error: 'unknown seed entity' })
5548
.end((err,res) => { if (err) throw err });
56-
5749
})
5850
})
5951

@@ -84,7 +76,7 @@ describe('Seeds API', () => {
8476

8577
describe('with unsupported fields', () => {
8678
it('returns an error', () => {
87-
const body = { entity: 'platforms', data: { id: 'potato', name: 'potato', type: MusicPlatformType.sound, hackyou: 'boo' } }
79+
const body = { entity: 'platforms', data: { id: 'potato', name: 'potato', type: 'sound', hackyou: 'boo' } }
8880
const signature = wallet.sign(JSON.stringify(body)).signature
8981

9082
supertest(app).post(endpoint).send(body)
@@ -96,13 +88,13 @@ describe('Seeds API', () => {
9688

9789
describe('with a valid payload', () => {
9890
it('returns a 200', () => {
99-
const body = { entity: 'platforms', data: { id: 'jamboni', name: 'Jamboni Jams', type: MusicPlatformType.sound } }
91+
const body = { entity: 'platforms', data: { id: 'jamboni', name: 'Jamboni Jams', type: 'sound' } }
10092
const signature = wallet.sign(JSON.stringify(body)).signature;
10193

10294
supertest(app).post(endpoint).send(body)
10395
.set('x-signature', signature)
10496
.expect(200)
105-
.end((err,res) => { if (err) throw err });
97+
.end((err,res) => { if (err) { throwDBHint(err) } });
10698
})
10799
it('persists the seed');
108100
})
@@ -177,7 +169,7 @@ describe('Seeds API', () => {
177169
supertest(app).post(endpoint).send(body)
178170
.set('x-signature', signature)
179171
.expect(200)
180-
.end((err,res) => { if (err) throw err });
172+
.end((err,res) => { if (err) throwDBHint(err) });
181173
})
182174
it('persists the seed');
183175
})
@@ -222,7 +214,7 @@ describe('Seeds API', () => {
222214
supertest(app).post(endpoint).send(body)
223215
.set('x-signature', signature)
224216
.expect(200)
225-
.end((err,res) => { if (err) throw err });
217+
.end((err,res) => { if (err) throwDBHint(err) });
226218
})
227219
it('persists the seed');
228220
})
@@ -267,7 +259,7 @@ describe('Seeds API', () => {
267259
supertest(app).post(endpoint).send(body)
268260
.set('x-signature', signature)
269261
.expect(200)
270-
.end((err,res) => { if (err) { throw err } });
262+
.end((err,res) => { if (err) { throwDBHint(err) } });
271263
})
272264
it('persists the seed');
273265
})

0 commit comments

Comments
 (0)