Skip to content

Commit

Permalink
add a SQL database to manage tracks
Browse files Browse the repository at this point in the history
  • Loading branch information
il3ven committed May 2, 2023
1 parent ae2519b commit cd79c73
Show file tree
Hide file tree
Showing 8 changed files with 642 additions and 104 deletions.
1 change: 0 additions & 1 deletion database/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
**/*.js
db
12 changes: 12 additions & 0 deletions database/knexfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import path from "path";

const config = {
client: "better-sqlite3",
connection: {
// path.resolve means that a .sqlite3 will be created at the current working directory
filename: path.resolve("./data/neume.sqlite3"),
},
useNullAsDefault: true,
};

export default config;
73 changes: 73 additions & 0 deletions database/migrations/20230318200321_schema.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
export const up = async function (knex) {
await knex.schema.createTable("tracks", (table) => {
table.string("version").notNullable();
table.string("title").notNullable();
table.string("duration");

table.string("artist_version").notNullable();
table.string("artist_name").notNullable();
table.string("artist_address").nullable();

table.string("platform_name").index().notNullable();
table.string("platform_version").notNullable();
table.string("platform_uri").notNullable();

table.string("erc721_version").notNullable();
table.string("erc721_address").notNullable();
table.string("erc721_uri");
table.json("erc721_metadata");

table.integer("lastUpdatedAt").index().notNullable();
table.string("uid");
table.primary("uid");
});

await knex.schema.createTable("manifestations", (table) => {
table.string("version").notNullable();
table.string("uri").notNullable();
table.string("mimetype").notNullable();

table.string("uid");
table.foreign(["uid"]).references(["uid"]).on("tracks");
table.primary(["uid", "uri"]);
});

await knex.schema.createTable("tokens", (table) => {
table.string("id").notNullable();
table.string("uri");
table.json("metadata");

table.string("uid");
table.foreign(["uid"]).references(["uid"]).on("tracks");
table.primary(["uid", "id"]);
});

await knex.schema.createTable("owners", (table) => {
table.integer("blockNumber").notNullable();
table.string("from").notNullable();
table.string("to").notNullable();
table.string("transactionHash").notNullable();
table.string("alias");

table.string("uid");
table.string("id");

table.foreign(["uid", "id"]).references(["uid", "id"]).on("tokens");
table.primary(["uid", "id", "transactionHash", "to"]);
});
};

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
export const down = async function (knex) {
await knex.schema.dropTable("owners");
await knex.schema.dropTable("tokens");
await knex.schema.dropTable("manifestations");
await knex.schema.dropTable("tracks");
};
23 changes: 23 additions & 0 deletions database/runMigration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Knex from "knex";
import path from "path";
import fs from "fs";
import { URL } from "url";

import config from "./knexfile.js";

// We don't use knex CLI because we need access to __dirname + '/migrations'
export default async function runMigration(type) {
const knex = Knex.default(config);
const dir = path.dirname(config.connection.filename);

if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}

const fn = knex.migrate[type];
await fn.call(knex.migrate, {
directory: new URL("./migrations", import.meta.url).pathname,
});

await knex.destroy();
}
237 changes: 237 additions & 0 deletions database/tracks.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import test from "ava";
import runMigration from "./runMigration.js";
import { tracksDB } from "./tracks.js";

test.beforeEach(async () => {
await runMigration("up");
await runMigration("down");
await runMigration("up");
});

const sample = [
{
version: "1.0.0",
title: "Blast",
uid: "polygon/106643/194",
duration: "PT05S",
artist: { version: "1.0.0", name: "jburn.lens", address: "106643" },
platform: { version: "1.0.0", name: "Lens", uri: "https://lens.xyz" },
erc721: {
version: "1.0.0",
tokens: [
{
id: "1",
uri: null,
metadata: null,
owners: [
{
from: "0x0000000000000000000000000000000000000000",
to: "0x77a395A6f7c6E91192697Abb207ea3c171F4B338",
blockNumber: 41988367,
transactionHash: "0xd622364913527f2d19ac5a09cba872df88a1bc8d37661bccac98ec2d38130787",
alias: "n0madz.lens",
},
],
},
],
address: "0xe8ebad85fe7eb36ed5b6f12ac95048c7d9bff15b",
uri: "ar://BCfUm5TsFP8VPpRtxtJq8Ue7mm_L-hhldRo3XrD-8Ck",
metadata: {
version: "2.0.0",
metadata_id: "dfa5863d-b325-47f4-93c7-d8af3d12e48e",
content:
"1 Wmatic to collect\n" +
"25% referral reward\n" +
"Nonexclusive Unlimited Usage Rights for owners\n" +
"\n" +
"Thanks everyone for the support on the beats lately, its been inspirational.",
external_url: "https://beatsapp.xyz/profile/jburn.lens",
image: "ipfs://bafybeieiuz7xqlrs4aq7gfd3h2ttkt5sjjw43dm57xk3cmpneeiadql4qa",
imageMimeType: "image/png",
name: "Blast",
tags: ["Song", ""],
animation_url: "ipfs://bafybeifgvqlonwa3m3rzgdzdix34ob57bap56fmiypsujewm74jruoonli",
mainContentFocus: "AUDIO",
contentWarning: null,
attributes: [
{ traitType: "type", displayType: "string", value: "audio" },
{ traitType: "genre", displayType: "string", value: "" },
{ traitType: "author", displayType: "string", value: "Jburn" },
],
media: [
{
type: "audio/mpeg",
altTag: "Audio file",
item: "ipfs://bafybeifgvqlonwa3m3rzgdzdix34ob57bap56fmiypsujewm74jruoonli",
},
],
locale: "en",
appId: "beats",
description:
"1 Wmatic to collect\n" +
"25% referral reward\n" +
"Nonexclusive Unlimited Usage Rights for owners\n" +
"\n" +
"Thanks everyone for the support on the beats lately, its been inspirational.",
},
},
manifestations: [
{
version: "1.0.0",
uri: "ipfs://bafybeieiuz7xqlrs4aq7gfd3h2ttkt5sjjw43dm57xk3cmpneeiadql4qa",
mimetype: "image",
},
{
version: "1.0.0",
uri: "ipfs://bafybeifgvqlonwa3m3rzgdzdix34ob57bap56fmiypsujewm74jruoonli",
mimetype: "audio",
},
],
},
{
version: "1.0.0",
title: "Blast",
uid: "polygon/9999/0011",
duration: "PT05S",
artist: { version: "1.0.0", name: "jburn.lens", address: "106643" },
platform: { version: "1.0.0", name: "Lens", uri: "https://lens.xyz" },
erc721: {
version: "1.0.0",
tokens: [
{
id: "1",
uri: null,
metadata: null,
owners: [
{
from: "0x0000000000000000000000000000000000000000",
to: "0x77a395A6f7c6E91192697Abb207ea3c171F4B338",
blockNumber: 41988367,
transactionHash: "0xd622364913527f2d19ac5a09cba872df88a1bc8d37661bccac98ec2d38130787",
alias: "n0madz.lens",
},
],
},
],
address: "0xe8ebad85fe7eb36ed5b6f12ac95048c7d9bff15b",
uri: "ar://BCfUm5TsFP8VPpRtxtJq8Ue7mm_L-hhldRo3XrD-8Ck",
metadata: {
version: "2.0.0",
metadata_id: "dfa5863d-b325-47f4-93c7-d8af3d12e48e",
content:
"1 Wmatic to collect\n" +
"25% referral reward\n" +
"Nonexclusive Unlimited Usage Rights for owners\n" +
"\n" +
"Thanks everyone for the support on the beats lately, its been inspirational.",
external_url: "https://beatsapp.xyz/profile/jburn.lens",
image: "ipfs://bafybeieiuz7xqlrs4aq7gfd3h2ttkt5sjjw43dm57xk3cmpneeiadql4qa",
imageMimeType: "image/png",
name: "Blast",
tags: ["Song", ""],
animation_url: "ipfs://bafybeifgvqlonwa3m3rzgdzdix34ob57bap56fmiypsujewm74jruoonli",
mainContentFocus: "AUDIO",
contentWarning: null,
attributes: [
{ traitType: "type", displayType: "string", value: "audio" },
{ traitType: "genre", displayType: "string", value: "" },
{ traitType: "author", displayType: "string", value: "Jburn" },
],
media: [
{
type: "audio/mpeg",
altTag: "Audio file",
item: "ipfs://bafybeifgvqlonwa3m3rzgdzdix34ob57bap56fmiypsujewm74jruoonli",
},
],
locale: "en",
appId: "beats",
description:
"1 Wmatic to collect\n" +
"25% referral reward\n" +
"Nonexclusive Unlimited Usage Rights for owners\n" +
"\n" +
"Thanks everyone for the support on the beats lately, its been inspirational.",
},
},
manifestations: [
{
version: "1.0.0",
uri: "ipfs://bafybeieiuz7xqlrs4aq7gfd3h2ttkt5sjjw43dm57xk3cmpneeiadql4qa",
mimetype: "image",
},
{
version: "1.0.0",
uri: "ipfs://bafybeifgvqlonwa3m3rzgdzdix34ob57bap56fmiypsujewm74jruoonli",
mimetype: "audio",
},
],
},
];

test.serial("should be able to add new track", async (t) => {
await tracksDB.upsertTrack(sample[0], 0);
const ret = await tracksDB.getTrack(sample[0].uid);
t.like(ret, sample[0]);
});

test.serial("should be able to add owner", async (t) => {
await tracksDB.upsertTrack(sample[0], 0);
const newOwner = {
from: "0x77a395A6f7c6E91192697Abb207ea3c171F4B338",
to: "0xAc177a395A6f7c6E91192bb207ea369771F4B338",
blockNumber: 1,
transactionHash: "0xd622364913527f2d19ac5a09cba872df88a1bc8d37661bccac98ec2d38130787",
alias: "neume.lens",
};
await tracksDB.upsertOwner(sample[0].uid, "1", newOwner, "neume", 1);
const ret = await tracksDB.getTrack(sample[0].uid);
const retToken = ret.erc721.tokens.find((t) => t.id === "1");

t.deepEqual(retToken?.owners, [
...(sample[0].erc721.tokens.find((t) => t.id === "1")?.owners ?? []),
newOwner,
]);
});

test.serial("should be able to get changed tracks", async (t) => {
await tracksDB.upsertTrack(sample[0], 0);
await tracksDB.upsertTrack(sample[1], 5);

let ret = await tracksDB.getTracksChanged(0, 0, sample[0].platform.name);
t.is(ret.length, 1);
t.deepEqual(ret[0], sample[0]);

ret = await tracksDB.getTracksChanged(0, 5, sample[0].platform.name);
t.is(ret.length, 2);
t.deepEqual(ret, sample);

ret = await tracksDB.getTracksChanged(5, 5, sample[1].platform.name);
t.is(ret.length, 1);
t.deepEqual(ret[0], sample[1]);

const newOwner = {
from: "0x77a395A6f7c6E91192697Abb207ea3c171F4B338",
to: "0xAc177a395A6f7c6E91192bb207ea369771F4B338",
blockNumber: 1,
transactionHash: "0xd622364913527f2d19ac5a09cba872df88a1bc8d37661bccac98ec2d38130787",
alias: "neume.lens",
};
tracksDB.upsertOwner(
sample[0].uid,
sample[0].erc721.tokens[0].id,
newOwner,
sample[0].platform.name,
5,
);
ret = await tracksDB.getTracksChanged(5, 5, sample[0].platform.name);
t.is(ret.length, 2);
});

test("should be able to update track", async (t) => {
await tracksDB.upsertTrack(sample[0], 0);
const newTrack = { ...sample[0], title: "New Title" };
await tracksDB.upsertTrack(newTrack, 0);
const ret = await tracksDB.getTrack(sample[0].uid);
t.deepEqual(ret, newTrack);
});
Loading

0 comments on commit cd79c73

Please sign in to comment.