Music Module

Generating the music module

Create module

This creates a new codegen module music-gen, in the libs directory. The gen postfix is added to generated modules, to distinguish them from other modules, coded by hand.

pnx g @logosphere/sdk:module --name music

Take a look at the project structure under libs/music-gen/src

Edit model file

In this step, we will create a simple model for the music domain, consisting of Artist, Album and Track entities, where one artist can have multiple albums and one album can have multiple tracks. We also have a predefined enumeration for genres.

The *.model.ts file that is pre-generated with a few sample entities, should be the only modifiable file in a codegen library. The rest of the code assets will be generated from the model defined in this file.

The model is based on TypeScript classes and is very similar to code-first modelling in other TypeScript frameworks, such as NestJS, TypeORM etc. Every entity, as per DDD definition of an entity is defined as a class with an @Ent() decorator and every property within an entity should be decorated with a @Prop() decorator. Enumerations should be registered with registerEnum() function.

For the complete list of model properties check the Logosphere Modeling documentation.

Modify libs/music-gen/src/music.model.ts file to add the following entities. Delete generated MusicEntity. Leave Wallet, WalletAsset, and User entities unmodified.

/* eslint-disable @typescript-eslint/no-unused-vars */
import { Ent, Prop, registerEnum } from '@logosphere/sdk';

export enum Genre
{
    Pop = 0,
    Rock,
    HipHop,
    RnB,
    Country,
    KPop,
    JPop,
    World
};

registerEnum(Genre, 'Genre');

@Ent('artist')
export class Artist {
  @Prop({
    examples: ['Taylor Swift', 'Lil Nas X', 'Dua Lipa'],
  })
  name: string;

  @Prop({
    examples: ['About Taylor Swift', 'Lil Nas X Bio', 'Dua Lipa is awesome'],
  })
  about: string;

}

@Ent('album')
export class Album {
  @Prop({
    examples: ['Fearless', 'MONTERO', 'Future Nostalgia'],
  })
  name: string;

  @Prop({ type: () => Genre })
  genre: Genre;

  @Prop({ type: () => Artist, index: false })
  artist: Artist;

}

@Ent('track')
export class Track {
  @Prop({
    examples: ['Shake If Off', 'Montero', 'Levitating'],
  })
  name: string;

  @Prop({ type: () => Genre })
  genre: Genre;

  @Prop({
    examples: ['Be yourself and shake it off', 'Also called call me by your name', 'Just a great song'],
  })
  description: string;

  @Prop({ type: () => Album, index: false })
  album: Album;

  @Prop({ type: () => Artist, index: false })
  artist: Artist;

}

@Ent('playlist')
export class Playlist {
  @Prop({
    examples: ['Top 40', 'Best of Pop', 'New releases'],
  })
  name: string;

  @Prop({ type: () => [Track], index: false})
  tracks: Track[];

}

@Ent('walletAsset')
class WalletAsset {
  @Prop({ doc: 'Name of the asset', examples: ['4269736f6e'] })
  name: string;

  @Prop({
    doc: 'Fingerprint of the asset',
    examples: ['asset12q7zh30hj2yme96wy8ms4fcdrwtep0auz8xqly'],
  })
  fingerprint: string;

  @Prop({
    doc: 'Policy ID of the asset',
    examples: ['0b7018936bc41808ddabd96b4908b583195a0c252b5752ad38012bdb'],
  })
  policyId: string;

  @Prop({ doc: 'Quantity of the asset', examples: ['1'] })
  quantity: number;

  @Prop({ doc: 'Metadata associated with the asset' })
  metadata: string;

  @Prop({ doc: 'Fluree subject ID of the asset', examples: ['87960930223082'] })
  assetSubjectId: string;

  @Prop({
    doc: 'Logosphere ID of the asset',
    examples: [
      '62c0ac76d6eebbf70828da57ea06c41a55001a2eb3cc929206d8f39abdbfaefc',
    ],
  })
  logosphereId: string;
}

@Ent('wallet')
class Wallet {
  @Prop({
    doc: 'Name of the wallet',
    examples: ['Babingos wallet'],
  })
  name: string;

  @Prop({
    doc: 'ID of the wallet',
    index: true,
    unique: true,
    examples: ['cd72843c95467883ccd6dafe227b91c96f071713'],
  })
  walletId: string;

  @Prop({
    doc: 'Address of the wallet',
    index: true,
    unique: true,
    examples: [
      'addr_test1qzsn8km55mp6cra7l20cymauks2fay8sqc3jr874mgmxpsa5mj4dvw5zrxmhauknj60c8tsf7x72ng0r8zmxa3necjlsgx9q6d',
    ],
  })
  address: string;

  @Prop({
    doc: 'Public key of the wallet',
    index: true,
    unique: true,
    examples: [
      'a1f009e6f5770c7b10729f27237c7ccc677739e31119a69766664dee611220948234926b2c445c2e9e2ff40f22beafa193d7fedf72e5e877bffd606d33b6638c',
    ],
  })
  publicKey: string;
  
  @Prop({
    doc: 'Balance of the wallet in lovelace',
    examples: [
      '0', '1000'
    ],
  })
  balance: number;

  @Prop({
    doc: 'Wallet assets',
    type: () => [WalletAsset],
  })
  assets: WalletAsset[];
}

@Ent('user')
class User {
  @Prop({
    doc: 'Username of the user',
    index: true,
    unique: true,
    examples: ['babingo_whoelse'],
  })
  username: string;

  @Prop({
    doc: 'User wallet',
    type: () => Wallet,
  })
  wallet: Wallet;
}

Generate API

Build model with the following command:

pnx affected:build

NX automatically tracks changes and only calls build target for affected libs. Alternatively you can build the codegen module with:

pnx build music-gen

Now you can generate GraphQL API with DDD assets architecture:

pnx g @logosphere/sdk:api --module music

After executing this command, you will see your music codegen module library populated with DDD assets and the two applications generated under apps folder:

  • apps/music-e2e

  • apps/music

The music-e2e is an app for running integration tests against it.

The music-app is an application that is built into a docker container and serves the GQL API

Create environment variables and docker-compose

To run, the application require set of environment variables and number of external services. To generate pre-configured .env and docker-compose files call the command below

pnx g @logosphere/sdk:docker-compose

If you already have one of generated files, the generator will ask you about override. In case of confirm the previous configuration will be lost

Shell environment variables

These environment variables will be used with the Logosphere NX plugin generators pnx g command and should be defined in a shell config file, such as .zshrc or in a file, such as .env-sh which should be sourced with source .env-sh

# FLUREE
# URL of the Fluree DB instance
export FLUREE_URL='http://localhost:8090'
# Fluree ledger in the format <network>/<ledger>
export FLUREE_LEDGER='local/music'
# Flag to update Fluree schema automatically when the model is changed
export FLUREE_AUTO_UPDATE_SCHEMA=true

Run single Docker image

Modify tags option in the docker target in the apps/music/project.json from logosphere to your DockerHub organization:

"docker": {
      "executor": "@nx-tools/nx-docker:build",
      "options": {
        "context": "dist",
        "tags": ["/music-app:latest"]
      }
    }

Build docker image:

pnx docker music --port=4000

Run whole application stack in Docker

Make sure that docker-compose and .env exists and contain proper values. Docker service must be running too. Being in root folder execute command:

docker-compose up

Test API

Navigate to http://localhost:3000/graphql and Apollo Sandbox will open for testing GraphQL queries and mutations

Get All Tracks

Copy & paste the trackFindAll query into the Apollo Sandbox

query trackFindAll {
  trackFindAll {
    id
    subjectId
    name
    genre
    description
    createdAt
    updatedAt
  }
}

Expectedly it should return empty result, since there is no data in the ledger yet.

Create a track

Copy & paste the trackSave mutation and variables into the Apollo Sandbox

mutation trackCreate($track: TrackInput!) {
  trackSave(track: $track) {
    id
    subjectId
    name
    genre
    album {
      id
      name
      artist {
        id
        name
        about
      }
    }
    description
    createdAt
    updatedAt
  }
}

Variables:

{
  "track": {
    "name": "Shake it off",
    "genre": "Pop",
    "description": "Song about shaking it off",
    "album": {
      "name": "1989",
      "genre": "Pop",
      "artist": {
        "name": "Taylor Swift",
        "about": "American pop singer"
      }
    }
  }
}

Output:

{
  "data": {
    "trackSave": {
      "id": "4234360906fdbc47726ba53cf5833a673ef28654e5db7bcbc5ba68a887cce8d9",
      "subjectId": "387028092977152",
      "name": "Shake it off",
      "genre": "Pop",
      "album": {
        "id": "b5726dacdc735d08fcdc08d73de740ed00067895cb5fbc24beb1cf33a321a335",
        "name": "1989",
        "artist": {
          "name": "Taylor Swift",
          "about": "American pop singer"
        }
      },
      "description": "Song about shaking it off",
      "createdAt": "2022-07-20T21:50:51.085Z",
      "updatedAt": "2022-07-20T21:50:51.085Z"
    }
  }
}

Last updated