Skip to content
Draft
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
6 changes: 4 additions & 2 deletions .firebaserc
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
"projects": {
"default": "csound-ide",
"develop": "csound-ide-dev"
}
}
},
"targets": {},
"etags": {}
}
4 changes: 2 additions & 2 deletions .github/workflows/develop.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ jobs:
PUBLIC_URL="https://csound-ide-dev.web.app" npm run build:dev
cp dist/index.html functions
cd functions
yarn install
yarn build
npm ci
npm run build
- name: Deploy to Firebase
uses: w9jds/firebase-action@master
with:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ jobs:
PUBLIC_URL="https://ide.csound.com" PRODUCTION=1 npm run build
cp dist/index.html functions
cd functions
yarn install
yarn build
npm ci
npm run build
- name: Deploy to Firebase
uses: w9jds/firebase-action@master
with:
Expand Down
1 change: 0 additions & 1 deletion config/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ module.exports = {
appSrc: resolveApp('src'),
appTsConfig: resolveApp('tsconfig.json'),
appJsConfig: resolveApp('jsconfig.json'),
yarnLockFile: resolveApp('yarn.lock'),
testsSetup: resolveModule(resolveApp, 'src/setupTests'),
proxySetup: resolveApp('src/setupProxy.js'),
appNodeModules: resolveApp('node_modules'),
Expand Down
1 change: 0 additions & 1 deletion config/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use strict";

const fs = require("fs");
const path = require("path");
const R = require("ramda");
const webpack = require("webpack");
Expand Down
87 changes: 40 additions & 47 deletions docs/developer.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,88 +6,81 @@

### Overview

* Firebase Authentication: user identity
* Firebase Database: Cloud Firestore or Realtime Database?
* Firebase Storage: for storage of binaries (ogg, wav, mp3)
* Firebase Hosting: for hosting the site
- Firebase Authentication: user identity
- Firebase Database: Cloud Firestore or Realtime Database?
- Firebase Storage: for storage of binaries (ogg, wav, mp3)
- Firebase Hosting: for hosting the site

### Data

* Projects have files and directories
* Files may be textual: CSD, ORC, TXT, MD (, HTML, JS)
* Resources: OGG, MP3, WAV
- Projects have files and directories
- Files may be textual: CSD, ORC, TXT, MD (, HTML, JS)
- Resources: OGG, MP3, WAV

Firestore DB layout

/users/username
/projects/projectname -- collection file refs which have name and URI (FirebaseStorage URI)
/collections/collectionname
/collections/collectionname

project files as binary - stored in Firebase Storage


### URL Structure

* /[user-name]
* /[user-name]/[project-name]
* /[user-name]/[project-name]/edit
* /[user-name]/[project-name]/[file-name]
* /[user-name]/[collections]


- /[user-name]
- /[user-name]/[project-name]
- /[user-name]/[project-name]/edit
- /[user-name]/[project-name]/[file-name]
- /[user-name]/[collections]

<!-- CLIENT -->

## Client

### Technologies

* npm/yarn
* React
* Redux
* Firebase
* Csound

- npm/yarn
- React
- Redux
- Firebase
- Csound

### Coding Practice

### Features

* IDE-like with project file tree on left (see Codepen.io Projects)
* Client-side code: user selects a file in file tree, we have mime-type like system to identify type, open editor for type (text editor with different hilighting, playback interface for auditioning audio files)
* Project can have "main" CSD file set
* Project can have multiple CSD files (maybe one for realtime, one for disk, etc.). See csound-live-code project for example.
* User can render to "disk" which will render to local filesystem. This will need to render to Emscripten FS, which is also where we will be mapping our project files to in memory. We should consider having a local area mapped to a separate peer folder to the project folder that user can render to so that and output render doesn't get added to project and synced back to firebase.
### Features

- IDE-like with project file tree on left (see Codepen.io Projects)
- Client-side code: user selects a file in file tree, we have mime-type like system to identify type, open editor for type (text editor with different hilighting, playback interface for auditioning audio files)
- Project can have "main" CSD file set
- Project can have multiple CSD files (maybe one for realtime, one for disk, etc.). See csound-live-code project for example.
- User can render to "disk" which will render to local filesystem. This will need to render to Emscripten FS, which is also where we will be mapping our project files to in memory. We should consider having a local area mapped to a separate peer folder to the project folder that user can render to so that and output render doesn't get added to project and synced back to firebase.

<!-- Milestone -->

### Milestone 1

* User can start a new project
* User can name project (must be unique)
* User can add .orc file
* User can add .sco file
* User can add .csd file
* User can use \#include and files include correctly
* User can start rendering with CSD
* User can eval to live code
* User can save project (or just have auto-save)
* User can mark project public/private
- User can start a new project
- User can name project (must be unique)
- User can add .orc file
- User can add .sco file
- User can add .csd file
- User can use \#include and files include correctly
- User can start rendering with CSD
- User can eval to live code
- User can save project (or just have auto-save)
- User can mark project public/private

### Milestone 2
* User can clone project
* User can fork project (with history)


- User can clone project
- User can fork project (with history)

### Desktop
* Electron application
* Look at NPM packaging for WebCsound? (is this necessary?)
* Another option is Progressive Web App (PWA)


- Electron application
- Look at NPM packaging for WebCsound? (is this necessary?)
- Another option is Progressive Web App (PWA)

## Brainstorming

* Loading .orc from external URL would be very handy (see Codepen, jsfiddle, etc.)
- Loading .orc from external URL would be very handy (see Codepen, jsfiddle, etc.)
12 changes: 12 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ import eslintPluginReact from "eslint-plugin-react";
export default [
eslint.configs.recommended,
...tseslint.configs.recommended,
{
files: ["scripts/**/*.js", "config/**/*.js", "functions/**/*.js"],
languageOptions: {
globals: {
...globals.node,
...globals.builtin
}
},
rules: {
"@typescript-eslint/no-require-imports": "off"
}
},
{
files: ["**/*.{js,jsx,ts,tsx}"],
languageOptions: {
Expand Down
25 changes: 14 additions & 11 deletions firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,7 @@
},
{
"source": "/editor/**",
"function": "host",
"headers": [
{
"key": "Cross-Origin-Embedder-Policy",
"value": "require-corp"
},
{
"key": "Cross-Origin-Opener-Policy",
"value": "same-origin"
}
]
"function": "host"
},
{
"source": "/profile/**",
Expand All @@ -43,6 +33,19 @@
}
],
"headers": [
{
"source": "/editor/**",
"headers": [
{
"key": "Cross-Origin-Embedder-Policy",
"value": "require-corp"
},
{
"key": "Cross-Origin-Opener-Policy",
"value": "same-origin"
}
]
},
{
"source": "**/*.@(jpg|svg|jpeg|png|eot|otf|ttf|ttc|woff|css|wasm|ico)",
"headers": [
Expand Down
90 changes: 68 additions & 22 deletions functions/README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,80 @@
## Installation
1. `npm install -g firebase-tools` (provides firebase-cli)
2. Login via google OAuth by typing `firebase login`
3. confirm that you're logged in by typing `firebase list`
4. Activate a default project from your google-appengine projects by typing `firebase use --add`.
# Local Testing Setup

## Useage
### Scope
This guide explains how to run a local copy of the web IDE connected to your own Firebase project, so you can test UI/UX flows (including account deletion) against a real database.

## Prerequisites

```bash
npm install -g firebase-tools
firebase login
```
# All functions
$ firebase deploy --only functions

## 1. Create a Firebase project

1. Go to <https://console.firebase.google.com> and create a new project
2. Enable **Authentication** → Sign-in method → Google
3. Enable **Firestore Database** (start in test mode)
4. Enable **Storage** (start in test mode)

## 2. Link the project locally

```bash
firebase use --add
# Select your newly created project and give it an alias, e.g. "local"
```

## 3. Install dependencies

```bash
# Root (frontend)
npm install

# Functions
cd functions && npm install && cd ..
```
# Some functions
$ firebase deploy --only functions:new_user_callback

## 4. Configure environment

Copy the Firebase web app config from your project's console (**Project Settings → Your apps → SDK setup**) into `src/config/firestore.ts`, replacing the existing credentials.

Set the Storage bucket URL so the `project_file_storage_delete_callback` function targets the right bucket:

```bash
# functions/.env (create if it doesn't exist)
STORAGE_BUCKET_URL=<your-project-id>.appspot.com
```

When developing run `firebase serve --only functions`
When deploying run `firebase deploy --only functions`
Watch the logs from the command line with firebase functions:log
### Search server (optional)

The search server (`search/`) connects to Firestore directly using Admin SDK service account keys. These are **not committed to the repo**. To enable it:

1. Go to Firebase Console → **Project Settings → Service accounts → Generate new private key**
2. Save the downloaded JSON files as:
- `search/service-key-dev.json`
- `search/service-key-prod.json`
3. Uncomment the code in `search/firebase.ts`

> The search server is not required for UI/UX testing — the frontend search calls go through the deployed `search_projects` function instead. Skip this unless you need to run the standalone search server.

## 5. Deploy functions

> **Blaze plan required.** Cloud Functions deployment requires the Firebase Blaze (pay-as-you-go) plan. Upgrade your test project at:
> `https://console.firebase.google.com/project/<your-project-id>/usage/details`
> There is no charge unless you exceed the free-tier limits, which is unlikely for local testing.

## Understanding the firebase-cli
Firebase is pretty strict on the directory structure. All cloud functions must be uploaded from one .js file from the functions directory.
The local frontend still calls production-style callable functions, so deploy them to your test project first:

## Troubleshoot
- Read the firebase-debug.log file when something crashes
- Common tip is to try `npm install -g @google-cloud/functions-emulator` for strange errors
```bash
cd functions
npm run build
firebase deploy --only functions
```

## 6. Start the frontend

```bash
# From the repo root
npm run dev
```

## Online resources
- https://github.com/firebase/functions-samples
- https://firebase.google.com/docs/functions/firestore-events
The app will be available at <http://localhost:3000>.
43 changes: 38 additions & 5 deletions functions/src/delete_user.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import admin from "firebase-admin";
import functions from "firebase-functions/v1";
import { HttpsError, onCall } from "firebase-functions/v2/https";
import { log } from "firebase-functions/logger";

const deleteUserDocument = async (
Expand Down Expand Up @@ -108,17 +109,49 @@ const deleteProjectsCount = async (
}
};

const cleanupDeletedUserData = async (
user: admin.auth.UserRecord
): Promise<void> => {
await deleteUserProjects(user);
await deleteProfileDocument(user);
await deleteUserDocument(user);
await deleteUsernameDocument(user);
await deleteProjectsCount(user);
};

export const deleteAccount = onCall({ cors: true }, async (request) => {
const auth = request.auth;

if (!auth?.uid) {
throw new HttpsError("unauthenticated", "Authentication required.");
}

try {
const user = await admin.auth().getUser(auth.uid);
await cleanupDeletedUserData(user);
await admin.auth().deleteUser(auth.uid);

return { success: true };
} catch (error) {
log(
"deleteAccount error: " +
JSON.stringify(error, Object.getOwnPropertyNames(error || {}))
);

throw new HttpsError(
"internal",
"Failed to delete account and associated data."
);
}
});

export const deleteUserCallback = functions.auth
.user()
.onDelete(async (user) => {
log(
`deleteUserCallback: Removing user: ${user.displayName}, uid: ${user.uid}`
);
await deleteUserProjects(user);
await deleteProfileDocument(user);
await deleteUserDocument(user);
await deleteUsernameDocument(user);
await deleteProjectsCount(user);
await cleanupDeletedUserData(user);

return true;
});
Loading
Loading