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
35 changes: 33 additions & 2 deletions CODE_GUIDELINES.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,33 @@ bun add package-name
bun run script-name
```

### Variable Naming

**CRITICAL**: Use descriptive variable names that clearly indicate what the value represents.

- Include units when dealing with time-related values
- Use specific names that describe the data type and purpose
- Avoid generic names that don't provide context

```typescript
// Good - descriptive names with units
const currentUnixTimestamp = Math.floor(Date.now() / 1000);
const currentDateTime = new Date();
const userResponseData = await response.json();
const tapCountResult = await db.prepare('SELECT COUNT(*) as count FROM taps').first();

// Good - descriptive names without units when context is clear
const userId = session.user.id;
const userName = user.name;
const tapType = 'resist';

// Bad - non-descriptive or generic
const now = Math.floor(Date.now() / 1000);
const now = new Date();
const data = await response.json();
const result = await db.prepare('SELECT COUNT(*) as count FROM taps').first();
```

### Type-Only Imports

**RECOMMENDED**: Use `import type` for type-only imports.
Expand Down Expand Up @@ -379,7 +406,11 @@ database_id = "local-dev-db"

```typescript
export class UserService {
constructor(private db: D1Database) {}
private db: D1Database;

constructor(db: D1Database) {
this.db = db;
}

async findById(id: string): Promise<User | null> {
const result = await this.db
Expand All @@ -392,7 +423,7 @@ export class UserService {
}
```

**CRITICAL**: Use `private` parameter shorthand in constructor.
**CRITICAL**: Declare properties explicitly in the class and assign them in the constructor body.

### Async Methods

Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ A minimalist, faith-centered application designed to help users resist temptatio

## Features

- **Onboarding flow**: First-time user onboarding experience
- **Single tap**: Records successful resistance to temptation
- **Double tap**: Records yielding to temptation (honest tracking without judgment)
- Cross-device sync (desktop and mobile)
Expand Down Expand Up @@ -90,6 +91,11 @@ curl http://localhost:8787/api/auth/get-session -b cookies.txt
curl http://localhost:8787/api/me -b cookies.txt
```

**Mark onboarding as complete (authenticated):**
```bash
curl -X POST http://localhost:8787/api/me/onboarding -b cookies.txt
```

**Create tap (authenticated):**
```bash
curl -X POST http://localhost:8787/api/taps \
Expand Down Expand Up @@ -137,7 +143,8 @@ All authenticated endpoints (`/api/me`, `/api/taps`, etc.) require a valid sessi

**Application**:
- `GET /health` - Health check (no auth required)
- `GET /api/me` - Get user profile (auth required)
- `GET /api/me` - Get user profile (includes onboardingCompleted flag) (auth required)
- `POST /api/me/onboarding` - Mark onboarding as complete (auth required)
- `POST /api/taps` - Record a new tap (resist or yield) (auth required)
- `GET /api/taps` - List tap history (auth required)
- `GET /api/taps/stats` - Get statistics (auth required)
Expand Down
3 changes: 3 additions & 0 deletions api/src/db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,6 @@ CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token);
CREATE INDEX IF NOT EXISTS idx_accounts_user_id ON accounts(user_id);
CREATE INDEX IF NOT EXISTS idx_verifications_identifier ON verifications(identifier);
CREATE INDEX IF NOT EXISTS idx_taps_user_timestamp ON taps(user_id, timestamp);

-- Migration: Add onboarding_completed column to users table
ALTER TABLE users ADD COLUMN onboarding_completed INTEGER NOT NULL DEFAULT 0;
11 changes: 11 additions & 0 deletions api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { cors } from 'hono/cors';
import { createAuthMiddleware, requireAuth } from './middleware/auth.ts';
import { createAuth, type AuthEnv } from './lib/auth.ts';
import { TapService } from './services/TapService.ts';
import { UserService } from './services/UserService.ts';
import type { Bindings, Variables, CreateTapInput } from './types.ts';
import openAPISpec from './openapi.ts';

Expand Down Expand Up @@ -41,6 +42,16 @@ app.get('/api/me', requireAuth(), async (c) => {
return c.json({ user });
});

// Marks onboarding as complete for the authenticated user
app.post('/api/me/onboarding', requireAuth(), async (c) => {
const user = c.get('user')!;
const userService = new UserService(c.env.DB);

await userService.markOnboardingComplete(user.id);

return c.json({ success: true });
});

// Records a new tap (resist or yield) for the authenticated user
app.post('/api/taps', requireAuth(), async (c) => {
const user = c.get('user')!;
Expand Down
48 changes: 46 additions & 2 deletions api/src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,13 @@ const openAPISpec = {
type: 'string',
format: 'date-time',
description: 'When the user was last updated'
},
onboardingCompleted: {
type: 'boolean',
description: 'Whether the user has completed onboarding'
}
},
required: ['id', 'name', 'email', 'emailVerified', 'createdAt', 'updatedAt']
required: ['id', 'name', 'email', 'emailVerified', 'createdAt', 'updatedAt', 'onboardingCompleted']
},
Tap: {
type: 'object',
Expand Down Expand Up @@ -420,7 +424,8 @@ const openAPISpec = {
emailVerified: true,
image: null,
createdAt: '2024-01-07T12:00:00.000Z',
updatedAt: '2024-01-07T12:00:00.000Z'
updatedAt: '2024-01-07T12:00:00.000Z',
onboardingCompleted: false
}
}
}
Expand All @@ -442,6 +447,45 @@ const openAPISpec = {
}
}
},
'/api/me/onboarding': {
post: {
summary: 'Complete onboarding',
description: 'Marks the authenticated user as having completed onboarding.',
tags: ['Users'],
security: [{ BetterAuth: [] }],
requestBody: {
description: 'Empty request body'
},
responses: {
'200': {
description: 'Onboarding marked as complete',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
success: {
type: 'boolean',
example: true
}
}
}
}
}
},
'401': {
description: 'Unauthorized',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/Error'
}
}
}
}
}
}
},
'/api/taps': {
post: {
summary: 'Create tap',
Expand Down
10 changes: 7 additions & 3 deletions api/src/services/TapService.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import type { Tap, CreateTapInput, TapStats, TapListQuery } from '../types.ts';

export class TapService {
constructor(private db: D1Database) {}
private db: D1Database;

constructor(db: D1Database) {
this.db = db;
}

// Creates a new tap record for a user
async create(userId: string, input: CreateTapInput): Promise<Tap> {
const timestamp = input.timestamp ?? Math.floor(Date.now() / 1000);
const now = Math.floor(Date.now() / 1000);
const currentUnixTimestamp = Math.floor(Date.now() / 1000);

const result = await this.db
.prepare(
'INSERT INTO taps (user_id, type, category, timestamp, created_at) VALUES (?, ?, ?, ?, ?) RETURNING *'
)
.bind(userId, input.type, input.category ?? null, timestamp, now)
.bind(userId, input.type, input.category ?? null, timestamp, currentUnixTimestamp)
.first<Tap>();

if (!result) {
Expand Down
19 changes: 19 additions & 0 deletions api/src/services/UserService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { User } from '../types.ts';

export class UserService {
private db: D1Database;

constructor(db: D1Database) {
this.db = db;
}

// Marks onboarding as complete for a user
async markOnboardingComplete(userId: string): Promise<void> {
const currentUnixTimestamp = Math.floor(Date.now() / 1000);

await this.db
.prepare('UPDATE users SET onboarding_completed = 1, updated_at = ? WHERE id = ?')
.bind(currentUnixTimestamp, userId)
.run();
}
}
2 changes: 2 additions & 0 deletions api/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface AuthUser {
image: string | null;
createdAt: Date;
updatedAt: Date;
onboardingCompleted: boolean;
}

// Session from Better Auth
Expand All @@ -54,6 +55,7 @@ export interface User {
image: string | null;
created_at: number;
updated_at: number;
onboarding_completed: number;
}

export type TapType = 'resist' | 'yield';
Expand Down
10 changes: 5 additions & 5 deletions mobile/src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ interface LogOptions {
}

function getTimestamp(): string {
const now = new Date();
const currentDateTime = new Date();
const pad = (n: number) => n.toString().padStart(2, '0');

const hours = pad(now.getHours());
const minutes = pad(now.getMinutes());
const seconds = pad(now.getSeconds());
const millis = now.getMilliseconds().toString().padStart(3, '0');
const hours = pad(currentDateTime.getHours());
const minutes = pad(currentDateTime.getMinutes());
const seconds = pad(currentDateTime.getSeconds());
const millis = currentDateTime.getMilliseconds().toString().padStart(3, '0');

return `${hours}:${minutes}:${seconds}.${millis}`;
}
Expand Down