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
2 changes: 2 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ jobs:
VITE_RPC_URL: https://soroban-testnet.stellar.org:443
VITE_NETWORK_PASSPHRASE: "Test SDF Network ; September 2015"
VITE_API_URL: http://localhost:3001
VITE_MOCK_WALLET: "true"
- run: npx playwright install --with-deps chromium
working-directory: frontend
- run: npm run test:e2e
Expand All @@ -62,6 +63,7 @@ jobs:
VITE_RPC_URL: https://soroban-testnet.stellar.org:443
VITE_NETWORK_PASSPHRASE: "Test SDF Network ; September 2015"
VITE_API_URL: http://localhost:3001
VITE_MOCK_WALLET: "true"
- uses: actions/upload-artifact@v4
if: failure()
with:
Expand Down
6 changes: 6 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ ADMIN_API_KEY=change-me-to-a-random-secret-key
# Data file path (relative to backend directory)
DATA_FILE=data.json

# Database URL (used by Knex migrations)
# SQLite (development): leave as default or set a file path
# DATABASE_URL=./dev.db
# PostgreSQL (production):
# DATABASE_URL=postgresql://user:password@host:5432/dbname

# Redis configuration (optional — falls back to in-memory)
# REDIS_URL=redis://localhost:6379
# CACHE_TTL_SECONDS=60
Expand Down
75 changes: 75 additions & 0 deletions backend/docs/migrations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Database Migrations

The backend uses **[Knex.js](https://knexjs.org/)** as the migration tool.
Migration state is tracked automatically in a `knex_migrations` table created by Knex on first run.

## Configuration

| Environment | Client | Connection |
|-------------|--------|------------|
| `development` | SQLite (`better-sqlite3`) | `./dev.db` or `DATABASE_URL` env var |
| `test` | SQLite in-memory | `:memory:` |
| `production` | PostgreSQL (`pg`) | `DATABASE_URL` env var (required) |

Set `DATABASE_URL` in your `.env` file:

```env
# SQLite (dev)
DATABASE_URL=./dev.db

# PostgreSQL (production)
DATABASE_URL=postgresql://user:password@host:5432/dbname
```

## Running migrations

```bash
# Apply all pending migrations
npm run migrate

# Roll back the most recent migration batch
npm run migrate:rollback

# Show applied / pending migration status
npm run migrate:status
```

## Creating a new migration

```bash
npm run migrate:make -- <migration_name>
# Example:
npm run migrate:make -- add_location_to_assets
```

This generates a timestamped file in `backend/migrations/`.
Edit the generated file and implement `up` (apply) and `down` (rollback):

```js
export async function up(knex) {
await knex.schema.table('assets', (table) => {
table.string('location');
});
}

export async function down(knex) {
await knex.schema.table('assets', (table) => {
table.dropColumn('location');
});
}
```

## Migration files

| File | Description |
|------|-------------|
| `20260628000000_create_assets_table.js` | Creates the `assets` table with core columns |

## How it works

1. On first run, Knex creates a `knex_migrations` table to record which migrations have been applied and in which batch.
2. `npm run migrate` runs all migrations that are not yet recorded in that table.
3. `npm run migrate:rollback` reverts the last batch of migrations by calling their `down` functions.
4. Each migration file exports an `up` and a `down` function — always implement both to support rollbacks.

> **Tip for contributors:** every PR that changes the database schema must include a new migration file. Never edit an existing migration that has already been applied in production.
34 changes: 34 additions & 0 deletions backend/knexfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Knex configuration for database migrations.
// Uses SQLite in development/test and PostgreSQL in production.
// Set DATABASE_URL in your .env to override the default SQLite path.

/** @type {import('knex').Knex.Config} */
const base = {
migrations: {
directory: './migrations',
tableName: 'knex_migrations',
},
};

export default {
development: {
...base,
client: 'better-sqlite3',
connection: { filename: process.env.DATABASE_URL || './dev.db' },
useNullAsDefault: true,
},

test: {
...base,
client: 'better-sqlite3',
connection: { filename: ':memory:' },
useNullAsDefault: true,
},

production: {
...base,
client: 'pg',
connection: process.env.DATABASE_URL,
pool: { min: 2, max: 10 },
},
};
30 changes: 30 additions & 0 deletions backend/migrations/20260628000000_create_assets_table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Initial migration: create the assets table.
*
* Each row represents one tokenized real-world asset, keyed by its
* Soroban contract ID. The `metadata` column stores any extra JSON
* fields that don't warrant their own column.
*
* @param {import('knex').Knex} knex
*/
export async function up(knex) {
await knex.schema.createTable('assets', (table) => {
table.string('contract_id').primary();
table.string('name').notNullable();
table.string('symbol').notNullable();
table.text('description');
table.string('image_url');
table.string('asset_type');
table.decimal('total_value', 20, 2);
table.string('currency', 10).defaultTo('USD');
table.jsonb('metadata');
table.timestamps(true, true); // created_at / updated_at
});
}

/**
* @param {import('knex').Knex} knex
*/
export async function down(knex) {
await knex.schema.dropTableIfExists('assets');
}
Loading
Loading