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
2 changes: 0 additions & 2 deletions convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import type * as auth from "../auth.js";
import type * as http from "../http.js";
import type * as lib_auth from "../lib/auth.js";
import type * as lib_country from "../lib/country.js";
import type * as lib_hierarchy from "../lib/hierarchy.js";
import type * as lib_sync from "../lib/sync.js";
import type * as migrations from "../migrations.js";
Expand All @@ -29,7 +28,6 @@ declare const fullApi: ApiFromModules<{
auth: typeof auth;
http: typeof http;
"lib/auth": typeof lib_auth;
"lib/country": typeof lib_country;
"lib/hierarchy": typeof lib_hierarchy;
"lib/sync": typeof lib_sync;
migrations: typeof migrations;
Expand Down
11 changes: 0 additions & 11 deletions convex/lib/country.ts

This file was deleted.

22 changes: 0 additions & 22 deletions convex/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,6 @@ const syncedFields = {
export default defineSchema({
...authTables,

// Convex Auth's own `users` table, restated so it can carry one extra field.
// Everything but `country` is copied from `authTables.users`; keep it in step
// with that definition when upgrading @convex-dev/auth.
users: defineTable({
name: v.optional(v.string()),
image: v.optional(v.string()),
email: v.optional(v.string()),
emailVerificationTime: v.optional(v.number()),
phone: v.optional(v.string()),
phoneVerificationTime: v.optional(v.number()),
isAnonymous: v.optional(v.boolean()),
/**
* ISO 3166-1 alpha-2 country the account was first seen from (e.g. "ES"),
* as reported by Cloudflare. Set once, never overwritten, and only the
* country — the IP address is never stored. Absent for accounts that have
* not opened the app since this was introduced.
*/
country: v.optional(v.string()),
})
.index("email", ["email"])
.index("phone", ["phone"]),

folders: defineTable({
...syncedFields,
/** Ciphertext when `cryptoVersion` > 0, plaintext when 0. */
Expand Down
70 changes: 0 additions & 70 deletions convex/users.test.ts

This file was deleted.

42 changes: 1 addition & 41 deletions convex/users.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { v } from "convex/values";

import type { Id } from "./_generated/dataModel";
import { mutation, query } from "./_generated/server";
import { requireUserId } from "./lib/auth";
import { normalizeCountry } from "./lib/country";
import { query } from "./_generated/server";

/**
* The signed-in account, or `null` when there is no session.
Expand Down Expand Up @@ -35,39 +31,3 @@ export const viewer = query({
};
},
});

/**
* Whether the caller's account already has a country on record, or `null` when
* signed out. The client asks this before reporting one, so an account that is
* already known costs no further requests.
*/
export const hasCountry = query({
args: {},
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) return null;

const user = await ctx.db.get(identity.subject.split("|")[0] as Id<"users">);
return user ? user.country !== undefined : null;
},
});

/**
* Records the country an account was first seen from. First write wins: a
* country already on file is never replaced, so travelling or a VPN cannot
* rewrite it, and an unusable value ("XX", "T1", garbage) stores nothing.
*/
export const recordCountry = mutation({
args: { country: v.string() },
handler: async (ctx, args) => {
const userId = await requireUserId(ctx);
const country = normalizeCountry(args.country);
if (!country) return { stored: false };

const user = await ctx.db.get(userId);
if (!user || user.country !== undefined) return { stored: false };

await ctx.db.patch(userId, { country });
return { stored: true };
},
});
67 changes: 0 additions & 67 deletions src/__tests__/account-country-route.test.ts

This file was deleted.

56 changes: 0 additions & 56 deletions src/app/api/account/country/route.ts

This file was deleted.

21 changes: 1 addition & 20 deletions src/components/ConvexClientProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { api } from "@convex/_generated/api";
import { ConvexAuthProvider, useAuthActions, useAuthToken } from "@convex-dev/auth/react";
import { useConvexAuth, useQuery } from "convex/react";
import { useCallback, useEffect, useMemo, useRef } from "react";
import { useCallback, useEffect, useMemo } from "react";

import { CloudSessionContext, type CloudSession } from "@/hooks/useCloudSession";
import { setAuthToken } from "@/lib/authToken";
Expand All @@ -28,25 +28,6 @@ function CloudSessionBridge({ children }: { children: React.ReactNode }) {
setAuthToken(token ?? null);
}, [token]);

// Accounts that predate country tracking, and new ones, report it once: the
// Worker reads it from Cloudflare and Convex keeps the first value. Failures
// are ignored — this is bookkeeping and the next visit simply asks again.
const hasCountry = useQuery(api.users.hasCountry, isAuthenticated ? {} : "skip");
const countryAskedFor = useRef<string | null>(null);
useEffect(() => {
const userId = viewer?.id;
if (!token || !userId || hasCountry !== false || countryAskedFor.current === userId) {
return;
}
countryAskedFor.current = userId;
void fetch("/api/account/country", {
method: "POST",
headers: { authorization: `Bearer ${token}` },
}).catch(() => {
countryAskedFor.current = null;
});
}, [hasCountry, token, viewer?.id]);

const handleSignIn = useCallback(async () => {
await signIn("github", { redirectTo: window.location.href });
}, [signIn]);
Expand Down
Loading