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
53 changes: 35 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,66 @@
# RpgMaestro

## TODO

- use Creator musics, credit him !!!
- Youtube to mp3
- make a script that do conversion on my server and put it in correct directory ✅
- upload on fourgate.cloud.private by default <--- figure out a way to either:
- have a private url for my own usage, only available in France && make the public one available to all && disable track uploading <-- demo version ?
- secure mp3 files behind auth
- crossOrigin={'use-credentials'} ?
- or add a token in url -> https://github.com/icidasset/diffuse/blob/6837490c25ec5a534fffdeb3abc818dea9386665/src/Javascript/Workers/service.js#L104
- for now, add them to default session via API
- auto create the track in RPG maestro shared db <- this is not possible yet, might want to think about that.. like a catalogue ?
- make a script that do conversion on my server and put it in correct directory ✅
- upload on fourgate.cloud.private by default <--- figure out a way to either:
- have a private url for my own usage, only available in France && make the public one available to all &&
disable track uploading <-- demo version ?
- secure mp3 files behind auth
- crossOrigin={'use-credentials'} ?
- or add a token in
url -> https://github.com/icidasset/diffuse/blob/6837490c25ec5a534fffdeb3abc818dea9386665/src/Javascript/Workers/service.js#L104
- for now, add them to default session via API
- auto create the track in RPG maestro shared db <- this is not possible yet, might want to think about that.. like
a catalogue ?
- demo env vs my personal use !
- a quick hack could be to have a real demo env.. with no users feat for now
- this will be done by implementing Maestro User auth feature
- there could be 2 two of users: Maestro and AdminMaestro
- a Maestro can only use existing tracks
- an AdminMaestro can upload tracks, and already have free TrackCatalogs loaded
- reminder: set a random Current song for every new sessions !
- I need to think about Maestros enrollment
- then they have their own Admin userspace, and can share a public Session link (one maestro = one session)
- no need to secure the session links for now
- a quick hack could be to have a real demo env.. with no users feat for now
- this will be done by implementing Maestro User auth feature
- there could be 2 two of users: Maestro and AdminMaestro
- a Maestro can only use existing tracks
- an AdminMaestro can upload tracks, and already have free TrackCatalogs loaded
- reminder: set a random Current song for every new sessions !
- TODO: think about possibility of having multiple maestro
- idea: maybe a quick hack would be for the gm to allow for others to modify its session with a simple button
and then, all Players with the URL can modify it
I will need to check userId or email vs the sessionOwner
I could simply add a list of additional Maestro on the session infos !!!!!!
- I need to think about Maestros enrollment
- then they have their own Admin userspace, and can share a public Session link (one maestro = one session)
- no need to secure the session links for now
- soundboard
- quick tags
- fix some tracks have unhandled "durations", example: 50717.210999999996 (this will probably negatively impact calculus and sync)
- fix some tracks have unhandled "durations", example: 50717.210999999996 (this will probably negatively impact calculus
and sync)
- add a local Redis (or equivalent) !
- create a CI/CD
- handle multienv (prod vs preprod-rpg-maestro.fourgate.cloud)
- replace docker-build and deploy tasks by a custom executor

## run tests

```
npx nx affected -t lint test e2e build --no-cloud
```

## run e2e tests

```
npx nx e2e rpg-maestro-ui-e2e --ui --no-cloud
```

## dev run

```
npm i
nx dev rpg-maestro
nx serve rpg-maestro-ui
```

## RPG-maestro server manual deploy

```
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin
docker build . -t acevedor/rpg-maestro -f apps/rpg-maestro/Dockerfile --platform linux/amd64
Expand Down
27 changes: 15 additions & 12 deletions apps/rpg-maestro-ui-e2e/src/e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { expect, Page, test } from '@playwright/test';

async function goToAdmin(page: Page) {
await page.goto('/admin');
expect(await page.locator('h1').innerText()).toContain('Admin UI');
async function goToMaestroPage(page: Page, sessionId: string) {
await page.goto(`/maestro/${sessionId}`);
expect(await page.locator('h1').innerText()).toContain('Maestro UI');
}
async function goToTracksManagement(page: Page) {
await page.goto('/admin/manage');

async function goToTracksManagement(page: Page, sessionId: string) {
await page.goto(`/maestro/manage/${sessionId}`);
expect(await page.locator('h1').innerText()).toContain('Tracks management');
}

const A_SESSION_ID = 'a-session';

test('a Maestro can load (via API) and play a current track for its players', async ({ page }) => {
await test.step('init tracks from fileserver using api', async () => {
try {
const res = await fetch('http://localhost:8099/admin/tracks', {
const res = await fetch(`http://localhost:8099/maestro/sessions/${A_SESSION_ID}/tracks`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Expand All @@ -31,8 +34,8 @@ test('a Maestro can load (via API) and play a current track for its players', as
}
});

await test.step('go to admin page, and list available tracks', async () => {
await goToAdmin(page);
await test.step('go to maestro page, and list available tracks', async () => {
await goToMaestroPage(page, A_SESSION_ID);
await expect(page.locator('.MuiDataGrid-row', { hasText: 'race1' })).toBeVisible();
});

Expand All @@ -41,22 +44,22 @@ test('a Maestro can load (via API) and play a current track for its players', as
});

await test.step('go to players page, current track should be displayed', async () => {
await page.goto('/');
await page.goto(`/${A_SESSION_ID}`);
expect(await page.locator('h1').innerText()).toContain('RPG-Maestro player UI');
await expect(page.getByText('race1')).toBeVisible();
});
});

test('a Maestro can add a new track located on a remote server', async ({ page }) => {
await test.step('go to Tracks management and add a track', async () => {
await goToTracksManagement(page);
await goToTracksManagement(page, A_SESSION_ID);
await page.getByLabel('URL').fill('http://localhost:8099/public/light-switch-sound-198508.mp3');
await page.getByText('CREATE TRACK').click();
await expect(page.getByText('CREATE TRACK')).toBeEnabled();
});

await test.step('track should be available on Admin UI', async () => {
await goToAdmin(page);
await test.step('track should be available on Maestro UI', async () => {
await goToMaestroPage(page, A_SESSION_ID);
await expect(page.locator('.MuiDataGrid-row', { hasText: 'light-switch-sound-198508' })).toBeVisible();
});
});
67 changes: 0 additions & 67 deletions apps/rpg-maestro-ui/src/app/admin-ui/admin-api.ts

This file was deleted.

21 changes: 12 additions & 9 deletions apps/rpg-maestro-ui/src/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
import styled from 'styled-components';

import { Route, Routes } from 'react-router-dom';
import { Navigate, Route, Routes } from 'react-router-dom';
import 'react-h5-audio-player/lib/styles.css';
import 'react-toastify/dist/ReactToastify.css';
import { MaestroSoundboard } from './admin-ui/maestro-soundboard';
import { ClientsUi } from './clients/clients-ui';
import { TracksManagement } from './admin-ui/tracks-management/tracks-management';
import { MaestroSoundboard } from './maestro-ui/maestro-soundboard';
import { ClientsUi } from './players-ui/clients-ui';
import { TracksManagement } from './maestro-ui/tracks-management/tracks-management';

const StyledApp = styled.div`
// Your style here
// Your style here
`;

export function App() {
return (
<StyledApp style={{fontFamily: '"Roboto", "Helvetica", "Arial", "sans-serif"'}}>
<StyledApp style={{ fontFamily: '"Roboto", "Helvetica", "Arial", "sans-serif"' }}>
{/* START: routes */}
{/* These routes and navigation have been generated for you */}
{/* Feel free to move and update them to fit your needs */}
<Routes>
<Route path="/" element={<ClientsUi />} />
<Route path="/admin" element={<MaestroSoundboard />} />
<Route path="/admin/manage" element={<TracksManagement />} />
<Route path="/:sessionId" element={<ClientsUi />} />
<Route path="/" element={<Navigate to="/default-current-session" replace />} />
<Route path="/maestro/:sessionId" element={<MaestroSoundboard />} />
<Route path="/maestro/manage/:sessionId" element={<TracksManagement />} />
<Route path="/maestro" element={<Navigate to="/maestro/default-current-session" replace />} />
<Route path="/admin" element={<Navigate to="/maestro" replace />} />
</Routes>
{/* END: routes */}
</StyledApp>
Expand Down
92 changes: 92 additions & 0 deletions apps/rpg-maestro-ui/src/app/maestro-ui/maestro-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { displayError } from '../error-utils';
import {
ChangeSessionPlayingTracksRequest,
Track,
TrackCreation,
TrackUpdate,
} from '@rpg-maestro/rpg-maestro-api-contract';

const rpgmaestroapiurl = import.meta.env.VITE_RPG_MAESTRO_API_URL; // TODO centralize

export const getAllTracks = async (sessionId: string): Promise<Track[]> => {
try {
const response = await fetch(rpgmaestroapiurl + `/maestro/sessions/${sessionId}/tracks`);
if (response.ok) {
return (await response.json()) as Track[];
} else {
console.log(response.status, response.statusText);
console.debug(response);
throw new Error('fetch failed for error: ' + response);
}
} catch (error) {
console.error(error);
displayError(`Fetch /maestro/sessions/${sessionId}/tracks error: ${error}`);
return [];
}
};

export const setTrackToPlay = async (
sessionId: string,
changeSessionPlayingTracksRequest: ChangeSessionPlayingTracksRequest
): Promise<void> => {
try {
const response = await fetch(`${rpgmaestroapiurl}/maestro/sessions/${sessionId}/playing-tracks`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(changeSessionPlayingTracksRequest),
});
if (!response.ok) {
console.log(response.status, response.statusText);
throw new Error('fetch failed for error: ' + response);
}
} catch (error) {
console.error(error);
displayError(`Fetch /maestro/sessions/${sessionId}/playing-tracks error: ${JSON.stringify(error)}`);
}
};

export const createTrack = async (sessionId: string, trackCreation: TrackCreation): Promise<Track> => {
try {
const response = await fetch(`${rpgmaestroapiurl}/maestro/sessions/${sessionId}/tracks`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify(trackCreation),
});
if (!response.ok) {
console.log(response.status, response.statusText);
throw new Error('fetch failed for error: ' + response);
}
return (await response.json()) as Track;
} catch (error) {
console.error(error);
displayError(`Fetch error: ${JSON.stringify(error)}`);
return Promise.reject();
}
};

export const updateTrack = async (sessionId: string, trackId: string, trackUpdate: TrackUpdate): Promise<Track> => {
try {
const response = await fetch(`${rpgmaestroapiurl}/maestro/sessions/${sessionId}/tracks/${trackId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify(trackUpdate),
});
if (!response.ok) {
console.log(response.status, response.statusText);
throw new Error('fetch failed for error: ' + response);
}
return (await response.json()) as Track;
} catch (error) {
console.error(error);
displayError(`Fetch error: ${JSON.stringify(error)}`);
return Promise.reject();
}
};
Loading
Loading