Compare commits
4 Commits
8f3a1f2962
...
730021a5ea
| Author | SHA1 | Date | |
|---|---|---|---|
|
730021a5ea
|
|||
|
c698a85cc1
|
|||
|
462799ca3d
|
|||
|
dcb48a5d5e
|
24
README.md
24
README.md
@@ -19,6 +19,11 @@ A modern publisher dashboard for managing organizations, members, and sites. Bui
|
|||||||
- **PostgreSQL** database
|
- **PostgreSQL** database
|
||||||
- **Postmark** for transactional emails
|
- **Postmark** for transactional emails
|
||||||
|
|
||||||
|
### CLI (`apps/cli`)
|
||||||
|
- **Stricli** for command parsing
|
||||||
|
- API token-based authentication
|
||||||
|
- User, organization, and site management commands
|
||||||
|
|
||||||
### Shared Packages
|
### Shared Packages
|
||||||
- `@reviq/api-contract` - Shared API contract (oRPC)
|
- `@reviq/api-contract` - Shared API contract (oRPC)
|
||||||
- `@reviq/db` - Database client and queries
|
- `@reviq/db` - Database client and queries
|
||||||
@@ -31,7 +36,7 @@ A modern publisher dashboard for managing organizations, members, and sites. Bui
|
|||||||
publisher-dashboard/
|
publisher-dashboard/
|
||||||
├── apps/
|
├── apps/
|
||||||
│ ├── api-server/ # Backend API server
|
│ ├── api-server/ # Backend API server
|
||||||
│ ├── cli/ # CLI tools
|
│ ├── cli/ # Command-line interface
|
||||||
│ └── publisher-dashboard/ # SvelteKit frontend
|
│ └── publisher-dashboard/ # SvelteKit frontend
|
||||||
├── packages/
|
├── packages/
|
||||||
│ ├── api-contract/ # Shared oRPC contract
|
│ ├── api-contract/ # Shared oRPC contract
|
||||||
@@ -107,6 +112,23 @@ bun run dev
|
|||||||
| `bun run test` | Run tests |
|
| `bun run test` | Run tests |
|
||||||
| `bun run db:codegen` | Generate database types |
|
| `bun run db:codegen` | Generate database types |
|
||||||
|
|
||||||
|
## CLI
|
||||||
|
|
||||||
|
The `@reviq/cli` package provides a command-line interface for managing users, organizations, and sites. See [apps/cli/README.md](apps/cli/README.md) for detailed usage.
|
||||||
|
|
||||||
|
Quick start:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build the CLI
|
||||||
|
bun run --cwd apps/cli build
|
||||||
|
|
||||||
|
# Login with an API token
|
||||||
|
./apps/cli/dist/reviq auth login --token <your-token>
|
||||||
|
|
||||||
|
# Check status
|
||||||
|
./apps/cli/dist/reviq auth status
|
||||||
|
```
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -238,3 +238,64 @@ export async function createTestUser(
|
|||||||
export async function destroyTestDb(db: Kysely<Database>): Promise<void> {
|
export async function destroyTestDb(db: Kysely<Database>): Promise<void> {
|
||||||
await db.destroy();
|
await db.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Shared Database Singleton (for transaction-based test isolation)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
let sharedDb: Kysely<Database> | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the shared test database once.
|
||||||
|
* Runs migrations and truncates all tables to start with a clean slate.
|
||||||
|
* Subsequent calls return the existing connection.
|
||||||
|
*
|
||||||
|
* Use this with `withTestTransaction()` for fast test isolation.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* beforeAll(async () => {
|
||||||
|
* await initTestDb();
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* test("does something", async () => {
|
||||||
|
* await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
|
* // test code using db
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export async function initTestDb(): Promise<Kysely<Database>> {
|
||||||
|
if (!sharedDb) {
|
||||||
|
await runMigrations();
|
||||||
|
sharedDb = createTestDb();
|
||||||
|
await truncateAllTables(sharedDb); // Clean slate once at start
|
||||||
|
}
|
||||||
|
return sharedDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the shared test database connection.
|
||||||
|
* Must call `initTestDb()` first.
|
||||||
|
*
|
||||||
|
* @throws Error if database not initialized
|
||||||
|
*/
|
||||||
|
export function getSharedDb(): Kysely<Database> {
|
||||||
|
if (!sharedDb) {
|
||||||
|
throw new Error(
|
||||||
|
"Test DB not initialized. Call initTestDb() in beforeAll first.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return sharedDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the shared test database connection.
|
||||||
|
* Call this in a global afterAll if needed.
|
||||||
|
*/
|
||||||
|
export async function destroySharedDb(): Promise<void> {
|
||||||
|
if (sharedDb) {
|
||||||
|
await sharedDb.destroy();
|
||||||
|
sharedDb = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
60
apps/api-server/src/__tests__/helpers/test-transaction.ts
Normal file
60
apps/api-server/src/__tests__/helpers/test-transaction.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Transaction-based test isolation helper
|
||||||
|
*
|
||||||
|
* Wraps test code in a transaction that auto-rollbacks, providing
|
||||||
|
* fast test isolation without truncating tables between tests.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Database } from "@reviq/db-schema";
|
||||||
|
import type { Kysely } from "kysely";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signal used to trigger transaction rollback after test completes
|
||||||
|
*/
|
||||||
|
class RollbackSignal extends Error {
|
||||||
|
constructor() {
|
||||||
|
super("RollbackSignal");
|
||||||
|
this.name = "RollbackSignal";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a test function inside a transaction that auto-rollbacks.
|
||||||
|
*
|
||||||
|
* The transaction implements the same interface as Kysely<Database>,
|
||||||
|
* so it can be passed to context builders and used for all queries.
|
||||||
|
* After the test completes, the transaction is rolled back, providing
|
||||||
|
* instant cleanup without truncating tables.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* test("creates user", async () => {
|
||||||
|
* await withTestTransaction(getSharedDb(), async (db) => {
|
||||||
|
* const user = await createTestUser(db, { email: "test@example.com" });
|
||||||
|
* const ctx = createAPIContext({ db });
|
||||||
|
* // ... test code
|
||||||
|
* }); // Auto-rollback here
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export async function withTestTransaction<T>(
|
||||||
|
db: Kysely<Database>,
|
||||||
|
testFn: (trx: Kysely<Database>) => Promise<T>,
|
||||||
|
): Promise<T | undefined> {
|
||||||
|
let result: T | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.transaction().execute(async (trx) => {
|
||||||
|
result = await testFn(trx);
|
||||||
|
// Force rollback by throwing after test completes successfully
|
||||||
|
throw new RollbackSignal();
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Swallow the rollback signal - this is expected behavior
|
||||||
|
if (!(e instanceof RollbackSignal)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
86
apps/cli/README.md
Normal file
86
apps/cli/README.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# RevIQ CLI
|
||||||
|
|
||||||
|
Command-line interface for RevIQ database and user management.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build the CLI
|
||||||
|
bun run build
|
||||||
|
|
||||||
|
# The compiled binary will be at dist/reviq
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run directly with bun
|
||||||
|
bun run cli <command>
|
||||||
|
|
||||||
|
# Or use the compiled binary
|
||||||
|
./dist/reviq <command>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Login with an API token
|
||||||
|
reviq auth login --token <your-token>
|
||||||
|
|
||||||
|
# Check authentication status
|
||||||
|
reviq auth status
|
||||||
|
|
||||||
|
# Logout
|
||||||
|
reviq auth logout
|
||||||
|
```
|
||||||
|
|
||||||
|
To get an API token:
|
||||||
|
1. Log in to the web dashboard
|
||||||
|
2. Go to Account Settings > API Tokens
|
||||||
|
3. Create a new token and copy it
|
||||||
|
|
||||||
|
### User Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a new user
|
||||||
|
reviq user create --email <email>
|
||||||
|
|
||||||
|
# Confirm email
|
||||||
|
reviq user confirm-email --code <code>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Organization Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List organizations
|
||||||
|
reviq org list
|
||||||
|
|
||||||
|
# Create an organization
|
||||||
|
reviq org create --name <name> --slug <slug>
|
||||||
|
|
||||||
|
# Add a site to an organization
|
||||||
|
reviq org add-site --org <slug> --domain <domain>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Admin Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Complete login (admin)
|
||||||
|
reviq admin complete-login
|
||||||
|
```
|
||||||
|
|
||||||
|
### Other Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Bootstrap the database
|
||||||
|
reviq bootstrap
|
||||||
|
|
||||||
|
# Generate shell completions
|
||||||
|
reviq completions
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Credentials are stored at `~/.config/reviq/credentials.json`.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
\restrict CIj4ub2A9kD8NQM2nKa1cg31hNutT3jXdOch0DnJ2bT48qpQKbe9XxNtViPwfYR
|
\restrict F9AizESreuRieL4inRcHWWg3hyNET0FgnBDFBBBU3cZGPEpHjb591l8S2iglpap
|
||||||
|
|
||||||
-- Dumped from database version 17.7
|
-- Dumped from database version 17.7
|
||||||
-- Dumped by pg_dump version 17.7
|
-- Dumped by pg_dump version 17.7
|
||||||
@@ -1084,7 +1084,7 @@ ALTER TABLE ONLY public.user_devices
|
|||||||
-- PostgreSQL database dump complete
|
-- PostgreSQL database dump complete
|
||||||
--
|
--
|
||||||
|
|
||||||
\unrestrict CIj4ub2A9kD8NQM2nKa1cg31hNutT3jXdOch0DnJ2bT48qpQKbe9XxNtViPwfYR
|
\unrestrict F9AizESreuRieL4inRcHWWg3hyNET0FgnBDFBBBU3cZGPEpHjb591l8S2iglpap
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|||||||
Reference in New Issue
Block a user