Compare commits
12 Commits
38739a3a0c
...
upgrade-ze
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7adb76f752 | ||
|
|
018dab38c0 | ||
|
|
b428ce172d | ||
|
|
92a2057179 | ||
|
|
0f958feb8d | ||
|
|
acfe62eb37 | ||
|
|
213c819cfd | ||
|
|
d8e5e8e522 | ||
|
|
9aad5a7e4e | ||
|
|
3ea28db9ac | ||
|
|
1b841c7d32 | ||
|
|
b0ebc98d42 |
@@ -12,3 +12,9 @@ ZERO_GET_QUERIES_FORWARD_COOKIES="true"
|
|||||||
|
|
||||||
ZERO_MUTATE_URL="http://localhost:3000/api/zero/mutate"
|
ZERO_MUTATE_URL="http://localhost:3000/api/zero/mutate"
|
||||||
ZERO_MUTATE_FORWARD_COOKIES="true"
|
ZERO_MUTATE_FORWARD_COOKIES="true"
|
||||||
|
|
||||||
|
PLAID_CLIENT_ID=
|
||||||
|
PLAID_SECRET=
|
||||||
|
PLAID_ENV=sandbox
|
||||||
|
|
||||||
|
EXPO_PUBLIC_TAILSCALE_MACHINE=laptop
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -31,7 +31,8 @@ yarn-error.*
|
|||||||
*.pem
|
*.pem
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env
|
.env*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
@@ -45,4 +46,3 @@ app-example
|
|||||||
.direnv/
|
.direnv/
|
||||||
.db/
|
.db/
|
||||||
.logs
|
.logs
|
||||||
.env
|
|
||||||
|
|||||||
56
README.md
56
README.md
@@ -1,50 +1,20 @@
|
|||||||
# Welcome to your Expo app đź‘‹
|
# Money
|
||||||
|
|
||||||
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
|
Personal finance application by [Max Koon](https://max.koon.us). Writen with Typescript, Expo/React Native, Zero Sync Engine, Better Auth, and Drizzle.
|
||||||
|
|
||||||
## Get started
|
## Development
|
||||||
|
|
||||||
1. Install dependencies
|
```sh
|
||||||
|
git clone https://git.koon.us/max/money.git
|
||||||
```bash
|
direnv allow
|
||||||
npm install
|
cp .env.example .env.dev
|
||||||
```
|
ln -s .env.dev .env
|
||||||
|
vim .env.dev # Update with Plaid credentials
|
||||||
2. Start the app
|
pnpm install
|
||||||
|
pnpm dev
|
||||||
```bash
|
|
||||||
npx expo start
|
|
||||||
```
|
|
||||||
|
|
||||||
In the output, you'll find options to open the app in a
|
|
||||||
|
|
||||||
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
|
|
||||||
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
|
|
||||||
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
|
|
||||||
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
|
|
||||||
|
|
||||||
You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
|
|
||||||
|
|
||||||
## Get a fresh project
|
|
||||||
|
|
||||||
When you're ready, run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run reset-project
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
|
## Deployment
|
||||||
|
|
||||||
## Learn more
|
An example deployment of this application can be found [here](https://git.koon.us/max/os/src/branch/main/host/ark/service/money.nix).
|
||||||
|
|
||||||
To learn more about developing your project with Expo, look at the following resources:
|
|
||||||
|
|
||||||
- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
|
|
||||||
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
|
|
||||||
|
|
||||||
## Join the community
|
|
||||||
|
|
||||||
Join our community of developers creating universal apps.
|
|
||||||
|
|
||||||
- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
|
|
||||||
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { genericOAuth } from "better-auth/plugins";
|
|||||||
import { expo } from "@better-auth/expo";
|
import { expo } from "@better-auth/expo";
|
||||||
import { drizzleSchema } from "@money/shared/db";
|
import { drizzleSchema } from "@money/shared/db";
|
||||||
import { db } from "./db";
|
import { db } from "./db";
|
||||||
|
import { BASE_URL, HOST } from "@money/shared";
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
database: drizzleAdapter(db, {
|
database: drizzleAdapter(db, {
|
||||||
@@ -11,7 +12,19 @@ export const auth = betterAuth({
|
|||||||
provider: "pg",
|
provider: "pg",
|
||||||
usePlural: true,
|
usePlural: true,
|
||||||
}),
|
}),
|
||||||
trustedOrigins: ["money://", "http://localhost:8081"],
|
trustedOrigins: [
|
||||||
|
"http://localhost:8081",
|
||||||
|
`exp://${HOST}:8081`,
|
||||||
|
`${BASE_URL}:8081`,
|
||||||
|
"https://money.koon.us",
|
||||||
|
"money://",
|
||||||
|
],
|
||||||
|
advanced: {
|
||||||
|
crossSubDomainCookies: {
|
||||||
|
enabled: process.env.NODE_ENV == 'production',
|
||||||
|
domain: "koon.us",
|
||||||
|
},
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
expo(),
|
expo(),
|
||||||
genericOAuth({
|
genericOAuth({
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { serve } from "@hono/node-server";
|
import { serve } from "@hono/node-server";
|
||||||
import { authDataSchema } from "@money/shared/auth";
|
import { authDataSchema } from "@money/shared/auth";
|
||||||
|
import { BASE_URL } from "@money/shared";
|
||||||
import { cors } from "hono/cors";
|
import { cors } from "hono/cors";
|
||||||
import { auth } from "./auth";
|
import { auth } from "./auth";
|
||||||
import { getHono } from "./hono";
|
import { getHono } from "./hono";
|
||||||
@@ -10,7 +11,7 @@ const app = getHono();
|
|||||||
app.use(
|
app.use(
|
||||||
"/api/*",
|
"/api/*",
|
||||||
cors({
|
cors({
|
||||||
origin: (origin) => origin ?? "",
|
origin: ['https://money.koon.us', `${BASE_URL}:8081`],
|
||||||
allowMethods: ["POST", "GET", "OPTIONS"],
|
allowMethods: ["POST", "GET", "OPTIONS"],
|
||||||
allowHeaders: ["Content-Type", "Authorization"],
|
allowHeaders: ["Content-Type", "Authorization"],
|
||||||
credentials: true,
|
credentials: true,
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ import { Configuration, CountryCode, PlaidApi, PlaidEnvironments, Products } fro
|
|||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from "crypto";
|
||||||
import { db } from "./db";
|
import { db } from "./db";
|
||||||
import { balance, plaidAccessTokens, plaidLink, transaction } from "@money/shared/db";
|
import { balance, plaidAccessTokens, plaidLink, transaction } from "@money/shared/db";
|
||||||
import { asc, desc, eq, inArray, sql, type InferInsertModel } from "drizzle-orm";
|
import { eq, inArray, sql, type InferInsertModel } from "drizzle-orm";
|
||||||
|
|
||||||
|
|
||||||
const configuration = new Configuration({
|
const configuration = new Configuration({
|
||||||
basePath: PlaidEnvironments.production,
|
basePath: process.env.PLAID_ENV == 'production' ? PlaidEnvironments.production : PlaidEnvironments.sandbox,
|
||||||
baseOptions: {
|
baseOptions: {
|
||||||
headers: {
|
headers: {
|
||||||
'PLAID-CLIENT-ID': process.env.PLAID_CLIENT_ID,
|
'PLAID-CLIENT-ID': process.env.PLAID_CLIENT_ID,
|
||||||
@@ -116,7 +116,7 @@ const createMutators = (authData: AuthData | null) => {
|
|||||||
const { data } = await plaidClient.transactionsGet({
|
const { data } = await plaidClient.transactionsGet({
|
||||||
access_token: account.token,
|
access_token: account.token,
|
||||||
start_date: "2025-10-01",
|
start_date: "2025-10-01",
|
||||||
end_date: "2025-10-18",
|
end_date: new Date().toISOString().split("T")[0],
|
||||||
});
|
});
|
||||||
|
|
||||||
const transactions = data.transactions.map(tx => ({
|
const transactions = data.transactions.map(tx => ({
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useMemo } from 'react';
|
|||||||
import { authDataSchema } from '@/shared/src/auth';
|
import { authDataSchema } from '@/shared/src/auth';
|
||||||
import { Platform } from 'react-native';
|
import { Platform } from 'react-native';
|
||||||
import type { ZeroOptions } from '@rocicorp/zero';
|
import type { ZeroOptions } from '@rocicorp/zero';
|
||||||
import { schema, type Schema, createMutators, type Mutators } from '@/shared/src';
|
import { schema, type Schema, createMutators, type Mutators, BASE_URL } from '@/shared/src';
|
||||||
import { expoSQLiteStoreProvider } from "@rocicorp/zero/react-native";
|
import { expoSQLiteStoreProvider } from "@rocicorp/zero/react-native";
|
||||||
|
|
||||||
export const unstable_settings = {
|
export const unstable_settings = {
|
||||||
@@ -32,7 +32,7 @@ export default function RootLayout() {
|
|||||||
return {
|
return {
|
||||||
storageKey: 'money',
|
storageKey: 'money',
|
||||||
kvStore,
|
kvStore,
|
||||||
server: process.env.NODE_ENV == 'production' ? 'https://zero.koon.us' : 'http://localhost:4848',
|
server: process.env.NODE_ENV == 'production' ? 'https://zero.koon.us' : `${BASE_URL}:4848`,
|
||||||
userID: authData?.user.id ?? "anon",
|
userID: authData?.user.id ?? "anon",
|
||||||
schema,
|
schema,
|
||||||
mutators: createMutators(authData),
|
mutators: createMutators(authData),
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { Button, View } from "react-native";
|
import { Button, View } from "react-native";
|
||||||
import { authClient } from "@/lib/auth-client";
|
import { authClient } from "@/lib/auth-client";
|
||||||
|
import { BASE_URL } from "@money/shared";
|
||||||
|
|
||||||
export default function Auth() {
|
export default function Auth() {
|
||||||
const onLogin = () => {
|
const onLogin = () => {
|
||||||
authClient.signIn.oauth2({
|
authClient.signIn.oauth2({
|
||||||
providerId: "koon-family",
|
providerId: "koon-family",
|
||||||
callbackURL: "http://localhost:8081"
|
callbackURL: process.env.NODE_ENV == 'production' ? 'https://money.koon.us' : `${BASE_URL}:8081`,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,83 +1,35 @@
|
|||||||
import { authClient } from '@/lib/auth-client';
|
import { authClient } from '@/lib/auth-client';
|
||||||
import { Pressable, ScrollView, Text, View } from 'react-native';
|
import { RefreshControl, ScrollView, StatusBar, Text, View } from 'react-native';
|
||||||
import { useQuery, useZero } from "@rocicorp/zero/react";
|
import { useQuery, useZero } from "@rocicorp/zero/react";
|
||||||
import { queries, type Mutators, type Schema } from '@money/shared';
|
import { queries, type Mutators, type Schema } from '@money/shared';
|
||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
export default function HomeScreen() {
|
export default function HomeScreen() {
|
||||||
const { data: session } = authClient.useSession();
|
const { data: session } = authClient.useSession();
|
||||||
|
|
||||||
const z = useZero<Schema, Mutators>();
|
|
||||||
const [transactions] = useQuery(queries.allTransactions(session));
|
|
||||||
const [balances] = useQuery(queries.getBalances(session));
|
const [balances] = useQuery(queries.getBalances(session));
|
||||||
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
|
||||||
const [idx, setIdx] = useState(0);
|
const onRefresh = async () => {
|
||||||
const [accountIdx, setAccountIdx] = useState(0);
|
setRefreshing(true);
|
||||||
|
// simulate async work
|
||||||
const account = balances.at(accountIdx)!;
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
setRefreshing(false);
|
||||||
const filteredTransactions = transactions
|
|
||||||
.filter(t => t.account_id == account.plaid_id)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
|
||||||
if (event.key === "j") {
|
|
||||||
setIdx((prevIdx) => {
|
|
||||||
if (prevIdx + 1 == filteredTransactions.length) return prevIdx;
|
|
||||||
return prevIdx + 1
|
|
||||||
});
|
|
||||||
} else if (event.key === "k") {
|
|
||||||
setIdx((prevIdx) => prevIdx == 0 ? 0 : prevIdx - 1);
|
|
||||||
} else if (event.key == 'g') {
|
|
||||||
setIdx(0);
|
|
||||||
} else if (event.key == "G") {
|
|
||||||
setIdx(transactions.length - 1);
|
|
||||||
} else if (event.key == 'R') {
|
|
||||||
z.mutate.link.updateTransactions();
|
|
||||||
z.mutate.link.updateBalences();
|
|
||||||
} else if (event.key == 'h') {
|
|
||||||
setAccountIdx((prevIdx) => prevIdx == 0 ? 0 : prevIdx - 1);
|
|
||||||
} else if (event.key == 'l') {
|
|
||||||
setAccountIdx((prevIdx) => {
|
|
||||||
if (prevIdx + 1 == balances.length) return prevIdx;
|
|
||||||
return prevIdx + 1
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("keydown", handleKeyDown);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("keydown", handleKeyDown);
|
|
||||||
};
|
|
||||||
}, [filteredTransactions, balances]);
|
|
||||||
|
|
||||||
function lpad(n: number): string {
|
|
||||||
const LEN = 9;
|
|
||||||
const nstr = n.toFixed(2).toLocaleString();
|
|
||||||
return Array.from({ length: LEN - nstr.length }).join(" ") + nstr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<>
|
||||||
<View style={{ flexDirection: "row" }}>
|
<StatusBar barStyle="dark-content" />
|
||||||
<View style={{ backgroundColor: '' }}>
|
<ScrollView contentContainerStyle={{ paddingTop: StatusBar.currentHeight, flexGrow: 1 }} refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />} style={{ paddingHorizontal: 10 }}>
|
||||||
{balances.map((bal, i) => <View key={bal.id} style={{ backgroundColor: i == accountIdx ? 'black' : undefined}}>
|
{balances.map(balance => <Balance key={balance.id} balance={balance} />)}
|
||||||
<Text style={{ fontFamily: 'mono', color: i == accountIdx ? 'white' : undefined }}>{bal.name}: {bal.current} ({bal.avaliable})</Text>
|
|
||||||
</View>)}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View>
|
|
||||||
{filteredTransactions.map((t, i) => <Pressable onHoverIn={() => {
|
|
||||||
setIdx(i);
|
|
||||||
}} style={{ backgroundColor: i == idx ? 'black' : undefined, cursor: 'default' as 'auto' }} key={t.id}>
|
|
||||||
<Text style={{ fontFamily: 'mono', color: i == idx ? 'white' : undefined }}>{new Date(t.datetime!).toDateString()} <Text style={{ color: t.amount > 0 ? 'red' : 'green' }}>{lpad(t.amount)}</Text> {t.name.substring(0, 50)}</Text>
|
|
||||||
</Pressable>)}
|
|
||||||
</View>
|
|
||||||
<ScrollView>
|
|
||||||
<Text style={{ fontFamily: 'mono' }}>{JSON.stringify(JSON.parse(filteredTransactions.at(idx)?.json || "null"), null, 4)}</Text>
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</>
|
||||||
</View>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Balance({ balance }: { balance: { name: string, current: number, avaliable: number } }) {
|
||||||
|
return <View style={{ backgroundColor: "#eee", borderColor: "#ddd", borderWidth: 1, marginBottom: 10, borderRadius: 10 }}>
|
||||||
|
<Text style={{ fontSize: 15, textAlign: "center" }}>{balance.name}</Text>
|
||||||
|
<Text style={{ fontSize: 30, textAlign: "center" }}>{balance.current}</Text>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
|||||||
99
app/index.web.tsx
Normal file
99
app/index.web.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import { authClient } from '@/lib/auth-client';
|
||||||
|
import { Button, Image, Platform, Pressable, ScrollView, Text, View } from 'react-native';
|
||||||
|
import { useQuery, useZero } from "@rocicorp/zero/react";
|
||||||
|
import { queries, type Mutators, type Schema } from '@money/shared';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Link } from 'expo-router';
|
||||||
|
|
||||||
|
export default function HomeScreen() {
|
||||||
|
const { data: session } = authClient.useSession();
|
||||||
|
|
||||||
|
const z = useZero<Schema, Mutators>();
|
||||||
|
const [transactions] = useQuery(queries.allTransactions(session));
|
||||||
|
const [balances] = useQuery(queries.getBalances(session));
|
||||||
|
|
||||||
|
const [idx, setIdx] = useState(0);
|
||||||
|
const [accountIdx, setAccountIdx] = useState(0);
|
||||||
|
|
||||||
|
const account = balances.at(accountIdx)!;
|
||||||
|
|
||||||
|
const filteredTransactions = transactions
|
||||||
|
.filter(t => t.account_id == account.plaid_id)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (Platform.OS != 'web') return;
|
||||||
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === "j") {
|
||||||
|
setIdx((prevIdx) => {
|
||||||
|
if (prevIdx + 1 == filteredTransactions.length) return prevIdx;
|
||||||
|
return prevIdx + 1
|
||||||
|
});
|
||||||
|
} else if (event.key === "k") {
|
||||||
|
setIdx((prevIdx) => prevIdx == 0 ? 0 : prevIdx - 1);
|
||||||
|
} else if (event.key == 'g') {
|
||||||
|
setIdx(0);
|
||||||
|
} else if (event.key == "G") {
|
||||||
|
setIdx(transactions.length - 1);
|
||||||
|
} else if (event.key == 'R') {
|
||||||
|
z.mutate.link.updateTransactions();
|
||||||
|
z.mutate.link.updateBalences();
|
||||||
|
} else if (event.key == 'h') {
|
||||||
|
setAccountIdx((prevIdx) => prevIdx == 0 ? 0 : prevIdx - 1);
|
||||||
|
} else if (event.key == 'l') {
|
||||||
|
setAccountIdx((prevIdx) => {
|
||||||
|
if (prevIdx + 1 == balances.length) return prevIdx;
|
||||||
|
return prevIdx + 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("keydown", handleKeyDown);
|
||||||
|
};
|
||||||
|
}, [filteredTransactions, balances]);
|
||||||
|
|
||||||
|
function lpad(n: number): string {
|
||||||
|
const LEN = 9;
|
||||||
|
const nstr = n.toFixed(2).toLocaleString();
|
||||||
|
return Array.from({ length: LEN - nstr.length }).join(" ") + nstr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function uuu(t: typeof filteredTransactions[number]): string | undefined {
|
||||||
|
if (!t.json) return;
|
||||||
|
const j = JSON.parse(t.json);
|
||||||
|
return j.counterparties.filter((c: any) => !!c.logo_url).at(0)?.logo_url || j.personal_finance_category_icon_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<Link prefetch href="/settings">
|
||||||
|
<Button title="Settings" />
|
||||||
|
</Link>
|
||||||
|
<View style={{ flexDirection: "row" }}>
|
||||||
|
<View style={{ backgroundColor: '' }}>
|
||||||
|
{balances.map((bal, i) => <View key={bal.id} style={{ backgroundColor: i == accountIdx ? 'black' : undefined}}>
|
||||||
|
<Text style={{ fontFamily: 'mono', color: i == accountIdx ? 'white' : undefined }}>{bal.name}: {bal.current} ({bal.avaliable})</Text>
|
||||||
|
</View>)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View>
|
||||||
|
{filteredTransactions.map((t, i) => <Pressable onHoverIn={() => {
|
||||||
|
setIdx(i);
|
||||||
|
}} style={{ backgroundColor: i == idx ? 'black' : undefined, cursor: 'default' as 'auto' }} key={t.id}>
|
||||||
|
<Text style={{ fontFamily: 'mono', color: i == idx ? 'white' : undefined }}>
|
||||||
|
{new Date(t.datetime!).toDateString()}
|
||||||
|
<Text style={{ color: t.amount > 0 ? 'red' : 'green' }}> {lpad(t.amount)}</Text>
|
||||||
|
<Image style={{ width: 15, height: 15, marginHorizontal: 10 }} source={{ uri: uuu(t) || "" }} />
|
||||||
|
{t.name.substring(0, 50)}
|
||||||
|
</Text>
|
||||||
|
</Pressable>)}
|
||||||
|
</View>
|
||||||
|
<ScrollView>
|
||||||
|
<Text style={{ fontFamily: 'mono' }}>{JSON.stringify(JSON.parse(filteredTransactions.at(idx)?.json || "null"), null, 4)}</Text>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,9 +2,10 @@ import { createAuthClient } from "better-auth/react";
|
|||||||
import { genericOAuthClient } from "better-auth/client/plugins";
|
import { genericOAuthClient } from "better-auth/client/plugins";
|
||||||
import { expoClient } from "@better-auth/expo/client";
|
import { expoClient } from "@better-auth/expo/client";
|
||||||
import * as SecureStore from "expo-secure-store";
|
import * as SecureStore from "expo-secure-store";
|
||||||
|
import { BASE_URL } from "@money/shared";
|
||||||
|
|
||||||
export const authClient = createAuthClient({
|
export const authClient = createAuthClient({
|
||||||
baseURL: process.env.NODE_ENV == 'production' ? 'https://money-api.koon.us' : "http://localhost:3000",
|
baseURL: process.env.NODE_ENV == 'production' ? 'https://money-api.koon.us' : `${BASE_URL}:3000`,
|
||||||
plugins: [
|
plugins: [
|
||||||
expoClient({
|
expoClient({
|
||||||
scheme: "money",
|
scheme: "money",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"@react-navigation/bottom-tabs": "^7.4.0",
|
"@react-navigation/bottom-tabs": "^7.4.0",
|
||||||
"@react-navigation/elements": "^2.6.3",
|
"@react-navigation/elements": "^2.6.3",
|
||||||
"@react-navigation/native": "^7.1.8",
|
"@react-navigation/native": "^7.1.8",
|
||||||
"@rocicorp/zero": "^0.23.2025090100",
|
"@rocicorp/zero": "^0.24.2025101500",
|
||||||
"better-auth": "^1.3.27",
|
"better-auth": "^1.3.27",
|
||||||
"drizzle-orm": "^0.44.6",
|
"drizzle-orm": "^0.44.6",
|
||||||
"expo": "~54.0.13",
|
"expo": "~54.0.13",
|
||||||
|
|||||||
2584
pnpm-lock.yaml
generated
2584
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -25,10 +25,14 @@ processes:
|
|||||||
initial_delay_seconds: 2
|
initial_delay_seconds: 2
|
||||||
period_seconds: 1
|
period_seconds: 1
|
||||||
|
|
||||||
|
tailscale_machine_name:
|
||||||
|
command: "pnpm tsx ./scripts/set-machine-name.ts"
|
||||||
|
|
||||||
expo:
|
expo:
|
||||||
command: "pnpm start"
|
command: "pnpm start"
|
||||||
environment:
|
depends_on:
|
||||||
- "DATABASE_URL=postgresql://postgres@localhost:5432/money"
|
tailscale_machine_name:
|
||||||
|
condition: process_completed_successfully
|
||||||
|
|
||||||
api:
|
api:
|
||||||
command: "pnpm run dev"
|
command: "pnpm run dev"
|
||||||
|
|||||||
36
scripts/set-machine-name.ts
Executable file
36
scripts/set-machine-name.ts
Executable file
@@ -0,0 +1,36 @@
|
|||||||
|
import { execSync } from "child_process";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
const ENV_FILE = ".env.dev";
|
||||||
|
const VARIABLE_NAME = "EXPO_PUBLIC_TAILSCALE_MACHINE";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const json = execSync("tailscale status --self --json", { encoding: "utf8" });
|
||||||
|
const data = JSON.parse(json);
|
||||||
|
const machine = data?.Self?.DNSName.split(".")[0];
|
||||||
|
|
||||||
|
if (!machine) {
|
||||||
|
console.error("Error: could not retrieve Tailscale machine name");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = "";
|
||||||
|
if (fs.existsSync(ENV_FILE)) {
|
||||||
|
content = fs.readFileSync(ENV_FILE, "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
|
const pattern = new RegExp(`^${VARIABLE_NAME}=.*`, "m");
|
||||||
|
if (pattern.test(content)) {
|
||||||
|
content = content.replace(pattern, `${VARIABLE_NAME}=${machine}`);
|
||||||
|
} else {
|
||||||
|
if (content && !content.endsWith("\n")) content += "\n";
|
||||||
|
content += `${VARIABLE_NAME}=${machine}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(ENV_FILE, content, "utf8");
|
||||||
|
console.log(`Updated ${ENV_FILE} with ${VARIABLE_NAME}=${machine}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to update .env.dev:", err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -9,10 +9,10 @@
|
|||||||
"./db": "./src/db/index.ts"
|
"./db": "./src/db/index.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"drizzle-zero": "^0.14.3"
|
"drizzle-zero": "^0.15.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"generate:zero": "drizzle-zero generate -s ./src/db/schema/public.ts -o ./src/zero-schema.gen.ts -f && sed -i 's/enableLegacyQueries: true,/enableLegacyQueries: false,/g' src/zero-schema.gen.ts && sed -i 's/enableLegacyMutators: true,/enableLegacyMutators: false,/g' src/zero-schema.gen.ts",
|
"generate:zero": "drizzle-zero generate -s ./src/db/schema/public.ts -o ./src/zero-schema.gen.ts -f --disable-legacy-queries --disable-legacy-mutators",
|
||||||
"db:migrate": "drizzle-kit push"
|
"db:migrate": "drizzle-kit push"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
shared/src/const.ts
Normal file
3
shared/src/const.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const HOST = process.env.EXPO_PUBLIC_TAILSCALE_MACHINE || "localhost";
|
||||||
|
export const BASE_URL = `http://${HOST}`;
|
||||||
|
|
||||||
@@ -2,3 +2,4 @@ export * from "./queries";
|
|||||||
export * from "./mutators";
|
export * from "./mutators";
|
||||||
export * from "./zero-schema.gen";
|
export * from "./zero-schema.gen";
|
||||||
export * from "./zql";
|
export * from "./zql";
|
||||||
|
export * from "./const";
|
||||||
|
|||||||
@@ -5,14 +5,19 @@ import { type AuthData } from "./auth";
|
|||||||
import { isLoggedIn } from "./zql";
|
import { isLoggedIn } from "./zql";
|
||||||
|
|
||||||
export const queries = {
|
export const queries = {
|
||||||
|
me: syncedQueryWithContext('me', z.tuple([]), (authData: AuthData | null) => {
|
||||||
|
isLoggedIn(authData);
|
||||||
|
return builder.users
|
||||||
|
.where('id', '=', authData.user.id)
|
||||||
|
.one();
|
||||||
|
}),
|
||||||
allTransactions: syncedQueryWithContext('allTransactions', z.tuple([]), (authData: AuthData | null) => {
|
allTransactions: syncedQueryWithContext('allTransactions', z.tuple([]), (authData: AuthData | null) => {
|
||||||
isLoggedIn(authData);
|
isLoggedIn(authData);
|
||||||
return builder.transaction
|
return builder.transaction
|
||||||
.where('user_id', '=', authData.user.id)
|
.where('user_id', '=', authData.user.id)
|
||||||
.orderBy('datetime', 'desc')
|
.orderBy('datetime', 'desc')
|
||||||
.limit(50)
|
.limit(50)
|
||||||
}
|
}),
|
||||||
),
|
|
||||||
getPlaidLink: syncedQueryWithContext('getPlaidLink', z.tuple([]), (authData: AuthData | null) => {
|
getPlaidLink: syncedQueryWithContext('getPlaidLink', z.tuple([]), (authData: AuthData | null) => {
|
||||||
isLoggedIn(authData);
|
isLoggedIn(authData);
|
||||||
return builder.plaidLink
|
return builder.plaidLink
|
||||||
@@ -23,6 +28,7 @@ export const queries = {
|
|||||||
getBalances: syncedQueryWithContext('getBalances', z.tuple([]), (authData: AuthData | null) => {
|
getBalances: syncedQueryWithContext('getBalances', z.tuple([]), (authData: AuthData | null) => {
|
||||||
isLoggedIn(authData);
|
isLoggedIn(authData);
|
||||||
return builder.balance
|
return builder.balance
|
||||||
.where('user_id', '=', authData.user.id);
|
.where('user_id', '=', authData.user.id)
|
||||||
|
.orderBy('name', 'asc');
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,11 +10,9 @@
|
|||||||
|
|
||||||
import type { Row } from "@rocicorp/zero";
|
import type { Row } from "@rocicorp/zero";
|
||||||
import { createBuilder } from "@rocicorp/zero";
|
import { createBuilder } from "@rocicorp/zero";
|
||||||
import type { DrizzleToZeroSchema, ZeroCustomType } from "drizzle-zero";
|
import type { CustomType } from "drizzle-zero";
|
||||||
import type * as drizzleSchema from "./db/schema/public";
|
import type * as drizzleSchema from "./db/schema/public";
|
||||||
|
|
||||||
type ZeroSchema = DrizzleToZeroSchema<typeof drizzleSchema>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Zero schema object.
|
* The Zero schema object.
|
||||||
* This type is auto-generated from your Drizzle schema definition.
|
* This type is auto-generated from your Drizzle schema definition.
|
||||||
@@ -27,8 +25,8 @@ export const schema = {
|
|||||||
id: {
|
id: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"balance",
|
"balance",
|
||||||
"id"
|
"id"
|
||||||
>,
|
>,
|
||||||
@@ -36,8 +34,8 @@ export const schema = {
|
|||||||
user_id: {
|
user_id: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"balance",
|
"balance",
|
||||||
"user_id"
|
"user_id"
|
||||||
>,
|
>,
|
||||||
@@ -46,8 +44,8 @@ export const schema = {
|
|||||||
plaid_id: {
|
plaid_id: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"balance",
|
"balance",
|
||||||
"plaid_id"
|
"plaid_id"
|
||||||
>,
|
>,
|
||||||
@@ -56,8 +54,8 @@ export const schema = {
|
|||||||
avaliable: {
|
avaliable: {
|
||||||
type: "number",
|
type: "number",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"balance",
|
"balance",
|
||||||
"avaliable"
|
"avaliable"
|
||||||
>,
|
>,
|
||||||
@@ -65,8 +63,8 @@ export const schema = {
|
|||||||
current: {
|
current: {
|
||||||
type: "number",
|
type: "number",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"balance",
|
"balance",
|
||||||
"current"
|
"current"
|
||||||
>,
|
>,
|
||||||
@@ -74,8 +72,8 @@ export const schema = {
|
|||||||
name: {
|
name: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"balance",
|
"balance",
|
||||||
"name"
|
"name"
|
||||||
>,
|
>,
|
||||||
@@ -83,8 +81,8 @@ export const schema = {
|
|||||||
createdAt: {
|
createdAt: {
|
||||||
type: "number",
|
type: "number",
|
||||||
optional: true,
|
optional: true,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"balance",
|
"balance",
|
||||||
"createdAt"
|
"createdAt"
|
||||||
>,
|
>,
|
||||||
@@ -93,8 +91,8 @@ export const schema = {
|
|||||||
updatedAt: {
|
updatedAt: {
|
||||||
type: "number",
|
type: "number",
|
||||||
optional: true,
|
optional: true,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"balance",
|
"balance",
|
||||||
"updatedAt"
|
"updatedAt"
|
||||||
>,
|
>,
|
||||||
@@ -109,8 +107,8 @@ export const schema = {
|
|||||||
id: {
|
id: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"plaidLink",
|
"plaidLink",
|
||||||
"id"
|
"id"
|
||||||
>,
|
>,
|
||||||
@@ -118,8 +116,8 @@ export const schema = {
|
|||||||
user_id: {
|
user_id: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"plaidLink",
|
"plaidLink",
|
||||||
"user_id"
|
"user_id"
|
||||||
>,
|
>,
|
||||||
@@ -127,8 +125,8 @@ export const schema = {
|
|||||||
link: {
|
link: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"plaidLink",
|
"plaidLink",
|
||||||
"link"
|
"link"
|
||||||
>,
|
>,
|
||||||
@@ -136,8 +134,8 @@ export const schema = {
|
|||||||
token: {
|
token: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"plaidLink",
|
"plaidLink",
|
||||||
"token"
|
"token"
|
||||||
>,
|
>,
|
||||||
@@ -145,8 +143,8 @@ export const schema = {
|
|||||||
createdAt: {
|
createdAt: {
|
||||||
type: "number",
|
type: "number",
|
||||||
optional: true,
|
optional: true,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"plaidLink",
|
"plaidLink",
|
||||||
"createdAt"
|
"createdAt"
|
||||||
>,
|
>,
|
||||||
@@ -161,8 +159,8 @@ export const schema = {
|
|||||||
id: {
|
id: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"transaction",
|
"transaction",
|
||||||
"id"
|
"id"
|
||||||
>,
|
>,
|
||||||
@@ -170,8 +168,8 @@ export const schema = {
|
|||||||
user_id: {
|
user_id: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"transaction",
|
"transaction",
|
||||||
"user_id"
|
"user_id"
|
||||||
>,
|
>,
|
||||||
@@ -179,8 +177,8 @@ export const schema = {
|
|||||||
plaid_id: {
|
plaid_id: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"transaction",
|
"transaction",
|
||||||
"plaid_id"
|
"plaid_id"
|
||||||
>,
|
>,
|
||||||
@@ -188,8 +186,8 @@ export const schema = {
|
|||||||
account_id: {
|
account_id: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"transaction",
|
"transaction",
|
||||||
"account_id"
|
"account_id"
|
||||||
>,
|
>,
|
||||||
@@ -197,8 +195,8 @@ export const schema = {
|
|||||||
name: {
|
name: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"transaction",
|
"transaction",
|
||||||
"name"
|
"name"
|
||||||
>,
|
>,
|
||||||
@@ -206,8 +204,8 @@ export const schema = {
|
|||||||
amount: {
|
amount: {
|
||||||
type: "number",
|
type: "number",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"transaction",
|
"transaction",
|
||||||
"amount"
|
"amount"
|
||||||
>,
|
>,
|
||||||
@@ -215,8 +213,8 @@ export const schema = {
|
|||||||
datetime: {
|
datetime: {
|
||||||
type: "number",
|
type: "number",
|
||||||
optional: true,
|
optional: true,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"transaction",
|
"transaction",
|
||||||
"datetime"
|
"datetime"
|
||||||
>,
|
>,
|
||||||
@@ -224,8 +222,8 @@ export const schema = {
|
|||||||
authorized_datetime: {
|
authorized_datetime: {
|
||||||
type: "number",
|
type: "number",
|
||||||
optional: true,
|
optional: true,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"transaction",
|
"transaction",
|
||||||
"authorized_datetime"
|
"authorized_datetime"
|
||||||
>,
|
>,
|
||||||
@@ -233,8 +231,8 @@ export const schema = {
|
|||||||
json: {
|
json: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: true,
|
optional: true,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"transaction",
|
"transaction",
|
||||||
"json"
|
"json"
|
||||||
>,
|
>,
|
||||||
@@ -242,8 +240,8 @@ export const schema = {
|
|||||||
createdAt: {
|
createdAt: {
|
||||||
type: "number",
|
type: "number",
|
||||||
optional: true,
|
optional: true,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"transaction",
|
"transaction",
|
||||||
"createdAt"
|
"createdAt"
|
||||||
>,
|
>,
|
||||||
@@ -252,8 +250,8 @@ export const schema = {
|
|||||||
updatedAt: {
|
updatedAt: {
|
||||||
type: "number",
|
type: "number",
|
||||||
optional: true,
|
optional: true,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"transaction",
|
"transaction",
|
||||||
"updatedAt"
|
"updatedAt"
|
||||||
>,
|
>,
|
||||||
@@ -268,8 +266,8 @@ export const schema = {
|
|||||||
id: {
|
id: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"users",
|
"users",
|
||||||
"id"
|
"id"
|
||||||
>,
|
>,
|
||||||
@@ -277,8 +275,8 @@ export const schema = {
|
|||||||
name: {
|
name: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: true,
|
optional: true,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"users",
|
"users",
|
||||||
"name"
|
"name"
|
||||||
>,
|
>,
|
||||||
@@ -286,8 +284,8 @@ export const schema = {
|
|||||||
email: {
|
email: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: false,
|
optional: false,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"users",
|
"users",
|
||||||
"email"
|
"email"
|
||||||
>,
|
>,
|
||||||
@@ -295,8 +293,8 @@ export const schema = {
|
|||||||
emailVerified: {
|
emailVerified: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
optional: true,
|
optional: true,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"users",
|
"users",
|
||||||
"emailVerified"
|
"emailVerified"
|
||||||
>,
|
>,
|
||||||
@@ -305,8 +303,8 @@ export const schema = {
|
|||||||
image: {
|
image: {
|
||||||
type: "string",
|
type: "string",
|
||||||
optional: true,
|
optional: true,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"users",
|
"users",
|
||||||
"image"
|
"image"
|
||||||
>,
|
>,
|
||||||
@@ -314,8 +312,8 @@ export const schema = {
|
|||||||
createdAt: {
|
createdAt: {
|
||||||
type: "number",
|
type: "number",
|
||||||
optional: true,
|
optional: true,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"users",
|
"users",
|
||||||
"createdAt"
|
"createdAt"
|
||||||
>,
|
>,
|
||||||
@@ -324,8 +322,8 @@ export const schema = {
|
|||||||
updatedAt: {
|
updatedAt: {
|
||||||
type: "number",
|
type: "number",
|
||||||
optional: true,
|
optional: true,
|
||||||
customType: null as unknown as ZeroCustomType<
|
customType: null as unknown as CustomType<
|
||||||
ZeroSchema,
|
typeof drizzleSchema,
|
||||||
"users",
|
"users",
|
||||||
"updatedAt"
|
"updatedAt"
|
||||||
>,
|
>,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { AuthData } from "./auth";
|
|||||||
export function isLoggedIn(
|
export function isLoggedIn(
|
||||||
authData: AuthData | null,
|
authData: AuthData | null,
|
||||||
): asserts authData is AuthData {
|
): asserts authData is AuthData {
|
||||||
|
console.log("AUTHDATA", authData);
|
||||||
if (!authData?.user.id) {
|
if (!authData?.user.id) {
|
||||||
throw new Error("User is not logged in");
|
throw new Error("User is not logged in");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
"declaration": true,
|
"declaration": false,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user