swap coach table for barcode products in appwrite
replace chat collection with barcode_products table, seed verified products, drop ollama env vars and types, prune setup docs
This commit is contained in:
+2
-12
@@ -2,21 +2,11 @@ VITE_APPWRITE_ENDPOINT=https://fra.cloud.appwrite.io/v1
|
|||||||
VITE_APPWRITE_PROJECT_ID=6a0752ee001fb2ef7138
|
VITE_APPWRITE_PROJECT_ID=6a0752ee001fb2ef7138
|
||||||
VITE_APPWRITE_DATABASE_ID=redbull_tracker
|
VITE_APPWRITE_DATABASE_ID=redbull_tracker
|
||||||
VITE_APPWRITE_COLLECTION_ID=intake_entries
|
VITE_APPWRITE_COLLECTION_ID=intake_entries
|
||||||
VITE_APPWRITE_CHAT_COLLECTION_ID=coach_chats
|
VITE_APPWRITE_BARCODE_COLLECTION_ID=barcode_products
|
||||||
|
|
||||||
# Optional. Leave blank in local dev so the app uses the current Vite origin,
|
# Optional. Leave blank in local dev so the app uses the current Vite origin.
|
||||||
# including fallback ports like http://127.0.0.1:5174.
|
|
||||||
VITE_APPWRITE_OAUTH_SUCCESS_URL=
|
VITE_APPWRITE_OAUTH_SUCCESS_URL=
|
||||||
VITE_APPWRITE_OAUTH_FAILURE_URL=
|
VITE_APPWRITE_OAUTH_FAILURE_URL=
|
||||||
|
|
||||||
# Server-only. Do not prefix with VITE_ or it will be exposed to the browser.
|
|
||||||
OLLAMA_API_KEY=
|
|
||||||
OLLAMA_MODEL=deepseek-v4-pro:cloud
|
|
||||||
VITE_OLLAMA_PROXY_URL=/api/ollama-chat
|
|
||||||
|
|
||||||
# Server/admin only. Never prefix with VITE_. Needed only for npm run setup:appwrite.
|
# Server/admin only. Never prefix with VITE_. Needed only for npm run setup:appwrite.
|
||||||
APPWRITE_API_KEY=
|
APPWRITE_API_KEY=
|
||||||
|
|
||||||
# Appwrite chat table columns needed for encrypted coach chats:
|
|
||||||
# userId, encryptedTitle, encryptedMessages, titleIv, messagesIv, salt, updatedAt as strings
|
|
||||||
# version as integer. Enable row security and Users -> Create at table level.
|
|
||||||
|
|||||||
+52
-198
@@ -1,222 +1,76 @@
|
|||||||
# Red Bull Intake Tracker Setup
|
# Red Bull tracker setup
|
||||||
|
|
||||||
## Commands
|
This app uses Appwrite for auth and intake entries.
|
||||||
|
|
||||||
```bash
|
## env
|
||||||
npm install
|
|
||||||
npm run dev
|
Copy `.env.example` to `.env.local`, then fill in:
|
||||||
npm run build
|
|
||||||
npm run lint
|
```sh
|
||||||
|
VITE_APPWRITE_ENDPOINT=https://fra.cloud.appwrite.io/v1
|
||||||
|
VITE_APPWRITE_PROJECT_ID=your_project_id
|
||||||
|
VITE_APPWRITE_DATABASE_ID=redbull_tracker
|
||||||
|
VITE_APPWRITE_COLLECTION_ID=intake_entries
|
||||||
|
APPWRITE_API_KEY=server_key_for_setup_only
|
||||||
```
|
```
|
||||||
|
|
||||||
The Vite dev app runs at `http://localhost:5173` unless that port is already taken.
|
Leave the OAuth URLs empty in local dev unless you need fixed callback URLs.
|
||||||
|
|
||||||
## Environment
|
## setup
|
||||||
|
|
||||||
Copy `.env.example` to `.env.local` and adjust IDs if you choose different Appwrite resource IDs:
|
Run:
|
||||||
|
|
||||||
```bash
|
```sh
|
||||||
cp .env.example .env.local
|
|
||||||
```
|
|
||||||
|
|
||||||
This app uses only the Appwrite browser SDK. Do not add an API key to the frontend.
|
|
||||||
|
|
||||||
To create/update the database tables from this repo, set a server/admin key as `APPWRITE_API_KEY` in `.env.local` and run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run setup:appwrite
|
npm run setup:appwrite
|
||||||
```
|
```
|
||||||
|
|
||||||
The setup script reads `APPWRITE_API_KEY` only from Node, never from browser code.
|
The script creates or updates:
|
||||||
|
|
||||||
Configured defaults:
|
- database: `redbull_tracker`
|
||||||
|
- table: `intake_entries`
|
||||||
|
- table permission: `Users -> Create`
|
||||||
|
- row security: enabled
|
||||||
|
|
||||||
- Endpoint: `https://fra.cloud.appwrite.io/v1`
|
Rows use per-user read, update, and delete permissions.
|
||||||
- Project ID: `6a0752ee001fb2ef7138`
|
|
||||||
- Project name: `Red Bull Tracker App`
|
|
||||||
- Database ID: `redbull_tracker`
|
|
||||||
- Collection ID: `intake_entries`
|
|
||||||
- Chat collection ID: `coach_chats`
|
|
||||||
|
|
||||||
`client.ping()` is called automatically during app boot in `src/App.tsx` through `pingAppwrite()` from `src/lib/appwrite.ts`.
|
## intake columns
|
||||||
|
|
||||||
## Auth
|
| key | type | required |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `userId` | String, 64 | Yes |
|
||||||
|
| `cans` | Float | Yes |
|
||||||
|
| `flavour` | String, 128 | Yes |
|
||||||
|
| `flavourAccent` | String, 32 | Yes |
|
||||||
|
| `sizeMl` | Integer | Yes |
|
||||||
|
| `pricePerCan` | Float | Yes |
|
||||||
|
| `dateTime` | DateTime | Yes |
|
||||||
|
| `notes` | String, 2000 | No |
|
||||||
|
| `store` | String, 256 | No |
|
||||||
|
| `sugarFree` | Boolean | Yes |
|
||||||
|
| `caffeineMgPerCan` | Float | No |
|
||||||
|
| `importKey` | String, 512 | Yes |
|
||||||
|
| `source` | String, 32 | Yes |
|
||||||
|
|
||||||
Enable these auth methods in Appwrite Console:
|
## indexes
|
||||||
|
|
||||||
- Email/password
|
- `user_date_desc`: `userId`, `dateTime`
|
||||||
- GitHub OAuth
|
- `user_import_key`: `userId`, `importKey`
|
||||||
- Google OAuth
|
|
||||||
|
|
||||||
Add a Web platform in Appwrite Console for local development:
|
## run
|
||||||
|
|
||||||
- Hostname: `localhost`
|
```sh
|
||||||
- Hostname: `127.0.0.1`
|
npm install
|
||||||
|
npm run dev
|
||||||
If `client.ping()` shows `Failed to fetch`, this is usually the first thing to check.
|
|
||||||
|
|
||||||
For local OAuth callback URLs, add:
|
|
||||||
|
|
||||||
- Success URL: `http://localhost:5173`
|
|
||||||
- Failure URL: `http://localhost:5173`
|
|
||||||
- If Vite starts on another port, add that origin too, for example `http://127.0.0.1:5174`
|
|
||||||
|
|
||||||
For production, add your deployed origin as both success and failure URL, then update the `VITE_APPWRITE_OAUTH_*` variables.
|
|
||||||
|
|
||||||
In local dev, you can leave `VITE_APPWRITE_OAUTH_SUCCESS_URL` and `VITE_APPWRITE_OAUTH_FAILURE_URL` blank. The app will use the current browser origin automatically, which avoids getting redirected to a stale Vite port.
|
|
||||||
|
|
||||||
If OAuth returns to the app but you are still logged out:
|
|
||||||
|
|
||||||
- Confirm the current browser origin is listed under Appwrite project platforms, for example `localhost` and `127.0.0.1`.
|
|
||||||
- Confirm the same origin is allowed in the OAuth provider success/failure URLs.
|
|
||||||
- Clear old sessions/cookies for the local app and try again.
|
|
||||||
- Restart Vite after editing `.env.local`.
|
|
||||||
|
|
||||||
## Database
|
|
||||||
|
|
||||||
Appwrite currently uses newer Console wording in many places:
|
|
||||||
|
|
||||||
| In this app / older SDK wording | Current Appwrite Console wording |
|
|
||||||
| --- | --- |
|
|
||||||
| Collection | Table |
|
|
||||||
| Attribute | Column |
|
|
||||||
| Document | Row |
|
|
||||||
|
|
||||||
So if the Console asks you to create a **table**, that is the same resource as the `VITE_APPWRITE_COLLECTION_ID` this app currently points at. If the setup below says **attributes**, add them as **columns** inside that table.
|
|
||||||
|
|
||||||
The app uses Appwrite's current `TablesDB` SDK methods (`listRows`, `createRow`, `updateRow`, `deleteRow`). The env var remains named `VITE_APPWRITE_COLLECTION_ID` for compatibility with the first setup pass, but its value should be your table ID.
|
|
||||||
|
|
||||||
Create a database with ID:
|
|
||||||
|
|
||||||
```text
|
|
||||||
redbull_tracker
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Create a collection with ID:
|
## deployment-only files
|
||||||
|
|
||||||
```text
|
The repo ignores `.deploy/` and local public HTML pages.
|
||||||
intake_entries
|
|
||||||
```
|
|
||||||
|
|
||||||
Enable document-level permissions on the collection.
|
For your own deployment, create:
|
||||||
|
|
||||||
Recommended collection-level permissions:
|
- `.deploy/head.html` for analytics or other head-only snippets
|
||||||
|
- `.deploy/body-end.html` for footer links or deploy-only markup
|
||||||
|
- any local public HTML pages your host needs
|
||||||
|
|
||||||
- Create: `users`
|
Vite injects the optional `.deploy` snippets into `index.html` at build time.
|
||||||
- Read: none
|
|
||||||
- Update: none
|
|
||||||
- Delete: none
|
|
||||||
|
|
||||||
The app writes per-document permissions for the current user:
|
|
||||||
|
|
||||||
- `read("user:{userId}")`
|
|
||||||
- `update("user:{userId}")`
|
|
||||||
- `delete("user:{userId}")`
|
|
||||||
|
|
||||||
## Permission Troubleshooting
|
|
||||||
|
|
||||||
If the app shows:
|
|
||||||
|
|
||||||
```text
|
|
||||||
No permissions provided for action 'create'
|
|
||||||
```
|
|
||||||
|
|
||||||
the table is reachable, but the signed-in user is not allowed to create rows yet.
|
|
||||||
|
|
||||||
Fix it in Appwrite Console:
|
|
||||||
|
|
||||||
1. Open **Databases**.
|
|
||||||
2. Open database `redbull_tracker`.
|
|
||||||
3. Open table `intake_entries`.
|
|
||||||
4. Go to **Settings**.
|
|
||||||
5. Enable **Row Security**.
|
|
||||||
6. Under **Permissions**, add role **Users**.
|
|
||||||
7. Check **Create** only.
|
|
||||||
8. Leave table-level **Read**, **Update**, and **Delete** unchecked.
|
|
||||||
9. Click **Update** / **Save**.
|
|
||||||
|
|
||||||
Why: table-level **Create** lets authenticated users add their own rows. The app then writes row-level read/update/delete permissions for that exact user, so users do not see each other's entries.
|
|
||||||
|
|
||||||
## Attributes
|
|
||||||
|
|
||||||
Create these attributes:
|
|
||||||
|
|
||||||
| Key | Type | Required | Notes |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `userId` | String, 64 | Yes | Current Appwrite user ID |
|
|
||||||
| `cans` | Float | Yes | Allows partial cans |
|
|
||||||
| `flavour` | String, 128 | Yes | Red Bull flavour |
|
|
||||||
| `flavourAccent` | String, 32 | Yes | UI colour |
|
|
||||||
| `sizeMl` | Integer | Yes | Can size in ml |
|
|
||||||
| `pricePerCan` | Float | Yes | GBP price per can |
|
|
||||||
| `dateTime` | DateTime | Yes | Intake timestamp |
|
|
||||||
| `notes` | String, 2000 | No | Optional notes |
|
|
||||||
| `store` | String, 256 | No | Store/location |
|
|
||||||
| `sugarFree` | Boolean | Yes | Sugar-free flag |
|
|
||||||
| `caffeineMgPerCan` | Float | No | Custom-size override |
|
|
||||||
| `importKey` | String, 512 | Yes | Duplicate detection signature |
|
|
||||||
| `source` | String, 32 | Yes | `manual`, `quick-add`, `excel`, or `json` |
|
|
||||||
|
|
||||||
Recommended indexes:
|
|
||||||
|
|
||||||
- `user_date_desc`: key index on `userId`, `dateTime`
|
|
||||||
- `user_import_key`: key index on `userId`, `importKey`
|
|
||||||
- Optional unique index on `userId`, `importKey` if your Appwrite plan/schema supports it
|
|
||||||
|
|
||||||
## Encrypted Coach Chats
|
|
||||||
|
|
||||||
Create a second table with ID:
|
|
||||||
|
|
||||||
```text
|
|
||||||
coach_chats
|
|
||||||
```
|
|
||||||
|
|
||||||
Enable row security on `coach_chats`.
|
|
||||||
|
|
||||||
Recommended table-level permissions:
|
|
||||||
|
|
||||||
- Create: `users`
|
|
||||||
- Read: none
|
|
||||||
- Update: none
|
|
||||||
- Delete: none
|
|
||||||
|
|
||||||
The app encrypts chat titles and messages in the browser before writing rows. The encryption passphrase is not stored, and Appwrite only receives ciphertext.
|
|
||||||
|
|
||||||
Create these chat columns:
|
|
||||||
|
|
||||||
| Key | Type | Required | Notes |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `userId` | String, 64 | Yes | Current Appwrite user ID |
|
|
||||||
| `encryptedTitle` | String, 4000 | Yes | AES-GCM ciphertext |
|
|
||||||
| `encryptedMessages` | String, 50000+ | Yes | AES-GCM ciphertext for message JSON |
|
|
||||||
| `titleIv` | String, 128 | Yes | Base64 IV |
|
|
||||||
| `messagesIv` | String, 128 | Yes | Base64 IV |
|
|
||||||
| `salt` | String, 128 | Yes | Base64 PBKDF2 salt |
|
|
||||||
| `version` | Integer | Yes | Crypto version |
|
|
||||||
| `updatedAt` | DateTime | Yes | Sort key |
|
|
||||||
|
|
||||||
Recommended chat index:
|
|
||||||
|
|
||||||
- `user_chat_updated`: key index on `userId`, `updatedAt`
|
|
||||||
|
|
||||||
## Component Structure
|
|
||||||
|
|
||||||
- `src/App.tsx`: UI shell, auth gate, dashboard/logbook/trends/coach/data views, modals, and action state.
|
|
||||||
- `src/lib/appwrite.ts`: Appwrite SDK client, account/database services, env config, and ping helper.
|
|
||||||
- `src/lib/appwriteEntries.ts`: User-scoped Appwrite CRUD, document permissions, duplicate signatures.
|
|
||||||
- `src/lib/encryptedChats.ts`: Client-side encrypted chat storage for Appwrite.
|
|
||||||
- `src/lib/excel.ts`: Styled `.xlsx` export, summary sheet, row validation, duplicate-aware import preview.
|
|
||||||
- `src/lib/metrics.ts`: Prices, caffeine/sugar estimates, stats, grouping, streaks.
|
|
||||||
- `src/lib/storage.ts`: JSON backup export/import parser.
|
|
||||||
- `src/data/flavours.ts`: Built-in flavours and accent metadata.
|
|
||||||
|
|
||||||
## Nutrition Defaults
|
|
||||||
|
|
||||||
- 250ml: `£1.75`, `80mg` caffeine
|
|
||||||
- 355ml: `£2.20`, `114mg` caffeine
|
|
||||||
- 473ml: `£2.85`, `151mg` caffeine
|
|
||||||
- Custom sizes: caffeine is proportional from 250ml unless a custom override is entered
|
|
||||||
|
|
||||||
The UI shows this disclaimer:
|
|
||||||
|
|
||||||
> Caffeine and sugar values are estimates. Check the can label for exact nutritional information.
|
|
||||||
|
|||||||
+63
-13
@@ -1,6 +1,7 @@
|
|||||||
/* global console, fetch, process, setTimeout */
|
/* global console, fetch, process, setTimeout */
|
||||||
|
|
||||||
import { existsSync, readFileSync } from "node:fs";
|
import { existsSync, readFileSync } from "node:fs";
|
||||||
|
import { URL } from "node:url";
|
||||||
|
|
||||||
const env = loadEnvFiles([".env", ".env.local"]);
|
const env = loadEnvFiles([".env", ".env.local"]);
|
||||||
|
|
||||||
@@ -8,8 +9,11 @@ const endpoint = readEnv("VITE_APPWRITE_ENDPOINT", "https://fra.cloud.appwrite.i
|
|||||||
const projectId = readEnv("VITE_APPWRITE_PROJECT_ID", "6a0752ee001fb2ef7138");
|
const projectId = readEnv("VITE_APPWRITE_PROJECT_ID", "6a0752ee001fb2ef7138");
|
||||||
const databaseId = readEnv("VITE_APPWRITE_DATABASE_ID", "redbull_tracker");
|
const databaseId = readEnv("VITE_APPWRITE_DATABASE_ID", "redbull_tracker");
|
||||||
const intakeTableId = readEnv("VITE_APPWRITE_COLLECTION_ID", "intake_entries");
|
const intakeTableId = readEnv("VITE_APPWRITE_COLLECTION_ID", "intake_entries");
|
||||||
const chatTableId = readEnv("VITE_APPWRITE_CHAT_COLLECTION_ID", "coach_chats");
|
const barcodeTableId = readEnv("VITE_APPWRITE_BARCODE_COLLECTION_ID", "barcode_products");
|
||||||
const apiKey = readEnv("APPWRITE_API_KEY", "");
|
const apiKey = readEnv("APPWRITE_API_KEY", "");
|
||||||
|
const verifiedBarcodeProducts = JSON.parse(
|
||||||
|
readFileSync(new URL("../src/data/verified-barcodes.json", import.meta.url), "utf8"),
|
||||||
|
);
|
||||||
|
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
throw new Error("APPWRITE_API_KEY missing. Add a server/admin Appwrite key to .env.local, without VITE_.");
|
throw new Error("APPWRITE_API_KEY missing. Add a server/admin Appwrite key to .env.local, without VITE_.");
|
||||||
@@ -40,20 +44,30 @@ await ensureTable({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
await ensureTable({
|
await ensureTable({
|
||||||
tableId: chatTableId,
|
tableId: barcodeTableId,
|
||||||
name: "Coach chats",
|
name: "Barcode products",
|
||||||
columns: [
|
columns: [
|
||||||
{ kind: "string", key: "userId", size: 64, required: true },
|
{ kind: "string", key: "scope", size: 16, required: true },
|
||||||
{ kind: "string", key: "encryptedTitle", size: 4000, required: true, encrypt: true },
|
{ kind: "string", key: "ownerUserId", size: 64, required: false },
|
||||||
{ kind: "longtext", key: "encryptedMessages", required: true, encrypt: true },
|
{ kind: "string", key: "barcode", size: 32, required: true },
|
||||||
{ kind: "string", key: "titleIv", size: 128, required: true },
|
{ kind: "string", key: "flavourName", size: 128, required: true },
|
||||||
{ kind: "string", key: "messagesIv", size: 128, required: true },
|
{ kind: "integer", key: "sizeMl", required: true },
|
||||||
{ kind: "string", key: "salt", size: 128, required: true },
|
{ kind: "float", key: "pricePerCan", required: true },
|
||||||
{ kind: "integer", key: "version", required: true },
|
{ kind: "boolean", key: "sugarFree", required: true },
|
||||||
{ kind: "datetime", key: "updatedAt", required: true },
|
{ kind: "float", key: "caffeineMgPerCan", required: false },
|
||||||
|
{ kind: "string", key: "verifiedBy", size: 512, required: false },
|
||||||
|
{ kind: "string", key: "sourceName", size: 512, required: false },
|
||||||
|
{ kind: "string", key: "sourceUrl", size: 2048, required: false },
|
||||||
|
{ kind: "string", key: "variant", size: 64, required: false },
|
||||||
|
{ kind: "string", key: "notes", size: 2000, required: false },
|
||||||
|
],
|
||||||
|
indexes: [
|
||||||
|
{ key: "barcode", type: "key", columns: ["barcode"], orders: ["ASC"], lengths: [32] },
|
||||||
|
{ key: "scope_barcode", type: "key", columns: ["scope", "barcode"], orders: ["ASC", "ASC"], lengths: [16, 32] },
|
||||||
|
{ key: "user_barcode", type: "key", columns: ["ownerUserId", "barcode"], orders: ["ASC", "ASC"], lengths: [64, 32] },
|
||||||
],
|
],
|
||||||
indexes: [{ key: "user_chat_updated", type: "key", columns: ["userId", "updatedAt"], orders: ["ASC", "DESC"], lengths: [32] }],
|
|
||||||
});
|
});
|
||||||
|
await seedVerifiedBarcodeProducts(barcodeTableId, verifiedBarcodeProducts);
|
||||||
|
|
||||||
console.log("Appwrite database and tables ready.");
|
console.log("Appwrite database and tables ready.");
|
||||||
|
|
||||||
@@ -116,12 +130,48 @@ async function ensureColumn(tableId, column) {
|
|||||||
array: false,
|
array: false,
|
||||||
};
|
};
|
||||||
if (column.size) body.size = column.size;
|
if (column.size) body.size = column.size;
|
||||||
if (column.encrypt) body.encrypt = true;
|
|
||||||
|
|
||||||
await request("POST", `/tablesdb/${databaseId}/tables/${tableId}/columns/${column.kind}`, body, [202, 201]);
|
await request("POST", `/tablesdb/${databaseId}/tables/${tableId}/columns/${column.kind}`, body, [202, 201]);
|
||||||
console.log(`Column ${tableId}.${column.key} created.`);
|
console.log(`Column ${tableId}.${column.key} created.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function seedVerifiedBarcodeProducts(tableId, products) {
|
||||||
|
for (const [barcode, product] of Object.entries(products)) {
|
||||||
|
const rowId = `verified_${barcode}`;
|
||||||
|
const data = {
|
||||||
|
scope: "verified",
|
||||||
|
ownerUserId: "",
|
||||||
|
barcode,
|
||||||
|
flavourName: product.flavourName,
|
||||||
|
sizeMl: product.sizeMl,
|
||||||
|
pricePerCan: product.pricePerCan,
|
||||||
|
sugarFree: Boolean(product.sugarFree),
|
||||||
|
caffeineMgPerCan: product.caffeineMgPerCan,
|
||||||
|
verifiedBy: product.verifiedBy ?? "",
|
||||||
|
sourceName: product.sourceName ?? "",
|
||||||
|
sourceUrl: product.sourceUrl ?? "",
|
||||||
|
variant: product.variant ?? "",
|
||||||
|
notes: product.notes ?? "",
|
||||||
|
};
|
||||||
|
const path = `/tablesdb/${databaseId}/tables/${tableId}/rows/${rowId}`;
|
||||||
|
const existing = await request("GET", path, undefined, [200, 404]);
|
||||||
|
|
||||||
|
if (existing.status === 404) {
|
||||||
|
await request(
|
||||||
|
"POST",
|
||||||
|
`/tablesdb/${databaseId}/tables/${tableId}/rows`,
|
||||||
|
{ rowId, data, permissions: ['read("users")'] },
|
||||||
|
[201],
|
||||||
|
);
|
||||||
|
console.log(`Verified barcode ${barcode} seeded.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await request("PUT", path, { data, permissions: ['read("users")'] }, [200]);
|
||||||
|
console.log(`Verified barcode ${barcode} updated.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function ensureIndex(tableId, index) {
|
async function ensureIndex(tableId, index) {
|
||||||
const existing = await request("GET", `/tablesdb/${databaseId}/tables/${tableId}/indexes/${index.key}`, undefined, [200, 404]);
|
const existing = await request("GET", `/tablesdb/${databaseId}/tables/${tableId}/indexes/${index.key}`, undefined, [200, 404]);
|
||||||
if (existing.status === 200) {
|
if (existing.status === 200) {
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ export const appwriteConfig = {
|
|||||||
projectId: env.VITE_APPWRITE_PROJECT_ID || "6a0752ee001fb2ef7138",
|
projectId: env.VITE_APPWRITE_PROJECT_ID || "6a0752ee001fb2ef7138",
|
||||||
databaseId: env.VITE_APPWRITE_DATABASE_ID || "redbull_tracker",
|
databaseId: env.VITE_APPWRITE_DATABASE_ID || "redbull_tracker",
|
||||||
collectionId: env.VITE_APPWRITE_COLLECTION_ID || "intake_entries",
|
collectionId: env.VITE_APPWRITE_COLLECTION_ID || "intake_entries",
|
||||||
chatCollectionId: env.VITE_APPWRITE_CHAT_COLLECTION_ID || "coach_chats",
|
barcodeCollectionId: env.VITE_APPWRITE_BARCODE_COLLECTION_ID || "barcode_products",
|
||||||
oauthSuccessUrl: resolveOAuthUrl(env.VITE_APPWRITE_OAUTH_SUCCESS_URL),
|
oauthSuccessUrl: resolveOAuthUrl(env.VITE_APPWRITE_OAUTH_SUCCESS_URL),
|
||||||
oauthFailureUrl: resolveOAuthUrl(env.VITE_APPWRITE_OAUTH_FAILURE_URL),
|
oauthFailureUrl: resolveOAuthUrl(env.VITE_APPWRITE_OAUTH_FAILURE_URL),
|
||||||
};
|
};
|
||||||
|
|||||||
Vendored
+1
-2
@@ -5,10 +5,9 @@ interface ImportMetaEnv {
|
|||||||
readonly VITE_APPWRITE_PROJECT_ID?: string;
|
readonly VITE_APPWRITE_PROJECT_ID?: string;
|
||||||
readonly VITE_APPWRITE_DATABASE_ID?: string;
|
readonly VITE_APPWRITE_DATABASE_ID?: string;
|
||||||
readonly VITE_APPWRITE_COLLECTION_ID?: string;
|
readonly VITE_APPWRITE_COLLECTION_ID?: string;
|
||||||
readonly VITE_APPWRITE_CHAT_COLLECTION_ID?: string;
|
readonly VITE_APPWRITE_BARCODE_COLLECTION_ID?: string;
|
||||||
readonly VITE_APPWRITE_OAUTH_SUCCESS_URL?: string;
|
readonly VITE_APPWRITE_OAUTH_SUCCESS_URL?: string;
|
||||||
readonly VITE_APPWRITE_OAUTH_FAILURE_URL?: string;
|
readonly VITE_APPWRITE_OAUTH_FAILURE_URL?: string;
|
||||||
readonly VITE_OLLAMA_PROXY_URL?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
|
|||||||
Reference in New Issue
Block a user