Skip to content
Open
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
59 changes: 59 additions & 0 deletions packages/api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# @readied/api

Backend API for Readied cloud sync. Built with Hono for edge runtime compatibility.

## Features

- **Magic Link Auth** - Passwordless authentication via email
- **Cloud Sync** - Push/pull encrypted notes across devices
- **Subscription Management** - Stripe integration for Pro tier

## Deployment

Deployable to:
- Cloudflare Workers (recommended)
- Vercel Edge Functions
- Deno Deploy
- Any Node.js runtime

## Development

```bash
# Start dev server
pnpm dev

# Typecheck
pnpm typecheck

# Deploy to Cloudflare
pnpm deploy
```

## Environment Variables

Set these as secrets in your deployment platform:

| Variable | Description |
|----------|-------------|
| `DATABASE_URL` | Neon Postgres connection string |
| `JWT_SECRET` | Secret for signing JWTs |
| `RESEND_API_KEY` | API key for Resend email service |
| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret |

## API Endpoints

### Auth
- `POST /auth/magic-link` - Request magic link email
- `POST /auth/verify` - Verify token and get JWT
- `POST /auth/refresh` - Refresh access token
- `GET /auth/me` - Get current user (protected)

### Sync
- `GET /sync?cursor=0` - Pull changes since cursor (protected)
- `POST /sync` - Push local changes (protected)
- `GET /sync/status` - Get sync status (protected)

### Subscription
- `POST /subscription/webhook` - Stripe webhook handler
- `GET /subscription/status` - Get subscription status (protected)
- `POST /subscription/portal` - Create Stripe portal session (protected)
11 changes: 11 additions & 0 deletions packages/api/drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'turso',
dbCredentials: {
url: process.env.TURSO_DATABASE_URL!,
authToken: process.env.TURSO_AUTH_TOKEN,
},
});
74 changes: 74 additions & 0 deletions packages/api/drizzle/0000_chubby_zzzax.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
CREATE TABLE `devices` (
`id` text PRIMARY KEY NOT NULL,
`user_id` text NOT NULL,
`device_id` text NOT NULL,
`name` text,
`platform` text,
`last_seen_at` text NOT NULL,
`created_at` text NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE INDEX `idx_devices_user_device` ON `devices` (`user_id`,`device_id`);--> statement-breakpoint
CREATE UNIQUE INDEX `idx_devices_unique` ON `devices` (`user_id`,`device_id`);--> statement-breakpoint
CREATE TABLE `magic_links` (
`id` text PRIMARY KEY NOT NULL,
`user_id` text NOT NULL,
`token` text NOT NULL,
`expires_at` text NOT NULL,
`used_at` text,
`created_at` text NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `magic_links_token_unique` ON `magic_links` (`token`);--> statement-breakpoint
CREATE INDEX `idx_magic_links_token` ON `magic_links` (`token`);--> statement-breakpoint
CREATE TABLE `subscriptions` (
`id` text PRIMARY KEY NOT NULL,
`user_id` text NOT NULL,
`stripe_customer_id` text,
`stripe_subscription_id` text,
`status` text DEFAULT 'inactive' NOT NULL,
`plan` text DEFAULT 'free' NOT NULL,
`trial_ends_at` text,
`current_period_end` text,
`canceled_at` text,
`created_at` text NOT NULL,
`updated_at` text NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE UNIQUE INDEX `subscriptions_user_id_unique` ON `subscriptions` (`user_id`);--> statement-breakpoint
CREATE TABLE `sync_cursors` (
`id` text PRIMARY KEY NOT NULL,
`user_id` text NOT NULL,
`device_id` text NOT NULL,
`last_synced_version` integer DEFAULT 0 NOT NULL,
`updated_at` text NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE INDEX `idx_sync_cursors_user_device` ON `sync_cursors` (`user_id`,`device_id`);--> statement-breakpoint
CREATE UNIQUE INDEX `idx_sync_cursors_unique` ON `sync_cursors` (`user_id`,`device_id`);--> statement-breakpoint
CREATE TABLE `sync_log` (
`id` text PRIMARY KEY NOT NULL,
`user_id` text NOT NULL,
`note_id` text NOT NULL,
`version` integer NOT NULL,
`operation` text NOT NULL,
`encrypted_data` text,
`device_id` text NOT NULL,
`created_at` text NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE INDEX `idx_sync_log_user_version` ON `sync_log` (`user_id`,`version`);--> statement-breakpoint
CREATE INDEX `idx_sync_log_user_note` ON `sync_log` (`user_id`,`note_id`);--> statement-breakpoint
CREATE TABLE `users` (
`id` text PRIMARY KEY NOT NULL,
`email` text NOT NULL,
`created_at` text NOT NULL,
`updated_at` text NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);
Loading