Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
27eb43f
➕ Add rudimentary amber backend
devmount Jun 3, 2025
e0840f2
➕ Add ddev setup
devmount Jun 11, 2025
c7a293b
➕ Add test admin
devmount Jun 15, 2025
768ed48
➕ Add custom endpoint
devmount Jun 17, 2025
ee1539e
📦 Update Amber
devmount Jun 19, 2025
b94411f
➕ Change config
devmount Aug 29, 2025
a2b2120
initial amber setup
EinEnton Aug 29, 2025
92d077d
➕️ Complete entity model properties
devmount Aug 30, 2025
f04a9bd
➕️ Adapt entity model properties
devmount Aug 30, 2025
3e30453
➕️ Import from central type definition file
devmount Aug 30, 2025
0deb202
➕️ Add song tags and languages (hardcoded for now)
devmount Sep 1, 2025
5f0aef1
📦️ Update amber
devmount Sep 7, 2025
0b9b6d7
📦️ Update dependencies
devmount Oct 3, 2025
2169721
🔨 Use default tenant
devmount Oct 12, 2025
61d0a1c
🔨 Play with access rights (wip)
devmount Nov 17, 2025
442a2e1
🔨 User mapping and setlist title edit
devmount Nov 19, 2025
7622d5e
🔨 Test setlist song list
devmount Nov 21, 2025
722107d
➕️ Adapt entity model properties
devmount Nov 22, 2025
858ca55
➕️ Adapt entity model properties
devmount Nov 23, 2025
5d55e3c
➕️ Use role enum
devmount Nov 24, 2025
c1ff00f
➕️ Add data migration script
devmount Nov 24, 2025
30b6193
🔨 Fix public accessTag
devmount Nov 24, 2025
fed1f79
➕️ Handle roles in migration script
devmount Nov 24, 2025
79f25a5
➕️ Handle custom login (wip)
devmount Dec 26, 2025
5d9f617
🔨 Handle custom login (wip)
devmount Jan 31, 2026
9965005
📦️ Update dependencies
devmount Apr 14, 2026
7656acb
📦️ Update dependencies and import enums
devmount Apr 18, 2026
717fc7b
➕️ Finalize working custom login
devmount Jul 26, 2026
54d2c03
📦️ Update backend to TS v6
devmount Jul 27, 2026
7555c5f
🔨 Rename ambigous component name
devmount Jul 28, 2026
53397e3
🔨 Make export names more distinguishable
devmount Jul 28, 2026
479332d
🔨 Migrate the dashboard to Amberbase
devmount Jul 28, 2026
f122075
🔨 Fix hard reload on client-side routes
devmount Jul 28, 2026
7762aed
🔨 Migrate the profile page to Amberbase
devmount Jul 28, 2026
094d855
🔨 Move the settings to the profile page
devmount Jul 28, 2026
ae5bf32
❌️ Remove settings page
devmount Jul 28, 2026
c0b6dc5
🔨 Migrate the songs page to Amberbase (wip)
devmount Jul 28, 2026
ffce7db
🔨 Migrate the songs page to Amberbase
devmount Jul 29, 2026
e79a42c
🔨 Fix missing rate limiter
devmount Jul 29, 2026
94a6df1
🔨 Migrate the SongShow page to Amberbase (wip)
devmount Jul 29, 2026
72d43e6
🔨 Migrate the SongShow page to Amberbase (wip)
devmount Jul 29, 2026
f403b1b
🔨 Fix tags
devmount Jul 30, 2026
fdcd0c0
🔨 Fix authors
devmount Jul 30, 2026
a502883
🔨 Rename tuning to key
devmount Jul 30, 2026
bf794a7
🔨 Rename single word components
devmount Jul 30, 2026
386a024
🔨 Fix songs permissions
devmount Jul 30, 2026
0f1d69b
🔨 Migrate the SongSet modal to Amberbase
devmount Jul 30, 2026
dcae12d
🔨 Fix textarea vertical offset glitch
devmount Jul 30, 2026
5d638ef
🔨 Migrate the SongDelete modal to Amberbase
devmount Jul 30, 2026
90f78e8
🔨 Migrate Setlists table to Amberbase
devmount Jul 31, 2026
e0e5897
🔨 Migrate SetlistShow to Amberbase
devmount Jul 31, 2026
8c0f4fc
🔨 Migrate SetlistSet to Amberbase
devmount Jul 31, 2026
7fc06d3
🔨 Migrate SetlistDelete to Amberbase
devmount Jul 31, 2026
337266e
🔨 Rewire password reset and signup
devmount Jul 31, 2026
ff24608
🔨 Fix login
devmount Jul 31, 2026
0e9208d
➕️ Link admin mailto
devmount Jul 31, 2026
df221ef
🧹 Remove unused language keys
devmount Jul 31, 2026
26038df
🧹 Remove mvp Amberbase frontend
devmount Jul 31, 2026
d9139cf
📜 Update docs after Amberbase migration
devmount Jul 31, 2026
3b4a171
➕️ Link to amber account manager
devmount Jul 31, 2026
8306139
⚡️ Heavily reduce bundle size
devmount Jul 31, 2026
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ node_modules
dist
Thumbs.db
deploy.sh
backend/migration/*.json
backend/migration/*.sql

# local env files
.env
Expand Down
11 changes: 4 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,15 @@
[![License](https://img.shields.io/badge/license-MIT-88b544.svg?style=flat-square)](./LICENSE)
[![Contribution Guidlines](https://img.shields.io/badge/contributions-welcome-88b544.svg?style=flat-square)](./.github/CONTRIBUTING.md)

A song management web application to store, sync and present songs and setlists, based on [Vue.js](//vuejs.org) and [Firebase](//firebase.google.com). Head over to the docs ([EN](https://github.com/devmount/SongDrive/blob/main/frontend/src/docs/docs.en.md), [DE](https://github.com/devmount/SongDrive/blob/main/frontend/src/docs/docs.en.md)) for more information about SongDrive and how to get started.

> [!NOTE]
> SongDrive is currently being restructured to have its own backend with database, authentication and API endpoints for the frontend. Find more information about this in <https://github.com/devmount/SongDrive/issues/213>.
A song management web application to store, sync and present songs and setlists, based on [Vue.js](//vuejs.org) and [Amberbase](https://github.com/amberbase/amberbase). Head over to the docs ([EN](https://github.com/devmount/SongDrive/blob/main/frontend/src/docs/docs.en.md), [DE](https://github.com/devmount/SongDrive/blob/main/frontend/src/docs/docs.en.md)) for more information about SongDrive and how to get started.

## Preview

This is how the SongDrive Dashboard currently looks like.

![20230725_songdrive_dashboard](https://github.com/devmount/SongDrive/assets/5441654/94df7e1a-3c65-460e-817d-24c1783e7685)

*A demo page is planned.*
*A demo page is coming soon™.*

## Features

Expand All @@ -32,12 +29,12 @@ This is how the SongDrive Dashboard currently looks like.
- Use Hotkeys for almost anything (hotkey cheatsheet included)
- Analyze general, setlist and user specific stats
- UI supports multiple languages (currently: EN, DE)
- Users can [manage their own profile](https://github.com/devmount/SongDrive/blob/main/frontend/src/docs/docs.en.md#account), change email and password or delete their account.
- Users can [manage their own profile](https://github.com/devmount/SongDrive/blob/main/frontend/src/docs/docs.en.md#account) and change their password.
- Many admin functions including export and Import of complete SongDrive data and managing users with [different roles](https://github.com/devmount/SongDrive/blob/main/frontend/src/docs/docs.en.md#user-roles)

## Code structure

SongDrive is divided into frontend and backend. The goal is to make both parts independent from each other. The restructuring is still work in progress. You can find documentation in the corresponding directory:
SongDrive is divided into frontend and backend. Both parts operate independent from each other. You can find documentation in the corresponding directory:

- [Frontend docs](./frontend/README.md)
- [Backend docs](./backend/README.md)
Expand Down
20 changes: 13 additions & 7 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
# ![SongDrive Logo Title](https://github.com/devmount/SongDrive/assets/5441654/77b68e2d-a877-4855-bece-c067f2219e71) &nbsp; backend
# ![SongDrive Logo Title](https://github.com/devmount/SongDrive/assets/5441654/77b68e2d-a877-4855-bece-c067f2219e71) :: Backend

This directory contains all backend functionality of SongDrive.
This directory contains all backend functionality of SongDrive. It is build with [Amberbase](https://github.com/amberbase/amberbase).

> <picture>
> <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/Mqxx/GitHub-Markdown/main/blockquotes/badge/light-theme/info.svg">
> <img alt="Info" src="https://raw.githubusercontent.com/Mqxx/GitHub-Markdown/main/blockquotes/badge/dark-theme/info.svg">
> </picture><br>
> This is currently work in progress.
## Get started

Make sure, MariaDB is installed.

```bash
cd backend
npm run build
npm run start
```

Now, the backend UI can be accessed via <http://localhost:3000/amber/ui/login>.
139 changes: 139 additions & 0 deletions backend/definitions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/**
* Available song tags
*/
export enum SongTag {
Adoration = 'adoration',
Assurance = 'assurance',
Beauty = 'beauty',
Blessing = 'blessing',
Celebration = 'celebration',
Children = 'children',
Christmas = 'christmas',
Comfort = 'comfort',
Commitment = 'commitment',
Communion = 'communion',
Creation = 'creation',
Cross = 'cross',
Declaration = 'declaration',
Desire = 'desire',
Easter = 'easter',
Encouragement = 'encouragement',
Evangelism = 'evangelism',
Faith = 'faith',
Faithfulness = 'faithfulness',
Father = 'father',
Forgiveness = 'forgiveness',
Freedom = 'freedom',
Glory = 'glory',
Grace = 'grace',
Greatness = 'greatness',
Guidance = 'guidance',
Healing = 'healing',
Holiness = 'holiness',
HolySpirit = 'holyspirit',
Hope = 'hope',
Humility = 'humility',
Jesus = 'jesus',
Joy = 'joy',
Kingship = 'kingship',
Life = 'life',
Light = 'light',
Longing = 'longing',
Love = 'love',
Obedience = 'obedience',
Peace = 'peace',
Petition = 'petition',
Power = 'power',
Praise = 'praise',
Prayer = 'prayer',
Presence = 'presence',
Rest = 'rest',
Resurrection = 'resurrection',
Revival = 'revival',
Sacrifice = 'sacrifice',
Salvation = 'salvation',
Strength = 'strength',
Surrender = 'surrender',
Thankfulness = 'thankfulness',
Trust = 'trust',
Truth = 'truth',
Unity = 'unity',
Victory = 'victory',
Worship = 'worship',
}

/**
* Available song languges
*/
export enum SongLanguage {
DE = 'de',
EN = 'en',
FR = 'fr',
HE = 'he',
LI = 'li',
}

/**
* Supported user permission roles
*/
export enum UserRole {
Admin = 'admin',
Editor = 'editor',
Performer = 'performer',
Reader = 'reader',
};

/**
* Ownership/visibility context for actions whose access depends on a
* specific document rather than just the user's role (e.g. setlist
* update/delete, which the backend also gates by ownership and isPublic
* per the setlists collection's accessRights in backend/index.ts).
*/
export type CanContext = {
userId?: string;
ownerId?: string;
isPublic?: boolean;
};

/**
* Ruleset to check of a given roles list
*/
export const can = (action: string, roles: UserRole[], context?: CanContext): boolean => {
// Check valid input
if (!action || !roles) return false;

// Get the highes role from the given list of roles
const level = {
[UserRole.Admin]: 8,
[UserRole.Editor]: 4,
[UserRole.Performer]: 2,
[UserRole.Reader]: 1,
};
const highestRole = roles.toSorted((a, b) => level[a] - level[b])[0];

// Admins are allowed in general
if (highestRole === UserRole.Admin) {
return true;
}

// All others are assigned as defined here
switch (action) {
case 'createSetlists':
return [UserRole.Editor, UserRole.Performer].includes(highestRole);
case 'updateSetlists':
// owned by the current user, or public and editor/performer
if (!context) return false;
if (context.userId && context.userId === context.ownerId) return true;
return !!context.isPublic && [UserRole.Editor, UserRole.Performer].includes(highestRole);
case 'deleteSetlists':
// editors may delete any setlist, others only their own
if (highestRole === UserRole.Editor) return true;
return !!context && !!context.userId && context.userId === context.ownerId;
case 'createSongs':
case 'updateSongs':
case 'deleteSongs':
return [UserRole.Editor].includes(highestRole);
default:
return false;
}
}
116 changes: 116 additions & 0 deletions backend/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { amber, CollectionAccessAction, UserContext } from 'amberbase';
import cookieParser from 'cookie-parser';
import express from 'express';
import rateLimit from 'express-rate-limit';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { SetlistEntity, SongEntity } from './models.js';
import { UserRole } from './definitions.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const appInit = amber()
.withConfig({
db_host: 'localhost',
db_password: 'root',
db_username: 'root',
db_name: 'songdrive',
db_port: 3306,
})
.withPath('/amber')
.withCollection<SongEntity>('songs', {
accessRights: {
[UserRole.Editor]: ['create', 'subscribe', 'update', 'delete'],
[UserRole.Performer]: ['subscribe'],
[UserRole.Reader]: ['subscribe']
}
})
.withCollection<SetlistEntity>('setlists', {
accessRights: (user: UserContext, doc: SetlistEntity | null, action: CollectionAccessAction) => {
// Public setlists can be created by editors and performers.
// Private setlists can be created by everyone.
if (action === 'create') {
if (user.roles.includes(UserRole.Editor) || user.roles.includes(UserRole.Performer)) {
return true;
}
if (user.roles.includes(UserRole.Reader)) {
return doc?.createdBy === user.userId && !doc?.isPublic;
}
return false;
}
// Setlists can be subscribed to by all roles, but only public setlists can be read per default.
// This is done via access tags below.
if (action === 'subscribe') {
return user.roles.includes(UserRole.Editor) || user.roles.includes(UserRole.Performer) || user.roles.includes(UserRole.Reader);
}
// Setlists can be deleted by editors or the corresponding creator.
if (action === 'delete') {
return user.roles.includes(UserRole.Editor) || doc?.createdBy === user.userId;
}
// Setlists can be updated by the corresponding creator.
// Public setlists can be updated by editors and performers.
if (action === 'update') {
if (doc?.createdBy === user.userId) {
return true;
}
if (doc?.isPublic && (user.roles.includes(UserRole.Editor) || user.roles.includes(UserRole.Performer))) {
return true;
}
return false;
}

return false;
},

// The owner of a private setlist can share it with other users
accessTagsFromDocument: (doc: SetlistEntity) => {
const tags = [`o-${doc.createdBy}`];
if (doc.isPublic) {
tags.push('public');
} else {
doc.sharedWith.forEach((userId) => {
tags.push(`s-${userId}`);
});
}
return tags;
},
accessTagsFromUser: (user: UserContext) => [
'public',
`o-${user.userId}`,
`s-${user.userId}`,
],
})
.withUi({
availableRoles: [UserRole.Editor, UserRole.Performer, UserRole.Reader],
theme: 'dark',
loginTargetUrl: '/#/tenant={tenant}',
title: 'SongDrive Manager',
});

const expressApp = express();
const amberApp = await appInit.create(expressApp);

expressApp.use(cookieParser())
expressApp.use(express.static(path.join(__dirname, 'static')));

expressApp.get('/version', (_req, res) => {
res.send(`Version: 1.2.3`);
});

// Fallback to the SPA entry point for client-side routes (e.g. /profile) on hard reload.
// Splat was chosen as conventional default name.
const spaFallbackLimiter = rateLimit({
windowMs: 60 * 1000,
limit: 100,
standardHeaders: true,
legacyHeaders: false,
});
expressApp.get('/*splat', spaFallbackLimiter, (_req, res) => {
res.sendFile(path.join(__dirname, 'static', 'index.html'));
});

amberApp.addAdminIfNotExists('admin@songdrive.de', 'Admin', 'password');
amberApp.addOrUpdateTenant('default', 'Default Tenant', {});

amberApp.listen(3333, '0.0.0.0');
Loading
Loading