Skip to content

Commit f5b6f88

Browse files
author
Daniele Briggi
committed
feat(example): sport tracker app initial commit
1 parent 7fe2c47 commit f5b6f88

27 files changed

+4743
-2
lines changed

.gitignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
*.xcbkptlist
55
*.plist
66
/build
7-
/dist
7+
**/dist/**
88
/coverage
99
*.sqlite
1010
*.a
1111
unittest
1212
/curl/src
13-
.vscode
13+
.vscode
14+
**/node_modules/**
15+
.env
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copy from from the SQLite Cloud Dashboard
2+
# eg: sqlitecloud://myhost.cloud:8860/my-remote-database.sqlite
3+
VITE_SQLITECLOUD_CONNECTION_STRING=
4+
# The database name
5+
# eg: my-remote-database.sqlite
6+
VITE_SQLITECLOUD_DATABASE=
7+
# Your SQLite Cloud API key
8+
# Copy it from the SQLite Cloud Dashboard -> Settings -> API Keys
9+
VITE_SQLITECLOUD_API_KEY=
10+
# Your SQLite Cloud url for APIs
11+
# Get it from the SQLite Cloud Dashboard in the Weblite section
12+
# eg: https://myhost.cloud
13+
VITE_SQLITECLOUD_API_URL=
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Sport Tracker app with SQLite Sync 🚵
2+
3+
A Vite/React demonstration app showcasing [**SQLite Sync**](https://github.com/sqliteai/sqlite-sync) implementation for **offline-first** data synchronization across multiple devices. This example illustrates how to integrate SQLite AI's sync capabilities into modern web applications with proper authentication via [Access Token](https://docs.sqlitecloud.io/docs/access-tokens) and [Row-Level Security (RLS)](https://docs.sqlitecloud.io).
4+
5+
6+
## Features
7+
8+
From a **user experience** perspective, this is a simple sport tracking application where users can:
9+
- Create accounts and log activities (running, cycling, swimming, etc.)
10+
- View personal statistics and workout history
11+
- Access "Coach Mode" for managing multiple users' workouts
12+
13+
From a **developer perspective**, this app showcases:
14+
- **Offline-first** architecture with sync to the remote database using **SQLite Sync** extension for SQLite
15+
- **Row-Level Security (RLS)** implementation for data isolation and access control on the SQLite Cloud database
16+
- **Access Tokens** for secure user authentication with SQLite Sync and RLS policy enforcement
17+
- **Multi-user** data isolation and sharing patterns across different user sessions
18+
19+
## Setup Instructions
20+
21+
### 1. Prerequisites
22+
- Node.js 18+
23+
- [SQLite Cloud account](https://sqlitecloud.io)
24+
25+
### 2. Database Setup
26+
1. Create database in [SQLite Cloud Dashboard](https://dashboard.sqlitecloud.io/).
27+
2. Execute the exact schema from `sport-tracker-schema.sql`.
28+
3. Enable OffSync for all tables on the remote database from the **SQLite Cloud Dashboard -> Databases**.
29+
4. Enable and configure RLS policies on the **SQLite Cloud Dashboard -> Databases**. See the file `rls-policies.md`.
30+
31+
### 3. Environment Configuration
32+
33+
Rename the `.env.example` into `.env` and fill with your values.
34+
35+
### 4. Installation & Run
36+
37+
```bash
38+
npm install
39+
npm run dev
40+
```
41+
42+
## Demo Use Case: Multi-User Sync Scenario
43+
44+
This walkthrough demonstrates how SQLite Sync handles offline-first synchronization between multiple users:
45+
46+
### The Story: Bob the Runner & Coach Sarah
47+
48+
1. **Bob starts tracking offline** 📱
49+
- Open [localhost:5173](http://localhost:5173) in your browser
50+
- Create user `bob` and add some activities
51+
- Notice Bob's data is stored locally - no internet required!
52+
53+
2. **Bob goes online and syncs** 🌐
54+
- Click `SQLite Sync` to authenticate SQLite Sync
55+
- Click `Sync & Refresh` - this generates an Access Token and synchronizes Bob's local data to the cloud
56+
- Bob's activities are now replicated in the cloud
57+
58+
3. **Coach Sarah joins from another device** 👩‍💼
59+
- Open a new private/incognito browser window at [localhost:5173](http://localhost:5173)
60+
- Create user `coach` (this triggers special coach privileges via RLS)
61+
- Enable `SQLite Sync` - Coach can now see Bob's synced activities thanks to RLS policies
62+
63+
4. **Coach creates a workout for Bob** 💪
64+
- Coach creates a workout assigned to Bob
65+
- Click `Sync & Refresh` to upload the workout to the cloud
66+
67+
5. **Bob receives his workout** 📲
68+
- Go back to Bob's browser window
69+
- Click `Sync & Refresh` - Bob's local database downloads the new workout from Coach
70+
- Bob can now see his personalized workout
71+
72+
6. **Bob gets a new device** 📱➡️💻
73+
- Log out Bob, then select it and click `Restore from cloud`
74+
- This simulates Bob logging in from a completely new device with no local data
75+
- Enable `SQLite Sync` and sync - all of Bob's activities and workouts are restored from the cloud
76+
77+
**Key takeaway**: Users can work offline, sync when convenient, and seamlessly restore data on new devices!
78+
79+
80+
## SQLite Sync Implementation
81+
82+
### 1. Database Initialization
83+
84+
```typescript
85+
// database.ts - Initialize sync for each table
86+
export class Database {
87+
async initSync() {
88+
await this.exec('SELECT cloudsync_init("users")');
89+
await this.exec('SELECT cloudsync_init("activities")');
90+
await this.exec('SELECT cloudsync_init("workouts")');
91+
}
92+
}
93+
```
94+
95+
### 2. Token Management
96+
97+
```typescript
98+
// SQLiteSync.ts - Access token handling
99+
private async getValidToken(userId: string, name: string): Promise<string> {
100+
const storedTokenData = localStorage.getItem('token');
101+
102+
if (storedTokenData) {
103+
const parsed: TokenData = JSON.parse(storedTokenData);
104+
const tokenExpiry = new Date(parsed.expiresAt);
105+
106+
if (tokenExpiry > new Date()) {
107+
return parsed.token; // Use cached token
108+
}
109+
}
110+
111+
// Fetch new token from API
112+
const tokenData = await this.fetchNewToken(userId, name);
113+
localStorage.setItem('token', JSON.stringify(tokenData));
114+
return tokenData.token;
115+
}
116+
```
117+
118+
Then authorize SQLite Sync with the token. This operation is executed again when tokens expire and a new one is provided.
119+
120+
```typescript
121+
async sqliteSyncSetToken(token: string) {
122+
await this.exec(`SELECT cloudsync_network_set_token('${token}')`);
123+
}
124+
```
125+
126+
### 3. Synchronization
127+
128+
The sync operation sends local changes to the cloud and receives remote changes:
129+
130+
```typescript
131+
async sqliteSyncNetworkSync() {
132+
await this.exec('SELECT cloudsync_network_sync()');
133+
}
134+
```
135+
136+
## Row-Level Security (RLS)
137+
138+
This app demonstrates **Row-Level Security** configured in the SQLite Cloud Dashboard. RLS policies ensure:
139+
140+
- **Users** can only see their own activities and workouts
141+
- **Coaches** can access all users' data and create workouts for the users
142+
- **Data isolation** is enforced at the database level
143+
144+
### Example RLS Policies
145+
146+
```sql
147+
-- Policy for selecting activities
148+
auth_userid() = user_id OR json_extract(auth_json(), '$.name') = 'coach'
149+
150+
-- Policy for inserting into workouts table
151+
json_extract(auth_json(), '$.name') = 'coach'
152+
```
153+
154+
> **Note**: Configure RLS policies in your SQLite Cloud Dashboard under Databases → RLS
155+
156+
## Security Considerations
157+
158+
⚠️ **Important**: This demo includes client-side API key usage for simplicity. In production:
159+
160+
- Never expose API keys in client code
161+
- Use **server-side generation** for Access Tokens
162+
- Implement a proper authentication flow
163+
164+
## Documentation Links
165+
166+
Explore the code and learn more:
167+
168+
- **SQLite Sync Documentation**: [sqlite-sync](https://github.com/sqliteai/sqlite-sync)
169+
- **Access Tokens Guide**: [SQLite Cloud Access Tokens](https://docs.sqlitecloud.com/authentication/access-tokens)
170+
- **Row-Level Security**: [SQLite Cloud RLS](https://docs.sqlitecloud.com)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Sport Tracker</title>
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<script type="module" src="/src/main.tsx"></script>
12+
</body>
13+
</html>

0 commit comments

Comments
 (0)