Music Module
Generating the music 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
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. 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;
}
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 APITo 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 belowpnx 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
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
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": ["logosphere/music-app:latest"]
}
}
Build docker image:
pnx docker music --port=4000
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
Navigate to http://localhost:3000/graphql and Apollo Sandbox will open for testing GraphQL queries and mutations
Copy & paste the
trackFindAll
query into the Apollo Sandboxquery trackFindAll {
trackFindAll {
id
subjectId
name
genre
description
createdAt
updatedAt
}
}
Expectedly it should return empty result, since there is no data in the ledger yet.
Copy & paste the
trackSave
mutation and variables into the Apollo Sandboxmutation 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 modified 6mo ago