Compare commits

...
60 Commits
Author SHA1 Message Date
dimitar 3f39a3d05a db 2026-04-03 05:10:10 +02:00
dimitar 573238f65e live sync working 2026-04-03 05:09:55 +02:00
dimitar 76fa04d129 error fixed
we must refresh for new msg
2026-04-03 04:41:38 +02:00
dimitar 9cbdc35903 chat implemented
need testing
2026-04-03 03:25:11 +02:00
dimitar c90f8cb1fa geofence refinement
and manual failsafe
2026-04-03 00:13:22 +02:00
dimitar 71ccea85d2 geofence impemented 2026-04-02 22:47:27 +02:00
dimitar e2706118d1 dbs 2026-04-01 20:25:35 +02:00
dimitar 4e322503cc db 2026-03-31 21:54:33 +02:00
dimitar e9685193a4 reduce statistics refetch log noise and dedupe requests 2026-03-31 21:54:04 +02:00
dimitar ad3eba48b0 remove attendance tab and screen from mobile navigation 2026-03-31 20:25:04 +02:00
dimitar 0ccf59344e db 2026-03-31 20:16:49 +02:00
dimitar 42122ac341 Merge branch 'adminRecc' 2026-03-31 20:04:37 +02:00
dimitar 4dd2ed5839 dbg 2026-03-31 20:04:05 +02:00
dimitar f9a588fcd6 db 2026-03-31 20:03:52 +02:00
dimitar 9330f4fd05 regenerate linked active goals when admin approves ai recommendation 2026-03-31 20:02:28 +02:00
dimitar d6683e6e5e dbs 2026-03-31 19:51:44 +02:00
dimitar bac7df33e8 Merge branch 'screen2' 2026-03-31 19:46:00 +02:00
dimitar 178ad3fa90 db 2026-03-31 19:44:55 +02:00
dimitar ef9f39e564 add resilient self-plan generation with provider fallback 2026-03-31 19:43:19 +02:00
dimitar 73218402f6 fix self ai plan generation authorization and error handling 2026-03-31 19:31:45 +02:00
dimitar c877577fba fix ai activity plan conversion and immediate goals refresh 2026-03-31 19:21:00 +02:00
dimitar e119f0923c pause previous ai-linked goals on self plan regeneration 2026-03-31 19:10:17 +02:00
dimitar a65b3cac08 add client self-service ai activity plans on goals screen 2026-03-31 18:51:30 +02:00
dimitar 0825bb3d65 Merge branch 'workout' 2026-03-31 18:19:12 +02:00
dimitar 6740dcb18f android buuild clean uo p 2026-03-31 18:18:36 +02:00
dimitar 12d6c07186 fix step progress header overflow on home card 2026-03-31 18:16:59 +02:00
dimitar 5010a579d6 add daily pedometer steps metric on home screen 2026-03-31 18:00:35 +02:00
dimitar 2cff8eafbd add home workout quick action with attendance check-in/out 2026-03-31 17:38:12 +02:00
dimitar 275248fc35 dbs 2026-03-31 17:18:41 +02:00
dimitar 4c2e97b66d Merge branch 'screen1' 2026-03-31 17:05:43 +02:00
dimitar 21afb085e3 scan food 2026-03-31 16:55:55 +02:00
dimitar ca64a100b6 integrate barcode scan with openfoodfacts and meal type selection 2026-03-31 16:36:18 +02:00
dimitar 3c3dfb6cd6 hydration calories persistance 2026-03-31 16:17:08 +02:00
dimitar 871f33bf5a dfg 2026-03-31 15:57:23 +02:00
dimitar c5dde63355 dbs 2026-03-31 13:48:39 +02:00
dimitar cd13333b52 stabilize membership loading and home motivational message 2026-03-30 20:24:13 +02:00
dimitar a620921202 Merge branch 'uifix' 2026-03-29 20:08:39 +02:00
dimitar ed14c57749 normalize mobile auth and profile ui to theme components 2026-03-29 20:07:33 +02:00
dimitar 7ada05da6a db up 2026-03-29 19:54:17 +02:00
dimitar 50ece15089 add membership features endpoint and use it in mobile 2026-03-29 19:51:36 +02:00
dimitar 091cb5ba85 Merge branch 'planDef' 2026-03-29 16:26:38 +02:00
dimitar ebfd633a11 enforce membership feature access in api and surface plans in admin 2026-03-29 16:22:45 +02:00
dimitar 1f4800c055 membership 2026-03-29 16:05:10 +02:00
dimitar 0ddac10c59 Merge branch 'phase-9-release-hardening' 2026-03-29 15:54:11 +02:00
dimitar 573690ab02 Merge branch 'phase-8-api-contract-tightening' 2026-03-29 15:54:00 +02:00
dimitar efbfa58c10 Merge branch 'phase-7-query-cache-consolidation' 2026-03-29 15:53:47 +02:00
dimitar 7ad1e5133e Merge branch 'phase-6-coverage-expansion' 2026-03-29 15:53:27 +02:00
dimitar 9c3d3f5b72 add release hardening checklist for admin and mobile 2026-03-29 15:49:47 +02:00
dimitar ff9f3d582a standardize mobile api response parsing with shared helper 2026-03-29 15:48:58 +02:00
dimitar 60d7a7963d initialize react-query provider for mobile app 2026-03-29 15:46:59 +02:00
dimitar 0eede3fa91 add mobile notifications api unit tests 2026-03-29 15:46:16 +02:00
dimitar b1f84722af Merge branch 'nextPhase' 2026-03-29 15:41:09 +02:00
dimitar 34e88bdde5 reset mobile context caches on user identity changes 2026-03-29 15:38:51 +02:00
dimitar aa662a9b74 add mobile api unit tests for gyms and recommendations 2026-03-29 15:34:43 +02:00
dimitar 80110acbf7 standardize mobile api transport for gyms and core services 2026-03-29 15:31:06 +02:00
dimitar c36cad9c54 Merge branch 'testRep' 2026-03-29 11:27:14 +02:00
dimitar 2ecb8a3515 android 2026-03-29 11:25:28 +02:00
dimitar 8275da687b fix mobile profile flow and onboarding edge cases 2026-03-29 11:23:49 +02:00
dimitar 10b58245f5 harden admin api authorization and add authz regression tests 2026-03-29 11:23:49 +02:00
dimitar 272c9b36dd todos 2026-03-29 09:52:22 +02:00
290 changed files with 14632 additions and 19969 deletions

No files matched your search

Binary file not shown.
+65 -1
View File
@@ -45,6 +45,7 @@
"react-hook-form": "^7.66.0",
"recharts": "^3.3.0",
"resend": "^6.9.3",
"socket.io-client": "^4.8.1",
"sonner": "^2.0.7",
"sqlite": "^5.1.1",
"sqlite3": "^5.1.7",
@@ -3506,6 +3507,12 @@
"@sinonjs/commons": "^3.0.1"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"license": "MIT"
},
"node_modules/@stablelib/base64": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz",
@@ -6691,6 +6698,28 @@
"once": "^1.4.0"
}
},
"node_modules/engine.io-client": {
"version": "6.6.4",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz",
"integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.18.3",
"xmlhttprequest-ssl": "~2.1.1"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/entities": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
@@ -13241,6 +13270,34 @@
"npm": ">= 3.0.0"
}
},
"node_modules/socket.io-client": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz",
"integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1",
"engine.io-client": "~6.6.1",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz",
"integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socks": {
"version": "2.8.7",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
@@ -15132,7 +15189,6 @@
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
@@ -15167,6 +15223,14 @@
"dev": true,
"license": "MIT"
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+1
View File
@@ -49,6 +49,7 @@
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-hook-form": "^7.66.0",
"socket.io-client": "^4.8.1",
"recharts": "^3.3.0",
"resend": "^6.9.3",
"sonner": "^2.0.7",
@@ -5,6 +5,7 @@ import { ensureUserSynced } from "@/lib/sync-user";
import { successResponse } from "@/lib/api/responses";
import { db as rawDb, sql } from "@fitai/database";
import { getUsersByGym, getClientsByGym } from "@/lib/gym-context";
import log from "@/lib/logger";
interface UserGrowthPoint {
label: string;
@@ -158,7 +159,7 @@ export async function GET(req: NextRequest) {
return successResponse({ analytics: analyticsData });
} catch (error) {
console.error("Analytics error:", error);
log.error("Analytics error", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
@@ -0,0 +1,103 @@
/**
* @jest-environment node
*/
import { NextRequest } from "next/server";
import { POST } from "../route";
jest.mock("@clerk/nextjs/server", () => ({
auth: jest.fn(),
}));
jest.mock("@/lib/database", () => ({
getDatabase: jest.fn(),
}));
jest.mock("@/lib/sync-user", () => ({
ensureUserSynced: jest.fn(),
}));
jest.mock("@/lib/clerk-helpers", () => ({
setUserRole: jest.fn(),
}));
describe("POST /api/admin/set-role", () => {
const mockAuth = require("@clerk/nextjs/server").auth as jest.Mock;
const mockGetDatabase = require("@/lib/database").getDatabase as jest.Mock;
const mockEnsureUserSynced = require("@/lib/sync-user")
.ensureUserSynced as jest.Mock;
const mockSetUserRole = require("@/lib/clerk-helpers")
.setUserRole as jest.Mock;
const mockDb = {
getUserById: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
mockGetDatabase.mockResolvedValue(mockDb);
});
it("returns 403 when admin tries to assign role across gyms", async () => {
mockAuth.mockResolvedValue({ userId: "admin_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "admin_1",
role: "admin",
gymId: "gym_a",
});
mockDb.getUserById.mockResolvedValue({
id: "user_2",
role: "client",
gymId: "gym_b",
});
const request = new NextRequest("http://localhost/api/admin/set-role", {
method: "POST",
body: JSON.stringify({
targetUserId: "user_2",
role: "trainer",
}),
});
const response = await POST(request);
expect(response.status).toBe(403);
expect(mockSetUserRole).not.toHaveBeenCalled();
});
it("allows superAdmin to assign roles across gyms", async () => {
mockAuth.mockResolvedValue({ userId: "super_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "super_1",
role: "superAdmin",
gymId: null,
});
mockDb.getUserById.mockResolvedValue({
id: "user_2",
role: "client",
gymId: "gym_b",
});
mockSetUserRole.mockResolvedValue({
id: "user_2",
emailAddresses: [{ emailAddress: "user2@example.com" }],
firstName: "User",
lastName: "Two",
publicMetadata: { role: "admin" },
});
const request = new NextRequest("http://localhost/api/admin/set-role", {
method: "POST",
body: JSON.stringify({
targetUserId: "user_2",
role: "admin",
}),
});
const response = await POST(request);
const payload = await response.json();
expect(response.status).toBe(200);
expect(payload.success).toBe(true);
expect(mockSetUserRole).toHaveBeenCalledWith("user_2", "admin");
});
});
+66 -20
View File
@@ -1,6 +1,9 @@
import { auth } from '@clerk/nextjs/server';
import { NextResponse } from 'next/server';
import { setUserRole, isAdmin, type UserRole } from '@/lib/clerk-helpers';
import { auth } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";
import { USER_ROLES, type UserRole } from "@fitai/shared";
import { setUserRole } from "@/lib/clerk-helpers";
import { getDatabase } from "@/lib/database";
import { ensureUserSynced } from "@/lib/sync-user";
export async function POST(req: Request) {
try {
@@ -8,16 +11,27 @@ export async function POST(req: Request) {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const db = await getDatabase();
const currentUser = await ensureUserSynced(userId, db);
if (!currentUser) {
return NextResponse.json(
{ error: "Forbidden: user not found" },
{ status: 403 },
);
}
// Check if the requesting user is an admin
const requestingUserIsAdmin = await isAdmin(userId);
const requestingUserIsAdmin =
currentUser.role === "admin" || currentUser.role === "superAdmin";
if (!requestingUserIsAdmin) {
return NextResponse.json(
{ error: 'Forbidden: Admin access required' },
{ status: 403 }
{ error: "Forbidden: Admin access required" },
{ status: 403 },
);
}
@@ -26,25 +40,57 @@ export async function POST(req: Request) {
const { targetUserId, role } = body;
// Validate inputs
if (!targetUserId || typeof targetUserId !== 'string') {
if (!targetUserId || typeof targetUserId !== "string") {
return NextResponse.json(
{ error: 'Invalid or missing targetUserId' },
{ status: 400 }
{ error: "Invalid or missing targetUserId" },
{ status: 400 },
);
}
if (!role || !['admin', 'trainer', 'client'].includes(role)) {
if (!role || !USER_ROLES.includes(role as UserRole)) {
return NextResponse.json(
{ error: 'Invalid role. Must be admin, trainer, or client' },
{ status: 400 }
{
error: `Invalid role. Must be one of: ${USER_ROLES.join(", ")}`,
},
{ status: 400 },
);
}
const allowedRolesByRequester: Record<UserRole, UserRole[]> = {
superAdmin: ["superAdmin", "admin", "trainer", "client"],
admin: ["admin", "trainer", "client"],
trainer: [],
client: [],
};
const allowedTargetRoles = allowedRolesByRequester[currentUser.role];
if (!allowedTargetRoles.includes(role as UserRole)) {
return NextResponse.json(
{ error: `Forbidden: cannot assign role '${role}'` },
{ status: 403 },
);
}
// Prevent admin from changing their own role
if (userId === targetUserId) {
return NextResponse.json(
{ error: 'Cannot change your own role' },
{ status: 400 }
{ error: "Cannot change your own role" },
{ status: 400 },
);
}
const targetUser = await db.getUserById(targetUserId);
if (!targetUser) {
return NextResponse.json({ error: "User not found" }, { status: 404 });
}
if (
currentUser.role !== "superAdmin" &&
(!currentUser.gymId || targetUser.gymId !== currentUser.gymId)
) {
return NextResponse.json(
{ error: "Cannot change roles for users from other gyms" },
{ status: 403 },
);
}
@@ -63,15 +109,15 @@ export async function POST(req: Request) {
},
});
} catch (error) {
console.error('Error setting user role:', error);
console.error("Error setting user role:", error);
if (error instanceof Error && error.message.includes('not found')) {
return NextResponse.json({ error: 'User not found' }, { status: 404 });
if (error instanceof Error && error.message.includes("not found")) {
return NextResponse.json({ error: "User not found" }, { status: 404 });
}
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
{ error: "Internal server error" },
{ status: 500 },
);
}
}
+14 -5
View File
@@ -3,20 +3,26 @@ import { NextResponse } from "next/server";
import { getDatabase } from "@/lib/database";
import { ensureUserSynced } from "@/lib/sync-user";
import { successResponse } from "@/lib/api/responses";
import log from "@/lib/logger";
export async function GET(req: Request) {
try {
const { userId } = await auth();
if (!userId) return new NextResponse("Unauthorized", { status: 401 });
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const db = await getDatabase();
const user = await ensureUserSynced(userId, db);
if (!user || (user.role !== "admin" && user.role !== "superAdmin")) {
return new NextResponse("Forbidden", { status: 403 });
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
if (user.role === "admin" && !user.gymId) {
return new NextResponse("Admin gymId not set", { status: 400 });
return NextResponse.json(
{ error: "Admin gymId not set" },
{ status: 400 },
);
}
const url = new URL(req.url);
@@ -54,7 +60,10 @@ export async function GET(req: Request) {
return successResponse({ stats });
} catch (error) {
console.error("Dashboard stats error:", error);
return new NextResponse("Internal Server Error", { status: 500 });
log.error("Dashboard stats error", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
@@ -1,137 +1,180 @@
/**
* @jest-environment node
*/
import { POST as checkIn } from '../check-in/route'
import { POST as checkOut } from '../check-out/route'
import { GET as history } from '../history/route'
import { NextRequest } from 'next/server'
import { POST as checkIn } from "../check-in/route";
import { POST as checkOut } from "../check-out/route";
import { GET as history } from "../history/route";
import { NextRequest } from "next/server";
// Mock dependencies
jest.mock('@clerk/nextjs/server', () => ({
auth: jest.fn(() => Promise.resolve({ userId: 'test_user_id' })),
currentUser: jest.fn(() => Promise.resolve({ id: 'test_user_id', emailAddresses: [{ emailAddress: 'test@example.com' }] }))
}))
jest.mock("@clerk/nextjs/server", () => ({
auth: jest.fn(() => Promise.resolve({ userId: "test_user_id" })),
currentUser: jest.fn(() =>
Promise.resolve({
id: "test_user_id",
emailAddresses: [{ emailAddress: "test@example.com" }],
}),
),
}));
jest.mock('@/lib/sync-user', () => ({
ensureUserSynced: jest.fn()
}))
jest.mock("@/lib/sync-user", () => ({
ensureUserSynced: jest.fn(),
}));
jest.mock("@/lib/geofence", () => ({
getUserGymGeofence: jest.fn(() =>
Promise.resolve({
id: "gym_1",
name: "Test Gym",
latitude: 1,
longitude: 1,
geofenceRadiusMeters: 30,
geofenceEnabled: true,
}),
),
parseUserLocation: jest.fn(() => ({
latitude: 1,
longitude: 1,
accuracy: 10,
})),
validateGeofence: jest.fn(() => ({ ok: true })),
validateGeofenceWithFallback: jest.fn(() => ({ ok: true })),
validateCheckInGeofence: jest.fn(() => ({ ok: true })),
}));
const mockDb = {
checkIn: jest.fn(),
checkOut: jest.fn(),
getAttendanceHistory: jest.fn(),
getActiveCheckIn: jest.fn(),
getUserById: jest.fn(),
createUser: jest.fn(),
getClientByUserId: jest.fn(),
createClient: jest.fn(),
getFitnessProfileByUserId: jest.fn(),
createFitnessProfile: jest.fn(),
}
checkIn: jest.fn(),
checkOut: jest.fn(),
getAttendanceHistory: jest.fn(),
getActiveCheckIn: jest.fn(),
getUserById: jest.fn(),
createUser: jest.fn(),
getClientByUserId: jest.fn(),
createClient: jest.fn(),
getFitnessProfileByUserId: jest.fn(),
createFitnessProfile: jest.fn(),
};
jest.mock('@/lib/database', () => ({
getDatabase: jest.fn(() => Promise.resolve(mockDb))
}))
jest.mock("@/lib/database", () => ({
getDatabase: jest.fn(() => Promise.resolve(mockDb)),
}));
describe('Attendance API', () => {
beforeEach(() => {
jest.clearAllMocks()
})
describe("Attendance API", () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('POST /api/attendance/check-in', () => {
it('should successfully check in', async () => {
mockDb.getUserById.mockResolvedValue({ id: 'test_user_id' })
mockDb.getActiveCheckIn.mockResolvedValue(null)
mockDb.checkIn.mockResolvedValue({
id: 'attendance_id',
userId: 'test_user_id',
checkInTime: new Date(),
type: 'gym'
})
describe("POST /api/attendance/check-in", () => {
it("should successfully check in", async () => {
mockDb.getUserById.mockResolvedValue({ id: "test_user_id" });
mockDb.getActiveCheckIn.mockResolvedValue(null);
mockDb.checkIn.mockResolvedValue({
id: "attendance_id",
userId: "test_user_id",
checkInTime: new Date(),
type: "gym",
});
const req = new NextRequest('http://localhost/api/attendance/check-in', {
method: 'POST',
body: JSON.stringify({ type: 'gym', notes: 'Test check-in' })
})
const req = new NextRequest("http://localhost/api/attendance/check-in", {
method: "POST",
body: JSON.stringify({
type: "gym",
notes: "Test check-in",
location: { latitude: 1, longitude: 1, accuracy: 10 },
}),
});
const res = await checkIn(req)
const data = await res.json()
const res = await checkIn(req);
const data = await res.json();
expect(res.status).toBe(200)
expect(data.id).toBe('attendance_id')
expect(data.userId).toBe('test_user_id')
expect(mockDb.checkIn).toHaveBeenCalledWith('test_user_id', 'gym', 'Test check-in')
})
expect(res.status).toBe(200);
expect(data.id).toBe("attendance_id");
expect(data.userId).toBe("test_user_id");
expect(mockDb.checkIn).toHaveBeenCalledWith(
"test_user_id",
"gym",
"Test check-in",
);
});
it('should fail if already checked in', async () => {
mockDb.getUserById.mockResolvedValue({ id: 'test_user_id' })
mockDb.getActiveCheckIn.mockResolvedValue({ id: 'existing_id' })
it("should fail if already checked in", async () => {
mockDb.getUserById.mockResolvedValue({ id: "test_user_id" });
mockDb.getActiveCheckIn.mockResolvedValue({ id: "existing_id" });
const req = new NextRequest('http://localhost/api/attendance/check-in', {
method: 'POST',
body: JSON.stringify({ type: 'gym' })
})
const req = new NextRequest("http://localhost/api/attendance/check-in", {
method: "POST",
body: JSON.stringify({
type: "gym",
location: { latitude: 1, longitude: 1, accuracy: 10 },
}),
});
const res = await checkIn(req)
const text = await res.text()
const res = await checkIn(req);
const text = await res.text();
expect(res.status).toBe(400)
expect(text).toBe('Already checked in')
})
})
expect(res.status).toBe(400);
expect(text).toBe("Already checked in");
});
});
describe('POST /api/attendance/check-out', () => {
it('should successfully check out', async () => {
mockDb.getActiveCheckIn.mockResolvedValue({ id: 'attendance_id' })
mockDb.checkOut.mockResolvedValue({
id: 'attendance_id',
checkOutTime: new Date()
})
describe("POST /api/attendance/check-out", () => {
it("should successfully check out", async () => {
mockDb.getActiveCheckIn.mockResolvedValue({ id: "attendance_id" });
mockDb.checkOut.mockResolvedValue({
id: "attendance_id",
checkOutTime: new Date(),
});
const req = new NextRequest('http://localhost/api/attendance/check-out', {
method: 'POST'
})
const req = new NextRequest("http://localhost/api/attendance/check-out", {
method: "POST",
body: JSON.stringify({
location: { latitude: 1, longitude: 1, accuracy: 10 },
}),
});
const res = await checkOut(req)
const data = await res.json()
const res = await checkOut(req);
const data = await res.json();
expect(res.status).toBe(200)
expect(data.id).toBe('attendance_id')
expect(data.checkOutTime).toBeDefined()
expect(mockDb.checkOut).toHaveBeenCalledWith('attendance_id')
})
expect(res.status).toBe(200);
expect(data.id).toBe("attendance_id");
expect(data.checkOutTime).toBeDefined();
expect(mockDb.checkOut).toHaveBeenCalledWith("attendance_id");
});
it('should fail if not checked in', async () => {
mockDb.getActiveCheckIn.mockResolvedValue(null)
it("should fail if not checked in", async () => {
mockDb.getActiveCheckIn.mockResolvedValue(null);
const req = new NextRequest('http://localhost/api/attendance/check-out', {
method: 'POST'
})
const req = new NextRequest("http://localhost/api/attendance/check-out", {
method: "POST",
body: JSON.stringify({
location: { latitude: 1, longitude: 1, accuracy: 10 },
}),
});
const res = await checkOut(req)
const text = await res.text()
const res = await checkOut(req);
const text = await res.text();
expect(res.status).toBe(404)
expect(text).toBe('No active check-in found')
})
})
expect(res.status).toBe(404);
expect(text).toBe("No active check-in found");
});
});
describe('GET /api/attendance/history', () => {
it('should return attendance history', async () => {
const historyData = [
{ id: '1', checkInTime: new Date() },
{ id: '2', checkInTime: new Date() }
]
mockDb.getUserById.mockResolvedValue({ id: 'test_user_id' })
mockDb.getAttendanceHistory.mockResolvedValue(historyData)
describe("GET /api/attendance/history", () => {
it("should return attendance history", async () => {
const historyData = [
{ id: "1", checkInTime: new Date() },
{ id: "2", checkInTime: new Date() },
];
mockDb.getUserById.mockResolvedValue({ id: "test_user_id" });
mockDb.getAttendanceHistory.mockResolvedValue(historyData);
const req = new NextRequest('http://localhost/api/attendance/history')
const res = await history(req)
const data = await res.json()
const req = new NextRequest("http://localhost/api/attendance/history");
const res = await history(req);
const data = await res.json();
expect(res.status).toBe(200)
expect(data).toEqual(JSON.parse(JSON.stringify(historyData))) // Handle date serialization
expect(mockDb.getAttendanceHistory).toHaveBeenCalledWith('test_user_id')
})
})
})
expect(res.status).toBe(200);
expect(data).toEqual(JSON.parse(JSON.stringify(historyData))); // Handle date serialization
expect(mockDb.getAttendanceHistory).toHaveBeenCalledWith("test_user_id");
});
});
});
@@ -2,12 +2,12 @@ import { auth } from "@clerk/nextjs/server";
import { NextRequest, NextResponse } from "next/server";
import { getDatabase } from "@/lib/database";
import { ensureUserSynced } from "@/lib/sync-user";
import log from "@/lib/logger";
import { checkInSchema } from "@/lib/validation/schemas";
import {
validateRequestBody,
validationErrorResponse,
} from "@/lib/validation/helpers";
getUserGymGeofence,
parseUserLocation,
validateCheckInGeofence,
} from "@/lib/geofence";
import log from "@/lib/logger";
export async function POST(req: NextRequest) {
try {
@@ -25,8 +25,26 @@ export async function POST(req: NextRequest) {
return new NextResponse("Already checked in", { status: 400 });
}
const body = await req.json();
const body = await req.json().catch(() => ({}));
const { type = "gym", notes } = body;
const fallbackRequested = Boolean(body.fallbackRequested);
const gym = await getUserGymGeofence(userId);
if (!gym) {
return NextResponse.json(
{ error: "No gym assigned for this user" },
{ status: 400 },
);
}
const location = parseUserLocation(body.location);
const geofence = validateCheckInGeofence(gym, location, fallbackRequested);
if (!geofence.ok) {
return NextResponse.json(
{ error: geofence.error },
{ status: geofence.status },
);
}
const attendance = await db.checkIn(userId, type, notes);
return NextResponse.json(attendance);
@@ -1,6 +1,11 @@
import { auth } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";
import { getDatabase } from "@/lib/database";
import {
getUserGymGeofence,
parseUserLocation,
validateGeofenceWithFallback,
} from "@/lib/geofence";
import log from "@/lib/logger";
export async function POST(req: Request) {
@@ -15,6 +20,30 @@ export async function POST(req: Request) {
return new NextResponse("No active check-in found", { status: 404 });
}
const body = await req.json().catch(() => ({}));
const fallbackRequested = Boolean(body.fallbackRequested);
const gym = await getUserGymGeofence(userId);
if (!gym) {
return NextResponse.json(
{ error: "No gym assigned for this user" },
{ status: 400 },
);
}
const location = parseUserLocation(body.location);
const geofence = validateGeofenceWithFallback(
gym,
location,
fallbackRequested,
);
if (!geofence.ok) {
return NextResponse.json(
{ error: geofence.error },
{ status: geofence.status },
);
}
const attendance = await db.checkOut(activeCheckIn.id);
return NextResponse.json(attendance);
} catch (error) {
+27 -1
View File
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import bcrypt from "bcryptjs";
import { auth } from "@clerk/nextjs/server";
import { getDatabase } from "../../../../lib/database/index";
import log from "@/lib/logger";
import { userSchema } from "@/lib/validation/schemas";
@@ -7,6 +8,8 @@ import {
validateRequestBody,
validationErrorResponse,
} from "@/lib/validation/helpers";
import { ensureUserSynced } from "@/lib/sync-user";
import { getUsersByGym } from "@/lib/gym-context";
export async function POST(request: NextRequest) {
try {
@@ -68,8 +71,31 @@ export async function POST(request: NextRequest) {
export async function GET() {
try {
const { userId: clerkUserId } = await auth();
if (!clerkUserId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const db = await getDatabase();
const allUsers = await db.getAllUsers();
const currentUser = await ensureUserSynced(clerkUserId, db);
if (!currentUser) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const canViewUsers =
currentUser.role === "superAdmin" || currentUser.role === "admin";
if (!canViewUsers) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const allUsers =
currentUser.role === "superAdmin"
? await db.getAllUsers()
: currentUser.gymId
? await getUsersByGym(currentUser.gymId)
: [];
const usersWithoutPassword = allUsers.map(
({ password: _, ...user }) => user,
);
@@ -0,0 +1,108 @@
/**
* @jest-environment node
*/
import { NextRequest } from "next/server";
import { GET as GET_THREADS } from "../threads/route";
import { POST as START_DM } from "../dm/start/route";
import { GET as GET_MY_DM } from "../dm/my-thread/route";
jest.mock("@clerk/nextjs/server", () => ({
auth: jest.fn(),
}));
jest.mock("@/lib/database", () => ({
getDatabase: jest.fn(),
}));
jest.mock("@/lib/sync-user", () => ({
ensureUserSynced: jest.fn(),
}));
jest.mock("@/lib/chat", () => ({
getOrCreateGymRoomThread: jest.fn(),
listThreadsForUser: jest.fn(),
getOrCreateDmThread: jest.fn(),
isChatError: jest.fn(() => false),
}));
jest.mock("@/lib/logger", () => ({
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
debug: jest.fn(),
}));
describe("/api/chat authz", () => {
const mockAuth = require("@clerk/nextjs/server").auth as jest.Mock;
const mockGetDatabase = require("@/lib/database").getDatabase as jest.Mock;
const mockEnsureUserSynced = require("@/lib/sync-user")
.ensureUserSynced as jest.Mock;
const mockListThreadsForUser = require("@/lib/chat")
.listThreadsForUser as jest.Mock;
const mockGetOrCreateDmThread = require("@/lib/chat")
.getOrCreateDmThread as jest.Mock;
const mockDb = {
getClientTrainerAssignment: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
mockGetDatabase.mockResolvedValue(mockDb);
mockListThreadsForUser.mockResolvedValue([]);
});
it("returns 401 for unauthenticated threads list", async () => {
mockAuth.mockResolvedValue({ userId: null });
const req = new NextRequest("http://localhost/api/chat/threads");
const res = await GET_THREADS(req);
expect(res.status).toBe(401);
});
it("returns 403 when non participant/non staff starts DM", async () => {
mockAuth.mockResolvedValue({ userId: "user_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "user_1",
role: "trainer",
gymId: "gym_1",
});
const req = new NextRequest("http://localhost/api/chat/dm/start", {
method: "POST",
body: JSON.stringify({ trainerId: "trainer_2", clientId: "client_2" }),
});
const res = await START_DM(req);
expect(res.status).toBe(403);
expect(mockGetOrCreateDmThread).not.toHaveBeenCalled();
});
it("returns 403 when non-client requests my-thread endpoint", async () => {
mockAuth.mockResolvedValue({ userId: "trainer_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "trainer_1",
role: "trainer",
gymId: "gym_1",
});
const res = await GET_MY_DM();
expect(res.status).toBe(403);
});
it("returns 404 when client has no active assignment", async () => {
mockAuth.mockResolvedValue({ userId: "client_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "client_1",
role: "client",
gymId: "gym_1",
});
mockDb.getClientTrainerAssignment.mockResolvedValue(null);
const res = await GET_MY_DM();
expect(res.status).toBe(404);
});
});
@@ -0,0 +1,61 @@
import { NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { ensureUserSynced } from "@/lib/sync-user";
import { getDatabase } from "@/lib/database";
import { getOrCreateDmThread, isChatError } from "@/lib/chat";
import log from "@/lib/logger";
export async function GET() {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const db = await getDatabase();
const currentUser = await ensureUserSynced(userId, db);
if (!currentUser) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
if (currentUser.role !== "client") {
return NextResponse.json(
{ error: "Only clients can use this endpoint" },
{ status: 403 },
);
}
const assignment = await db.getClientTrainerAssignment(currentUser.id);
if (!assignment || !assignment.isActive) {
return NextResponse.json(
{ error: "No active trainer assignment found" },
{ status: 404 },
);
}
const thread = await getOrCreateDmThread({
trainerId: assignment.trainerId,
clientId: assignment.clientId,
createdBy: {
id: currentUser.id,
role: currentUser.role,
gymId: currentUser.gymId,
},
});
return NextResponse.json({ thread });
} catch (error) {
if (isChatError(error)) {
return NextResponse.json(
{ error: error.message },
{ status: error.status },
);
}
log.error("Failed to get client DM thread", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
@@ -0,0 +1,70 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { ensureUserSynced } from "@/lib/sync-user";
import { getDatabase } from "@/lib/database";
import { getOrCreateDmThread, isChatError } from "@/lib/chat";
import log from "@/lib/logger";
export async function POST(request: NextRequest) {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await request.json().catch(() => null);
if (!body || typeof body !== "object") {
return NextResponse.json({ error: "Invalid body" }, { status: 400 });
}
const trainerId = String(body.trainerId ?? "").trim();
const clientId = String(body.clientId ?? "").trim();
if (!trainerId || !clientId) {
return NextResponse.json(
{ error: "trainerId and clientId are required" },
{ status: 400 },
);
}
const db = await getDatabase();
const currentUser = await ensureUserSynced(userId, db);
if (!currentUser) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const isStaff =
currentUser.role === "superAdmin" || currentUser.role === "admin";
const isParticipant =
currentUser.id === trainerId || currentUser.id === clientId;
if (!isStaff && !isParticipant) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const thread = await getOrCreateDmThread({
trainerId,
clientId,
createdBy: {
id: currentUser.id,
role: currentUser.role,
gymId: currentUser.gymId,
},
});
return NextResponse.json({ thread }, { status: 201 });
} catch (error) {
if (isChatError(error)) {
return NextResponse.json(
{ error: error.message },
{ status: error.status },
);
}
log.error("Failed to start direct message thread", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
@@ -0,0 +1,42 @@
import { NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { ensureUserSynced } from "@/lib/sync-user";
import { getDatabase } from "@/lib/database";
import { getOrCreateGymRoomThread, isChatError } from "@/lib/chat";
import log from "@/lib/logger";
export async function GET() {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const db = await getDatabase();
const currentUser = await ensureUserSynced(userId, db);
if (!currentUser) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const thread = await getOrCreateGymRoomThread({
id: currentUser.id,
role: currentUser.role,
gymId: currentUser.gymId,
});
return NextResponse.json({ thread });
} catch (error) {
if (isChatError(error)) {
return NextResponse.json(
{ error: error.message },
{ status: error.status },
);
}
log.error("Failed to get gym room thread", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
@@ -0,0 +1,181 @@
/**
* @jest-environment node
*/
import { NextRequest } from "next/server";
import { GET, POST } from "../route";
jest.mock("@clerk/nextjs/server", () => ({
auth: jest.fn(),
}));
jest.mock("@/lib/database", () => ({
getDatabase: jest.fn(),
}));
jest.mock("@/lib/sync-user", () => ({
ensureUserSynced: jest.fn(),
}));
jest.mock("@/lib/chat", () => ({
getThreadMessages: jest.fn(),
createThreadMessage: jest.fn(),
isChatError: jest.fn((error: unknown) =>
Boolean(
error && typeof error === "object" && "status" in (error as object),
),
),
}));
jest.mock("@/lib/logger", () => ({
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
debug: jest.fn(),
}));
describe("/api/chat/threads/[id]/messages authz", () => {
const mockAuth = require("@clerk/nextjs/server").auth as jest.Mock;
const mockGetDatabase = require("@/lib/database").getDatabase as jest.Mock;
const mockEnsureUserSynced = require("@/lib/sync-user")
.ensureUserSynced as jest.Mock;
const mockGetThreadMessages = require("@/lib/chat")
.getThreadMessages as jest.Mock;
const mockCreateThreadMessage = require("@/lib/chat")
.createThreadMessage as jest.Mock;
beforeEach(() => {
jest.clearAllMocks();
mockGetDatabase.mockResolvedValue({});
});
it("returns 401 for unauthenticated GET", async () => {
mockAuth.mockResolvedValue({ userId: null });
const request = new NextRequest(
"http://localhost/api/chat/threads/thread_1/messages",
);
const response = await GET(request, {
params: Promise.resolve({ id: "thread_1" }),
});
expect(response.status).toBe(401);
});
it("returns 403 when synced user is missing on GET", async () => {
mockAuth.mockResolvedValue({ userId: "user_1" });
mockEnsureUserSynced.mockResolvedValue(null);
const request = new NextRequest(
"http://localhost/api/chat/threads/thread_1/messages",
);
const response = await GET(request, {
params: Promise.resolve({ id: "thread_1" }),
});
expect(response.status).toBe(403);
expect(mockGetThreadMessages).not.toHaveBeenCalled();
});
it("maps ChatError to status code on GET", async () => {
mockAuth.mockResolvedValue({ userId: "user_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "user_1",
role: "client",
gymId: "gym_1",
});
mockGetThreadMessages.mockRejectedValue({
status: 403,
message: "Forbidden",
});
const request = new NextRequest(
"http://localhost/api/chat/threads/thread_1/messages",
);
const response = await GET(request, {
params: Promise.resolve({ id: "thread_1" }),
});
expect(response.status).toBe(403);
});
it("returns 401 for unauthenticated POST", async () => {
mockAuth.mockResolvedValue({ userId: null });
const request = new NextRequest(
"http://localhost/api/chat/threads/thread_1/messages",
{
method: "POST",
body: JSON.stringify({ body: "Hello" }),
},
);
const response = await POST(request, {
params: Promise.resolve({ id: "thread_1" }),
});
expect(response.status).toBe(401);
});
it("returns 400 when POST body is invalid", async () => {
mockAuth.mockResolvedValue({ userId: "user_1" });
const request = new NextRequest(
"http://localhost/api/chat/threads/thread_1/messages",
{
method: "POST",
body: "not json",
headers: { "Content-Type": "application/json" },
},
);
const response = await POST(request, {
params: Promise.resolve({ id: "thread_1" }),
});
expect(response.status).toBe(400);
});
it("returns 403 when synced user is missing on POST", async () => {
mockAuth.mockResolvedValue({ userId: "user_1" });
mockEnsureUserSynced.mockResolvedValue(null);
const request = new NextRequest(
"http://localhost/api/chat/threads/thread_1/messages",
{
method: "POST",
body: JSON.stringify({ body: "Hi" }),
},
);
const response = await POST(request, {
params: Promise.resolve({ id: "thread_1" }),
});
expect(response.status).toBe(403);
expect(mockCreateThreadMessage).not.toHaveBeenCalled();
});
it("maps ChatError to status code on POST", async () => {
mockAuth.mockResolvedValue({ userId: "user_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "user_1",
role: "client",
gymId: "gym_1",
});
mockCreateThreadMessage.mockRejectedValue({
status: 403,
message: "Forbidden",
});
const request = new NextRequest(
"http://localhost/api/chat/threads/thread_1/messages",
{
method: "POST",
body: JSON.stringify({ body: "Hello" }),
},
);
const response = await POST(request, {
params: Promise.resolve({ id: "thread_1" }),
});
expect(response.status).toBe(403);
});
});
@@ -0,0 +1,133 @@
/**
* @jest-environment node
*/
import { NextRequest } from "next/server";
import { GET, POST } from "../route";
jest.mock("@clerk/nextjs/server", () => ({
auth: jest.fn(),
}));
jest.mock("@/lib/database", () => ({
getDatabase: jest.fn(),
}));
jest.mock("@/lib/sync-user", () => ({
ensureUserSynced: jest.fn(),
}));
jest.mock("@/lib/chat", () => ({
getThreadMessages: jest.fn(),
createThreadMessage: jest.fn(),
isChatError: jest.fn(() => false),
}));
jest.mock("@/lib/logger", () => ({
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
debug: jest.fn(),
}));
describe("/api/chat/threads/[id]/messages params", () => {
const mockAuth = require("@clerk/nextjs/server").auth as jest.Mock;
const mockGetDatabase = require("@/lib/database").getDatabase as jest.Mock;
const mockEnsureUserSynced = require("@/lib/sync-user")
.ensureUserSynced as jest.Mock;
const mockGetThreadMessages = require("@/lib/chat")
.getThreadMessages as jest.Mock;
const mockCreateThreadMessage = require("@/lib/chat")
.createThreadMessage as jest.Mock;
beforeEach(() => {
jest.clearAllMocks();
mockAuth.mockResolvedValue({ userId: "user_1" });
mockGetDatabase.mockResolvedValue({});
mockEnsureUserSynced.mockResolvedValue({
id: "user_1",
role: "client",
gymId: "gym_1",
});
});
it("parses numeric cursor and limit", async () => {
mockGetThreadMessages.mockResolvedValue({ messages: [], nextCursor: null });
const request = new NextRequest(
"http://localhost/api/chat/threads/thread_1/messages?cursor=12345&limit=20",
);
const response = await GET(request, {
params: Promise.resolve({ id: "thread_1" }),
});
expect(response.status).toBe(200);
expect(mockGetThreadMessages).toHaveBeenCalledWith(
expect.objectContaining({
threadId: "thread_1",
cursor: 12345,
limit: 20,
}),
);
});
it("ignores invalid cursor and limit query values", async () => {
mockGetThreadMessages.mockResolvedValue({ messages: [], nextCursor: null });
const request = new NextRequest(
"http://localhost/api/chat/threads/thread_1/messages?cursor=abc&limit=xyz",
);
const response = await GET(request, {
params: Promise.resolve({ id: "thread_1" }),
});
expect(response.status).toBe(200);
expect(mockGetThreadMessages).toHaveBeenCalledWith(
expect.objectContaining({
threadId: "thread_1",
cursor: undefined,
limit: undefined,
}),
);
});
it("filters malformed attachments before create", async () => {
mockCreateThreadMessage.mockResolvedValue({ id: "msg_1" });
const request = new NextRequest(
"http://localhost/api/chat/threads/thread_1/messages",
{
method: "POST",
body: JSON.stringify({
body: "Hello",
attachments: [
{ url: "https://example.com/file.png", type: "image/png" },
{ url: 123, type: "image/png" },
{ foo: "bar" },
],
}),
},
);
const response = await POST(request, {
params: Promise.resolve({ id: "thread_1" }),
});
expect(response.status).toBe(201);
expect(mockCreateThreadMessage).toHaveBeenCalledWith(
expect.objectContaining({
threadId: "thread_1",
body: "Hello",
attachments: [
{
url: "https://example.com/file.png",
type: "image/png",
name: undefined,
},
],
}),
);
});
});
@@ -0,0 +1,148 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { ensureUserSynced } from "@/lib/sync-user";
import { getDatabase } from "@/lib/database";
import {
createThreadMessage,
getThreadMessages,
isChatError,
} from "@/lib/chat";
import log from "@/lib/logger";
interface Params {
params: Promise<{ id: string }>;
}
export async function GET(request: NextRequest, { params }: Params) {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id: threadId } = await params;
const db = await getDatabase();
const currentUser = await ensureUserSynced(userId, db);
if (!currentUser) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const cursorParam = request.nextUrl.searchParams.get("cursor");
const limitParam = request.nextUrl.searchParams.get("limit");
const cursor =
cursorParam && Number.isFinite(Number(cursorParam))
? Number(cursorParam)
: undefined;
const limit =
limitParam && Number.isFinite(Number(limitParam))
? Number(limitParam)
: undefined;
const result = await getThreadMessages({
currentUser: {
id: currentUser.id,
role: currentUser.role,
gymId: currentUser.gymId,
},
threadId,
cursor,
limit,
});
return NextResponse.json(result);
} catch (error) {
if (isChatError(error)) {
return NextResponse.json(
{ error: error.message },
{ status: error.status },
);
}
log.error("Failed to fetch chat messages", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
export async function POST(request: NextRequest, { params }: Params) {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id: threadId } = await params;
const body = await request.json().catch(() => null);
if (!body || typeof body !== "object") {
return NextResponse.json({ error: "Invalid body" }, { status: 400 });
}
const messageBody = String(body.body ?? "").trim();
if (!messageBody) {
return NextResponse.json(
{ error: "Message body is required" },
{ status: 400 },
);
}
const clientMessageIdRaw = body.clientMessageId;
const clientMessageId =
typeof clientMessageIdRaw === "string" &&
clientMessageIdRaw.trim().length > 0
? clientMessageIdRaw.trim()
: undefined;
const attachmentsRaw = body.attachments;
const attachments = Array.isArray(attachmentsRaw)
? attachmentsRaw
.filter(
(item): item is { url: string; type: string; name?: string } =>
!!item &&
typeof item === "object" &&
typeof item.url === "string" &&
typeof item.type === "string",
)
.map((item) => ({
url: item.url,
type: item.type,
name: typeof item.name === "string" ? item.name : undefined,
}))
: undefined;
const db = await getDatabase();
const currentUser = await ensureUserSynced(userId, db);
if (!currentUser) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const message = await createThreadMessage({
currentUser: {
id: currentUser.id,
role: currentUser.role,
gymId: currentUser.gymId,
},
threadId,
body: messageBody,
clientMessageId,
attachments,
});
return NextResponse.json({ message }, { status: 201 });
} catch (error) {
if (isChatError(error)) {
return NextResponse.json(
{ error: error.message },
{ status: error.status },
);
}
log.error("Failed to create chat message", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
@@ -0,0 +1,134 @@
/**
* @jest-environment node
*/
import { NextRequest } from "next/server";
import { POST } from "../route";
jest.mock("@clerk/nextjs/server", () => ({
auth: jest.fn(),
}));
jest.mock("@/lib/database", () => ({
getDatabase: jest.fn(),
}));
jest.mock("@/lib/sync-user", () => ({
ensureUserSynced: jest.fn(),
}));
jest.mock("@/lib/chat", () => ({
markThreadAsRead: jest.fn(),
isChatError: jest.fn((error: unknown) =>
Boolean(
error && typeof error === "object" && "status" in (error as object),
),
),
}));
jest.mock("@/lib/logger", () => ({
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
debug: jest.fn(),
}));
describe("/api/chat/threads/[id]/read authz", () => {
const mockAuth = require("@clerk/nextjs/server").auth as jest.Mock;
const mockGetDatabase = require("@/lib/database").getDatabase as jest.Mock;
const mockEnsureUserSynced = require("@/lib/sync-user")
.ensureUserSynced as jest.Mock;
const mockMarkThreadAsRead = require("@/lib/chat")
.markThreadAsRead as jest.Mock;
beforeEach(() => {
jest.clearAllMocks();
mockGetDatabase.mockResolvedValue({});
});
it("returns 401 for unauthenticated request", async () => {
mockAuth.mockResolvedValue({ userId: null });
const request = new NextRequest(
"http://localhost/api/chat/threads/thread_1/read",
{
method: "POST",
body: JSON.stringify({ lastReadMessageId: "msg_1" }),
},
);
const response = await POST(request, {
params: Promise.resolve({ id: "thread_1" }),
});
expect(response.status).toBe(401);
});
it("returns 403 when synced user is missing", async () => {
mockAuth.mockResolvedValue({ userId: "user_1" });
mockEnsureUserSynced.mockResolvedValue(null);
const request = new NextRequest(
"http://localhost/api/chat/threads/thread_1/read",
{
method: "POST",
body: JSON.stringify({ lastReadMessageId: "msg_1" }),
},
);
const response = await POST(request, {
params: Promise.resolve({ id: "thread_1" }),
});
expect(response.status).toBe(403);
expect(mockMarkThreadAsRead).not.toHaveBeenCalled();
});
it("maps ChatError to status code", async () => {
mockAuth.mockResolvedValue({ userId: "user_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "user_1",
role: "client",
gymId: "gym_1",
});
mockMarkThreadAsRead.mockRejectedValue({
status: 403,
message: "Forbidden",
});
const request = new NextRequest(
"http://localhost/api/chat/threads/thread_1/read",
{
method: "POST",
body: JSON.stringify({ lastReadMessageId: "msg_1" }),
},
);
const response = await POST(request, {
params: Promise.resolve({ id: "thread_1" }),
});
expect(response.status).toBe(403);
});
it("returns success for authorized user", async () => {
mockAuth.mockResolvedValue({ userId: "user_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "user_1",
role: "client",
gymId: "gym_1",
});
mockMarkThreadAsRead.mockResolvedValue(undefined);
const request = new NextRequest(
"http://localhost/api/chat/threads/thread_1/read",
{
method: "POST",
body: JSON.stringify({ lastReadMessageId: "msg_1" }),
},
);
const response = await POST(request, {
params: Promise.resolve({ id: "thread_1" }),
});
expect(response.status).toBe(200);
expect(mockMarkThreadAsRead).toHaveBeenCalled();
});
});
@@ -0,0 +1,60 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { ensureUserSynced } from "@/lib/sync-user";
import { getDatabase } from "@/lib/database";
import { isChatError, markThreadAsRead } from "@/lib/chat";
import log from "@/lib/logger";
interface Params {
params: Promise<{ id: string }>;
}
export async function POST(request: NextRequest, { params }: Params) {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id: threadId } = await params;
const body = await request.json().catch(() => ({}));
const lastReadMessageId =
body &&
typeof body === "object" &&
typeof body.lastReadMessageId === "string"
? body.lastReadMessageId
: undefined;
const db = await getDatabase();
const currentUser = await ensureUserSynced(userId, db);
if (!currentUser) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
await markThreadAsRead({
currentUser: {
id: currentUser.id,
role: currentUser.role,
gymId: currentUser.gymId,
},
threadId,
lastReadMessageId,
});
return NextResponse.json({ success: true });
} catch (error) {
if (isChatError(error)) {
return NextResponse.json(
{ error: error.message },
{ status: error.status },
);
}
log.error("Failed to mark chat thread as read", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
@@ -0,0 +1,52 @@
import { NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { ensureUserSynced } from "@/lib/sync-user";
import { getDatabase } from "@/lib/database";
import { isChatError, listThreadsForUser } from "@/lib/chat";
import log from "@/lib/logger";
interface Params {
params: Promise<{ id: string }>;
}
export async function GET(_request: Request, { params }: Params) {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { id: threadId } = await params;
const db = await getDatabase();
const currentUser = await ensureUserSynced(userId, db);
if (!currentUser) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const threads = await listThreadsForUser({
id: currentUser.id,
role: currentUser.role,
gymId: currentUser.gymId,
});
const thread = threads.find((item) => item.id === threadId);
if (!thread) {
return NextResponse.json({ error: "Thread not found" }, { status: 404 });
}
return NextResponse.json({ thread });
} catch (error) {
if (isChatError(error)) {
return NextResponse.json(
{ error: error.message },
{ status: error.status },
);
}
log.error("Failed to fetch chat thread", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
@@ -0,0 +1,57 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { ensureUserSynced } from "@/lib/sync-user";
import { getDatabase } from "@/lib/database";
import {
getOrCreateGymRoomThread,
isChatError,
listThreadsForUser,
} from "@/lib/chat";
import log from "@/lib/logger";
export async function GET(request: NextRequest) {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const db = await getDatabase();
const currentUser = await ensureUserSynced(userId, db);
if (!currentUser) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const includeGymRoom =
request.nextUrl.searchParams.get("includeGymRoom") !== "false";
if (includeGymRoom && currentUser.gymId) {
await getOrCreateGymRoomThread({
id: currentUser.id,
role: currentUser.role,
gymId: currentUser.gymId,
});
}
const threads = await listThreadsForUser({
id: currentUser.id,
role: currentUser.role,
gymId: currentUser.gymId,
});
return NextResponse.json({ threads });
} catch (error) {
if (isChatError(error)) {
return NextResponse.json(
{ error: error.message },
{ status: error.status },
);
}
log.error("Failed to fetch chat threads", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
@@ -0,0 +1,156 @@
import { NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { getUserMembershipContext } from "@/lib/membership/access";
import log from "@/lib/logger";
interface OpenFoodFactsProduct {
product_name?: string;
product_name_en?: string;
brands?: string;
image_url?: string;
image_front_url?: string;
serving_size?: string;
nutriments?: {
[key: string]: number | string | undefined;
"energy-kcal_serving"?: number;
"energy-kcal_100g"?: number;
proteins_serving?: number;
proteins_100g?: number;
carbohydrates_serving?: number;
carbohydrates_100g?: number;
fat_serving?: number;
fat_100g?: number;
};
}
interface OpenFoodFactsResponse {
status: number;
code: string;
product?: OpenFoodFactsProduct;
}
function normalizeBarcode(rawCode: string): string {
return rawCode.replace(/\D/g, "").trim();
}
function isSupportedBarcode(code: string): boolean {
return [8, 12, 13].includes(code.length);
}
function getNumber(value: unknown): number | undefined {
if (typeof value === "number" && Number.isFinite(value)) return value;
if (typeof value === "string") {
const parsed = Number(value);
if (Number.isFinite(parsed)) return parsed;
}
return undefined;
}
function buildProductPayload(code: string, product: OpenFoodFactsProduct) {
const caloriesPerServing =
getNumber(product.nutriments?.["energy-kcal_serving"]) ??
getNumber(product.nutriments?.["energy-kcal_100g"]) ??
0;
const protein =
getNumber(product.nutriments?.proteins_serving) ??
getNumber(product.nutriments?.proteins_100g);
const carbs =
getNumber(product.nutriments?.carbohydrates_serving) ??
getNumber(product.nutriments?.carbohydrates_100g);
const fat =
getNumber(product.nutriments?.fat_serving) ??
getNumber(product.nutriments?.fat_100g);
return {
barcode: code,
name: product.product_name || product.product_name_en || "Unknown Product",
brand: product.brands || null,
imageUrl: product.image_url || product.image_front_url || null,
servingSize: product.serving_size || "1 serving",
caloriesPerServing: Math.max(0, Math.round(caloriesPerServing)),
macros: {
protein: protein ?? null,
carbs: carbs ?? null,
fat: fat ?? null,
},
source: "openfoodfacts" as const,
};
}
export async function GET(
_request: Request,
{ params }: { params: Promise<{ code: string }> },
) {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { features, membershipType } = await getUserMembershipContext(userId);
if (!features.nutritionTracking) {
return NextResponse.json(
{
error:
"Barcode food scan is available on Premium and VIP memberships",
membershipType,
},
{ status: 403 },
);
}
const { code: rawCode } = await params;
const code = normalizeBarcode(rawCode);
if (!isSupportedBarcode(code)) {
return NextResponse.json(
{ error: "Invalid barcode. Use EAN-8, UPC-A, or EAN-13 formats." },
{ status: 400 },
);
}
const response = await fetch(
`https://world.openfoodfacts.org/api/v2/product/${code}.json`,
{
headers: {
"User-Agent": "FitAI/1.0 (fitai.app)",
},
cache: "no-store",
},
);
if (!response.ok) {
log.warn("OpenFoodFacts lookup failed", {
status: response.status,
barcode: code,
});
return NextResponse.json(
{ error: "Food lookup service unavailable. Please try again." },
{ status: 503 },
);
}
const payload = (await response.json()) as OpenFoodFactsResponse;
if (payload.status !== 1 || !payload.product) {
return NextResponse.json(
{ error: "Product not found in OpenFoodFacts" },
{ status: 404 },
);
}
return NextResponse.json({
success: true,
data: buildProductPayload(code, payload.product),
meta: {
timestamp: new Date().toISOString(),
},
});
} catch (error) {
log.error("Failed barcode food lookup", error);
return NextResponse.json(
{ error: "Failed to lookup food barcode" },
{ status: 500 },
);
}
}
+175 -24
View File
@@ -3,6 +3,7 @@ import { auth } from "@clerk/nextjs/server";
import { eq, sql } from "@fitai/database";
import { db, users as usersTable, gyms as gymsTable } from "@fitai/database";
import { ensureUserSynced } from "@/lib/sync-user";
import { getDatabase } from "@/lib/database";
import log from "@/lib/logger";
async function ensureGymsTable() {
@@ -17,6 +18,178 @@ async function ensureGymsTable() {
updated_at INTEGER NOT NULL
)
`);
const columns = await db.all(sql`PRAGMA table_info('gyms')`);
const columnNames = new Set(
(columns as Array<{ name?: string }>)
.map((col) => col.name)
.filter(Boolean),
);
if (!columnNames.has("latitude")) {
await db.run(sql`ALTER TABLE gyms ADD COLUMN latitude REAL`);
}
if (!columnNames.has("longitude")) {
await db.run(sql`ALTER TABLE gyms ADD COLUMN longitude REAL`);
}
if (!columnNames.has("geofence_radius_meters")) {
await db.run(
sql`ALTER TABLE gyms ADD COLUMN geofence_radius_meters REAL NOT NULL DEFAULT 30`,
);
}
if (!columnNames.has("geofence_enabled")) {
await db.run(
sql`ALTER TABLE gyms ADD COLUMN geofence_enabled INTEGER NOT NULL DEFAULT 1`,
);
}
}
// PATCH /api/gyms/[id]
// Update gym details and geofence configuration
export async function PATCH(
request: Request,
{ params }: { params: Promise<{ id: string }> },
) {
try {
const { id: gymId } = await params;
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const appDb = await getDatabase();
const currentUser = await ensureUserSynced(userId, appDb);
if (
!currentUser ||
(currentUser.role !== "superAdmin" && currentUser.role !== "admin")
) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
await ensureGymsTable();
const existingGym = await db
.select()
.from(gymsTable)
.where(eq(gymsTable.id, gymId))
.get();
if (!existingGym) {
return NextResponse.json({ error: "Gym not found" }, { status: 404 });
}
if (
currentUser.role === "admin" &&
currentUser.gymId &&
currentUser.gymId !== gymId
) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const body = await request.json().catch(() => null);
if (!body || typeof body !== "object") {
return NextResponse.json({ error: "Invalid body" }, { status: 400 });
}
const latitude =
body.latitude === undefined || body.latitude === null
? null
: Number(body.latitude);
const longitude =
body.longitude === undefined || body.longitude === null
? null
: Number(body.longitude);
const geofenceRadiusMeters =
body.geofenceRadiusMeters === undefined ||
body.geofenceRadiusMeters === null
? 30
: Number(body.geofenceRadiusMeters);
const geofenceEnabled =
body.geofenceEnabled === undefined ? true : Boolean(body.geofenceEnabled);
if (
latitude !== null &&
(!Number.isFinite(latitude) || latitude < -90 || latitude > 90)
) {
return NextResponse.json(
{ error: "latitude must be between -90 and 90" },
{ status: 400 },
);
}
if (
longitude !== null &&
(!Number.isFinite(longitude) || longitude < -180 || longitude > 180)
) {
return NextResponse.json(
{ error: "longitude must be between -180 and 180" },
{ status: 400 },
);
}
if (!Number.isFinite(geofenceRadiusMeters) || geofenceRadiusMeters <= 0) {
return NextResponse.json(
{ error: "geofenceRadiusMeters must be a positive number" },
{ status: 400 },
);
}
await db.run(sql`
UPDATE gyms
SET
latitude = ${latitude},
longitude = ${longitude},
geofence_radius_meters = ${geofenceRadiusMeters},
geofence_enabled = ${geofenceEnabled ? 1 : 0},
updated_at = ${Math.floor(Date.now() / 1000)}
WHERE id = ${gymId}
`);
const updatedRows = await db.all(sql`
SELECT
id,
name,
location,
latitude,
longitude,
geofence_radius_meters as geofenceRadiusMeters,
geofence_enabled as geofenceEnabled,
status,
admin_user_id as adminUserId,
created_at as createdAt,
updated_at as updatedAt
FROM gyms
WHERE id = ${gymId}
LIMIT 1
`);
const updated = updatedRows?.[0]
? {
...updatedRows[0],
geofenceEnabled:
typeof (updatedRows[0] as { geofenceEnabled?: unknown })
.geofenceEnabled === "boolean"
? (updatedRows[0] as { geofenceEnabled: boolean }).geofenceEnabled
: Boolean(
(updatedRows[0] as { geofenceEnabled?: unknown })
.geofenceEnabled,
),
}
: null;
return NextResponse.json(updated);
} catch (error) {
log.error("Failed to update gym", error);
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 },
);
}
}
// DELETE /api/gyms/[id]
@@ -33,30 +206,8 @@ export async function DELETE(
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
// Ensure user is synced
const currentUser = await ensureUserSynced(userId, {
getUserById: async (id: string) => {
const row = await db
.select()
.from(usersTable)
.where(eq(usersTable.id, id))
.get();
return row
? {
id: row.id,
email: row.email,
firstName: row.firstName,
lastName: row.lastName,
password: row.password ?? "",
phone: row.phone ?? undefined,
role: row.role,
imageUrl: undefined,
createdAt: new Date(row.createdAt),
updatedAt: new Date(row.updatedAt),
}
: null;
},
} as any);
const appDb = await getDatabase();
const currentUser = await ensureUserSynced(userId, appDb);
// Only superAdmin can delete gyms
if (!currentUser || currentUser.role !== "superAdmin") {
@@ -1,7 +1,10 @@
import { NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { eq, sql } from "@fitai/database";
import { db, gyms as gymsTable } from "@fitai/database";
import log from "@/lib/logger";
import { getDatabase } from "@/lib/database";
import { ensureUserSynced } from "@/lib/sync-user";
async function ensureGymsTable() {
await db.run(sql`
@@ -24,7 +27,33 @@ export async function GET(
{ params }: { params: Promise<{ id: string }> },
) {
try {
const { userId: clerkUserId } = await auth();
if (!clerkUserId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const appDb = await getDatabase();
const currentUser = await ensureUserSynced(clerkUserId, appDb);
if (!currentUser) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const { id: gymId } = await params;
const canViewGymStats =
currentUser.role === "superAdmin" || currentUser.role === "admin";
if (!canViewGymStats) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
if (currentUser.role !== "superAdmin" && currentUser.gymId !== gymId) {
return NextResponse.json(
{ error: "Forbidden - Cannot access other gym's data" },
{ status: 403 },
);
}
await ensureGymsTable();
// Get gym info using Drizzle ORM
@@ -0,0 +1,88 @@
/**
* @jest-environment node
*/
import { GET } from "../route";
jest.mock("@clerk/nextjs/server", () => ({
auth: jest.fn(),
}));
jest.mock("@/lib/database", () => ({
getDatabase: jest.fn(),
}));
jest.mock("@/lib/sync-user", () => ({
ensureUserSynced: jest.fn(),
}));
jest.mock("@fitai/database", () => ({
eq: jest.fn(() => ({})),
sql: jest.fn((strings: TemplateStringsArray) => strings.join("")),
db: {
run: jest.fn(),
select: jest.fn(() => ({
from: jest.fn(() => ({
where: jest.fn(() => ({
orderBy: jest.fn(() => ({
all: jest.fn().mockResolvedValue([
{ id: "gym_a", status: "active", name: "Gym A" },
{ id: "gym_b", status: "active", name: "Gym B" },
]),
})),
})),
})),
})),
gyms: {
status: "active",
},
users: {},
},
gyms: {
status: "active",
},
users: {},
}));
describe("GET /api/gyms authz", () => {
const mockAuth = require("@clerk/nextjs/server").auth as jest.Mock;
const mockGetDatabase = require("@/lib/database").getDatabase as jest.Mock;
const mockEnsureUserSynced = require("@/lib/sync-user")
.ensureUserSynced as jest.Mock;
beforeEach(() => {
jest.clearAllMocks();
mockGetDatabase.mockResolvedValue({});
});
it("returns only own gym for admin", async () => {
mockAuth.mockResolvedValue({ userId: "admin_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "admin_1",
role: "admin",
gymId: "gym_a",
});
const response = await GET();
const data = await response.json();
expect(response.status).toBe(200);
expect(data).toHaveLength(1);
expect(data[0].id).toBe("gym_a");
});
it("returns all gyms for superAdmin", async () => {
mockAuth.mockResolvedValue({ userId: "super_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "super_1",
role: "superAdmin",
gymId: null,
});
const response = await GET();
const data = await response.json();
expect(response.status).toBe(200);
expect(data).toHaveLength(2);
});
});
+191 -76
View File
@@ -3,6 +3,7 @@ import { auth } from "@clerk/nextjs/server";
import { eq, sql } from "@fitai/database";
import { db, users as usersTable, gyms as gymsTable } from "@fitai/database";
import { ensureUserSynced } from "@/lib/sync-user";
import { getDatabase } from "@/lib/database";
import log from "@/lib/logger";
async function ensureGymsTable() {
@@ -17,21 +18,102 @@ async function ensureGymsTable() {
updated_at INTEGER NOT NULL
)
`);
const columns = await db.all(sql`PRAGMA table_info('gyms')`);
const columnNames = new Set(
(columns as Array<{ name?: string }>)
.map((col) => col.name)
.filter(Boolean),
);
if (!columnNames.has("latitude")) {
await db.run(sql`ALTER TABLE gyms ADD COLUMN latitude REAL`);
}
if (!columnNames.has("longitude")) {
await db.run(sql`ALTER TABLE gyms ADD COLUMN longitude REAL`);
}
if (!columnNames.has("geofence_radius_meters")) {
await db.run(
sql`ALTER TABLE gyms ADD COLUMN geofence_radius_meters REAL NOT NULL DEFAULT 30`,
);
}
if (!columnNames.has("geofence_enabled")) {
await db.run(
sql`ALTER TABLE gyms ADD COLUMN geofence_enabled INTEGER NOT NULL DEFAULT 1`,
);
}
}
// GET /api/gyms
// Lists active gyms for selection (grid)
export async function GET() {
try {
await ensureGymsTable();
const rows = await db
.select()
.from(gymsTable)
.where(eq(gymsTable.status, "active"))
.orderBy(sql`created_at DESC`)
.all();
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
return NextResponse.json(rows);
const appDb = await getDatabase();
const currentUser = await ensureUserSynced(userId, appDb);
if (!currentUser) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
if (currentUser.role !== "admin" && currentUser.role !== "superAdmin") {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
await ensureGymsTable();
let rows = (await db.all(sql`
SELECT
id,
name,
location,
latitude,
longitude,
geofence_radius_meters as geofenceRadiusMeters,
geofence_enabled as geofenceEnabled,
status,
admin_user_id as adminUserId,
created_at as createdAt,
updated_at as updatedAt
FROM gyms
WHERE status = 'active'
ORDER BY created_at DESC
`)) as Array<{
id: string;
name: string;
location: string | null;
latitude: number | null;
longitude: number | null;
geofenceRadiusMeters: number | null;
geofenceEnabled: number | boolean | null;
status: "active" | "inactive";
adminUserId: string;
createdAt: number;
updatedAt: number;
}>;
if (currentUser.role !== "superAdmin") {
if (!currentUser.gymId) {
return NextResponse.json({ error: "No gym assigned" }, { status: 403 });
}
rows = rows.filter((row) => row.id === currentUser.gymId);
}
return NextResponse.json(
rows.map((row) => ({
...row,
geofenceEnabled:
typeof row.geofenceEnabled === "boolean"
? row.geofenceEnabled
: Boolean(row.geofenceEnabled),
})),
);
} catch (error) {
log.error("Failed to get gyms", error);
return new NextResponse("Internal Server Error", { status: 500 });
@@ -48,60 +130,8 @@ export async function POST(req: Request) {
const { userId } = await auth();
if (!userId) return new NextResponse("Unauthorized", { status: 401 });
// Ensure our local DB has the user synced (role, etc.)
const currentUser = await ensureUserSynced(userId, {
// minimal facade for ensureUserSynced to work: it expects an object implementing part of IDatabase
getUserById: async (id: string) => {
const row = await db
.select()
.from(usersTable)
.where(eq(usersTable.id, id))
.get();
return row
? {
id: row.id,
email: row.email,
firstName: row.firstName,
lastName: row.lastName,
password: row.password ?? "",
phone: row.phone ?? undefined,
role: row.role,
imageUrl: undefined,
createdAt: new Date(row.createdAt),
updatedAt: new Date(row.updatedAt),
}
: null;
},
updateUser: async (id: string, updates: any) => {
await db
.update(usersTable)
.set({
...updates,
updatedAt: new Date(),
})
.where(eq(usersTable.id, id))
.run();
const row = await db
.select()
.from(usersTable)
.where(eq(usersTable.id, id))
.get();
return row
? {
id: row.id,
email: row.email,
firstName: row.firstName,
lastName: row.lastName,
password: row.password ?? "",
phone: row.phone ?? undefined,
role: row.role,
imageUrl: undefined,
createdAt: new Date(row.createdAt),
updatedAt: new Date(row.updatedAt),
}
: null;
},
} as any);
const appDb = await getDatabase();
const currentUser = await ensureUserSynced(userId, appDb);
if (
!currentUser ||
@@ -117,6 +147,21 @@ export async function POST(req: Request) {
const name = String(body.name ?? "").trim();
const location = body.location ? String(body.location).trim() : null;
const latitude =
body.latitude === undefined || body.latitude === null
? null
: Number(body.latitude);
const longitude =
body.longitude === undefined || body.longitude === null
? null
: Number(body.longitude);
const geofenceRadiusMeters =
body.geofenceRadiusMeters === undefined ||
body.geofenceRadiusMeters === null
? 30
: Number(body.geofenceRadiusMeters);
const geofenceEnabled =
body.geofenceEnabled === undefined ? true : Boolean(body.geofenceEnabled);
let adminUserId: string | null = body.adminUserId
? String(body.adminUserId)
: null;
@@ -125,6 +170,33 @@ export async function POST(req: Request) {
return NextResponse.json({ error: "name is required" }, { status: 400 });
}
if (
latitude !== null &&
(!Number.isFinite(latitude) || latitude < -90 || latitude > 90)
) {
return NextResponse.json(
{ error: "latitude must be between -90 and 90" },
{ status: 400 },
);
}
if (
longitude !== null &&
(!Number.isFinite(longitude) || longitude < -180 || longitude > 180)
) {
return NextResponse.json(
{ error: "longitude must be between -180 and 180" },
{ status: 400 },
);
}
if (!Number.isFinite(geofenceRadiusMeters) || geofenceRadiusMeters <= 0) {
return NextResponse.json(
{ error: "geofenceRadiusMeters must be a positive number" },
{ status: 400 },
);
}
// Enforce admin ownership rules
if (currentUser.role === "admin") {
adminUserId = currentUser.id;
@@ -152,15 +224,33 @@ export async function POST(req: Request) {
const nowTs = new Date();
// Use Drizzle's insert method instead of raw SQL
await db.insert(gymsTable).values({
id,
name,
location: location ?? null,
status: "active",
adminUserId: adminUserId!,
createdAt: nowTs,
updatedAt: nowTs,
});
await db.run(sql`
INSERT INTO gyms (
id,
name,
location,
latitude,
longitude,
geofence_radius_meters,
geofence_enabled,
status,
admin_user_id,
created_at,
updated_at
) VALUES (
${id},
${name},
${location ?? null},
${latitude},
${longitude},
${geofenceRadiusMeters},
${geofenceEnabled ? 1 : 0},
${"active"},
${adminUserId!},
${Math.floor(nowTs.getTime() / 1000)},
${Math.floor(nowTs.getTime() / 1000)}
)
`);
// Assign the admin to this gym immediately after creation
await db
@@ -168,11 +258,36 @@ export async function POST(req: Request) {
.set({ gymId: id, updatedAt: nowTs })
.where(eq(usersTable.id, adminUserId!));
const created = await db
.select()
.from(gymsTable)
.where(eq(gymsTable.id, id))
.get();
const rowsCreated = await db.all(sql`
SELECT
id,
name,
location,
latitude,
longitude,
geofence_radius_meters as geofenceRadiusMeters,
geofence_enabled as geofenceEnabled,
status,
admin_user_id as adminUserId,
created_at as createdAt,
updated_at as updatedAt
FROM gyms
WHERE id = ${id}
LIMIT 1
`);
const createdRow = rowsCreated?.[0] ?? null;
const created = createdRow
? {
...createdRow,
geofenceEnabled:
typeof (createdRow as { geofenceEnabled?: unknown })
.geofenceEnabled === "boolean"
? (createdRow as { geofenceEnabled: boolean }).geofenceEnabled
: Boolean(
(createdRow as { geofenceEnabled?: unknown }).geofenceEnabled,
),
}
: null;
return NextResponse.json(created, { status: 201 });
} catch (error) {
log.error("Failed to create gym", error);
+37
View File
@@ -3,6 +3,7 @@ import { NextRequest, NextResponse } from "next/server";
import { getDatabase } from "@/lib/database";
import { ensureUserSynced } from "@/lib/sync-user";
import log from "@/lib/logger";
import { getUserMembershipContext } from "@/lib/membership/access";
export async function POST(req: NextRequest) {
try {
@@ -11,6 +12,18 @@ export async function POST(req: NextRequest) {
const db = await getDatabase();
await ensureUserSynced(userId, db);
const { features, membershipType } = await getUserMembershipContext(userId);
if (!features.hydrationTracking) {
return NextResponse.json(
{
error:
"Hydration tracking is available on Premium and VIP memberships",
membershipType,
},
{ status: 403 },
);
}
const body = await req.json();
const { date, entries, totalWater, waterGoal } = body;
@@ -58,6 +71,18 @@ export async function GET(req: NextRequest) {
const db = await getDatabase();
await ensureUserSynced(userId, db);
const { features, membershipType } = await getUserMembershipContext(userId);
if (!features.hydrationTracking) {
return NextResponse.json(
{
error:
"Hydration tracking is available on Premium and VIP memberships",
membershipType,
},
{ status: 403 },
);
}
const url = new URL(req.url);
const date = url.searchParams.get("date");
@@ -100,6 +125,18 @@ export async function DELETE(req: NextRequest) {
const db = await getDatabase();
await ensureUserSynced(userId, db);
const { features, membershipType } = await getUserMembershipContext(userId);
if (!features.hydrationTracking) {
return NextResponse.json(
{
error:
"Hydration tracking is available on Premium and VIP memberships",
membershipType,
},
{ status: 403 },
);
}
const url = new URL(req.url);
const id = url.searchParams.get("id");
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { auth, clerkClient } from "@clerk/nextjs/server";
import { getAuthContext } from "@/lib/auth/context";
import log from "@/lib/logger";
/**
@@ -18,6 +19,7 @@ export async function POST(
}
const { id: invitationId } = await params;
const authContext = await getAuthContext();
// Fetch pending invitations to find the one being resent
const client = await clerkClient();
@@ -38,11 +40,23 @@ export async function POST(
}
const metadata = invitation.publicMetadata as any;
const invitationGymId =
(metadata?.gymId as string | null | undefined) ?? null;
const createdBy = (metadata?.createdBy as string | undefined) ?? undefined;
const canManageByRole =
authContext.role === "superAdmin" ||
(authContext.role === "admin" &&
authContext.gymId !== null &&
invitationGymId === authContext.gymId);
// Check if current user created this invitation
if (metadata?.createdBy !== userId) {
if (createdBy !== userId && !canManageByRole) {
return NextResponse.json(
{ error: "Forbidden - You can only resend invitations you created" },
{
error:
"Forbidden - You can only resend invitations you created or manage within your scope",
},
{ status: 403 },
);
}
@@ -1,5 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { auth, clerkClient } from "@clerk/nextjs/server";
import { getAuthContext } from "@/lib/auth/context";
import log from "@/lib/logger";
/**
@@ -18,6 +19,7 @@ export async function DELETE(
}
const { id: invitationId } = await params;
const authContext = await getAuthContext();
// Fetch pending invitations to find and verify the one being revoked
const client = await clerkClient();
@@ -38,11 +40,23 @@ export async function DELETE(
}
const metadata = invitation.publicMetadata as any;
const invitationGymId =
(metadata?.gymId as string | null | undefined) ?? null;
const createdBy = (metadata?.createdBy as string | undefined) ?? undefined;
const canManageByRole =
authContext.role === "superAdmin" ||
(authContext.role === "admin" &&
authContext.gymId !== null &&
invitationGymId === authContext.gymId);
// Check if current user created this invitation
if (metadata?.createdBy !== userId) {
if (createdBy !== userId && !canManageByRole) {
return NextResponse.json(
{ error: "Forbidden - You can only cancel invitations you created" },
{
error:
"Forbidden - You can only cancel invitations you created or manage within your scope",
},
{ status: 403 },
);
}
@@ -0,0 +1,92 @@
/**
* @jest-environment node
*/
import { NextRequest } from "next/server";
import { POST } from "../route";
jest.mock("@clerk/nextjs/server", () => ({
auth: jest.fn(),
clerkClient: jest.fn(),
}));
jest.mock("@/lib/auth/context", () => ({
getAuthContext: jest.fn(),
}));
jest.mock("@/lib/logger", () => ({
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
debug: jest.fn(),
}));
describe("POST /api/invitations authz", () => {
const mockAuth = require("@clerk/nextjs/server").auth as jest.Mock;
const mockClerkClient = require("@clerk/nextjs/server")
.clerkClient as jest.Mock;
const mockGetAuthContext = require("@/lib/auth/context")
.getAuthContext as jest.Mock;
const createInvitation = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
mockClerkClient.mockResolvedValue({
invitations: {
createInvitation,
},
});
});
it("blocks admin from inviting into another gym", async () => {
mockAuth.mockResolvedValue({ userId: "admin_1" });
mockGetAuthContext.mockResolvedValue({
userId: "admin_1",
role: "admin",
gymId: "gym_a",
});
const request = new NextRequest("http://localhost/api/invitations", {
method: "POST",
body: JSON.stringify({
inviteeEmail: "test@example.com",
roleAssigned: "trainer",
gymId: "gym_b",
}),
});
const response = await POST(request);
expect(response.status).toBe(403);
expect(createInvitation).not.toHaveBeenCalled();
});
it("allows superAdmin to invite with explicit gym", async () => {
mockAuth.mockResolvedValue({ userId: "super_1" });
mockGetAuthContext.mockResolvedValue({
userId: "super_1",
role: "superAdmin",
gymId: null,
});
createInvitation.mockResolvedValue({ id: "inv_1" });
const request = new NextRequest("http://localhost/api/invitations", {
method: "POST",
body: JSON.stringify({
inviteeEmail: "test@example.com",
roleAssigned: "admin",
gymId: "gym_b",
}),
});
const response = await POST(request);
expect(response.status).toBe(201);
expect(createInvitation).toHaveBeenCalledWith(
expect.objectContaining({
emailAddress: "test@example.com",
}),
);
});
});
+31 -71
View File
@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import { auth, clerkClient } from "@clerk/nextjs/server";
import { getAuthContext } from "@/lib/auth/context";
import { validateGymAccess } from "@/lib/auth/permissions";
import { getInvitableRoles, validateGymAccess } from "@/lib/auth/permissions";
import log from "@/lib/logger";
/**
@@ -132,91 +132,51 @@ export async function POST(req: Request) {
);
}
// Fetch inviter user from Clerk
const client = await clerkClient();
const inviter = await client.users.getUser(userId);
const inviterRole =
(inviter.publicMetadata?.role as
| "superAdmin"
| "admin"
| "trainer"
| "client"
| "generalUser") ?? "client";
const inviterGymId =
(inviter.publicMetadata?.gymId as string | undefined) ?? undefined;
const authContext = await getAuthContext();
const { role: inviterRole, gymId: inviterGymId } = authContext;
const allowedRoles = getInvitableRoles(inviterRole);
if (!allowedRoles.includes(roleAssigned)) {
return NextResponse.json(
{ error: `Forbidden - Cannot invite role '${roleAssigned}'` },
{ status: 403 },
);
}
// Enforce role-based rules and resolve target gymId for the invitation
let gymIdForInvite: string | null = null;
switch (inviterRole) {
case "admin": {
if (roleAssigned !== "trainer" && roleAssigned !== "client") {
return NextResponse.json(
{ error: "Admin can only invite trainer or client" },
{ status: 403 },
);
}
if (!inviterGymId) {
return NextResponse.json(
{ error: "Inviter admin must be assigned to a gym" },
{ status: 400 },
);
}
gymIdForInvite = inviterGymId;
break;
}
case "trainer": {
if (roleAssigned !== "client") {
return NextResponse.json(
{ error: "Trainer can only invite client" },
{ status: 403 },
);
}
if (!inviterGymId) {
return NextResponse.json(
{ error: "Inviter trainer must be assigned to a gym" },
{ status: 400 },
);
}
gymIdForInvite = inviterGymId;
break;
}
case "superAdmin": {
if (
roleAssigned !== "admin" &&
roleAssigned !== "trainer" &&
roleAssigned !== "client"
) {
return NextResponse.json(
{ error: "Invalid roleAssigned for SuperAdmin" },
{ status: 400 },
);
}
// Prefer explicitly provided gymId, otherwise fall back to inviter's gymId if present
gymIdForInvite = requestedGymId || inviterGymId || null;
if (!gymIdForInvite) {
return NextResponse.json(
{ error: "gymId is required for SuperAdmin when inviting" },
{ status: 400 },
);
}
break;
}
default: {
if (inviterRole === "superAdmin") {
gymIdForInvite = requestedGymId || inviterGymId || null;
if (!gymIdForInvite) {
return NextResponse.json(
{ error: "Inviter role not permitted to create invitations" },
{ error: "gymId is required for superAdmin invitations" },
{ status: 400 },
);
}
} else {
if (!inviterGymId) {
return NextResponse.json(
{ error: "Inviter must be assigned to a gym" },
{ status: 400 },
);
}
if (requestedGymId && requestedGymId !== inviterGymId) {
return NextResponse.json(
{ error: "Cannot invite users into another gym" },
{ status: 403 },
);
}
gymIdForInvite = inviterGymId;
}
// Create Clerk invitation with metadata needed by webhook to assign role & gym
// reuse existing Clerk client instance
const client = await clerkClient();
const invitation = await client.invitations.createInvitation({
emailAddress: inviteeEmail,
publicMetadata: {
role: roleAssigned,
gymId: gymIdForInvite,
createdBy: inviter.id,
createdBy: userId,
},
});
@@ -0,0 +1,32 @@
import { NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { MEMBERSHIP_FEATURES } from "@/lib/membership/features";
import { getUserMembershipContext } from "@/lib/membership/access";
export async function GET() {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { membershipType, features } = await getUserMembershipContext(userId);
return NextResponse.json({
success: true,
data: {
membershipType,
currentFeatures: features,
plans: MEMBERSHIP_FEATURES,
},
meta: {
timestamp: new Date().toISOString(),
},
});
} catch {
return NextResponse.json(
{ error: "Failed to load membership features" },
{ status: 500 },
);
}
}
@@ -0,0 +1,87 @@
/**
* @jest-environment node
*/
import { NextRequest } from "next/server";
import { POST } from "../route";
jest.mock("@clerk/nextjs/server", () => ({
auth: jest.fn(),
}));
jest.mock("@/lib/database", () => ({
getDatabase: jest.fn(),
}));
jest.mock("@/lib/sync-user", () => ({
ensureUserSynced: jest.fn(),
}));
jest.mock("@/lib/logger", () => ({
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
debug: jest.fn(),
}));
describe("POST /api/notifications authz", () => {
const mockAuth = require("@clerk/nextjs/server").auth as jest.Mock;
const mockGetDatabase = require("@/lib/database").getDatabase as jest.Mock;
const mockEnsureUserSynced = require("@/lib/sync-user")
.ensureUserSynced as jest.Mock;
const mockDb = {
getUserById: jest.fn(),
createNotification: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
mockGetDatabase.mockResolvedValue(mockDb);
});
it("returns 403 for non-staff user", async () => {
mockAuth.mockResolvedValue({ userId: "client_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "client_1",
role: "client",
gymId: "gym_a",
});
const request = new NextRequest("http://localhost/api/notifications", {
method: "POST",
body: JSON.stringify({
targetUserId: "client_2",
title: "Hello",
message: "Test",
type: "system",
}),
});
const response = await POST(request);
expect(response.status).toBe(403);
});
it("returns 403 for cross-gym notify by admin", async () => {
mockAuth.mockResolvedValue({ userId: "admin_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "admin_1",
role: "admin",
gymId: "gym_a",
});
mockDb.getUserById.mockResolvedValue({ id: "client_2", gymId: "gym_b" });
const request = new NextRequest("http://localhost/api/notifications", {
method: "POST",
body: JSON.stringify({
targetUserId: "client_2",
title: "Hello",
message: "Test",
type: "system",
}),
});
const response = await POST(request);
expect(response.status).toBe(403);
});
});
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { getDatabase } from "@/lib/database";
import log from "@/lib/logger";
import { ensureUserSynced } from "@/lib/sync-user";
/**
* GET /api/notifications
@@ -84,6 +85,39 @@ export async function POST(req: NextRequest) {
}
const db = await getDatabase();
const currentUser = await ensureUserSynced(userId, db);
if (!currentUser) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const canCreateNotifications =
currentUser.role === "superAdmin" ||
currentUser.role === "admin" ||
currentUser.role === "trainer";
if (!canCreateNotifications) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const targetUser = await db.getUserById(targetUserId);
if (!targetUser) {
return NextResponse.json(
{ error: "Target user not found" },
{ status: 404 },
);
}
if (
currentUser.role !== "superAdmin" &&
(!currentUser.gymId || targetUser.gymId !== currentUser.gymId)
) {
return NextResponse.json(
{ error: "Forbidden - Cannot notify users from other gyms" },
{ status: 403 },
);
}
const notification = await db.createNotification({
id: crypto.randomUUID(),
userId: targetUserId,
@@ -3,6 +3,7 @@ import { NextRequest, NextResponse } from "next/server";
import { getDatabase } from "@/lib/database";
import { ensureUserSynced } from "@/lib/sync-user";
import log from "@/lib/logger";
import { getUserMembershipContext } from "@/lib/membership/access";
export async function POST(req: NextRequest) {
try {
@@ -11,6 +12,18 @@ export async function POST(req: NextRequest) {
const db = await getDatabase();
await ensureUserSynced(userId, db);
const { features, membershipType } = await getUserMembershipContext(userId);
if (!features.nutritionTracking) {
return NextResponse.json(
{
error:
"Nutrition tracking is available on Premium and VIP memberships",
membershipType,
},
{ status: 403 },
);
}
const body = await req.json();
const {
@@ -59,6 +72,18 @@ export async function GET(req: NextRequest) {
const db = await getDatabase();
await ensureUserSynced(userId, db);
const { features, membershipType } = await getUserMembershipContext(userId);
if (!features.nutritionTracking) {
return NextResponse.json(
{
error:
"Nutrition tracking is available on Premium and VIP memberships",
membershipType,
},
{ status: 403 },
);
}
const url = new URL(req.url);
const date = url.searchParams.get("date");
@@ -88,6 +113,18 @@ export async function DELETE(req: NextRequest) {
const db = await getDatabase();
await ensureUserSynced(userId, db);
const { features, membershipType } = await getUserMembershipContext(userId);
if (!features.nutritionTracking) {
return NextResponse.json(
{
error:
"Nutrition tracking is available on Premium and VIP memberships",
membershipType,
},
{ status: 403 },
);
}
const url = new URL(req.url);
const id = url.searchParams.get("id");
+37
View File
@@ -3,6 +3,7 @@ import { NextRequest, NextResponse } from "next/server";
import { getDatabase } from "@/lib/database";
import { ensureUserSynced } from "@/lib/sync-user";
import log from "@/lib/logger";
import { getUserMembershipContext } from "@/lib/membership/access";
export async function POST(req: NextRequest) {
try {
@@ -11,6 +12,18 @@ export async function POST(req: NextRequest) {
const db = await getDatabase();
await ensureUserSynced(userId, db);
const { features, membershipType } = await getUserMembershipContext(userId);
if (!features.nutritionTracking) {
return NextResponse.json(
{
error:
"Nutrition tracking is available on Premium and VIP memberships",
membershipType,
},
{ status: 403 },
);
}
const body = await req.json();
const { date, meals, totalCalories, calorieGoal } = body;
@@ -58,6 +71,18 @@ export async function GET(req: NextRequest) {
const db = await getDatabase();
await ensureUserSynced(userId, db);
const { features, membershipType } = await getUserMembershipContext(userId);
if (!features.nutritionTracking) {
return NextResponse.json(
{
error:
"Nutrition tracking is available on Premium and VIP memberships",
membershipType,
},
{ status: 403 },
);
}
const url = new URL(req.url);
const date = url.searchParams.get("date");
@@ -100,6 +125,18 @@ export async function DELETE(req: NextRequest) {
const db = await getDatabase();
await ensureUserSynced(userId, db);
const { features, membershipType } = await getUserMembershipContext(userId);
if (!features.nutritionTracking) {
return NextResponse.json(
{
error:
"Nutrition tracking is available on Premium and VIP memberships",
membershipType,
},
{ status: 403 },
);
}
const url = new URL(req.url);
const id = url.searchParams.get("id");
@@ -0,0 +1,83 @@
/**
* @jest-environment node
*/
import { POST } from "../route";
jest.mock("@clerk/nextjs/server", () => ({
auth: jest.fn(),
}));
jest.mock("@/lib/database", () => ({
getDatabase: jest.fn(),
}));
jest.mock("@/lib/sync-user", () => ({
ensureUserSynced: jest.fn(),
}));
jest.mock("@/lib/logger", () => ({
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
debug: jest.fn(),
}));
describe("POST /api/recommendations/approve authz", () => {
const mockAuth = require("@clerk/nextjs/server").auth as jest.Mock;
const mockGetDatabase = require("@/lib/database").getDatabase as jest.Mock;
const mockEnsureUserSynced = require("@/lib/sync-user")
.ensureUserSynced as jest.Mock;
const mockDb = {
getAllRecommendations: jest.fn(),
getUserById: jest.fn(),
updateRecommendation: jest.fn(),
createNotification: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
mockGetDatabase.mockResolvedValue(mockDb);
});
it("returns 403 for non-staff role", async () => {
mockAuth.mockResolvedValue({ userId: "client_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "client_1",
role: "client",
gymId: "gym_a",
});
const req = new Request("http://localhost/api/recommendations/approve", {
method: "POST",
body: JSON.stringify({ recommendationId: "rec_1", status: "approved" }),
headers: { "Content-Type": "application/json" },
});
const res = await POST(req);
expect(res.status).toBe(403);
});
it("returns 403 for cross-gym approval by admin", async () => {
mockAuth.mockResolvedValue({ userId: "admin_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "admin_1",
role: "admin",
gymId: "gym_a",
});
mockDb.getAllRecommendations.mockResolvedValue([
{ id: "rec_1", userId: "client_1" },
]);
mockDb.getUserById.mockResolvedValue({ id: "client_1", gymId: "gym_b" });
const req = new Request("http://localhost/api/recommendations/approve", {
method: "POST",
body: JSON.stringify({ recommendationId: "rec_1", status: "approved" }),
headers: { "Content-Type": "application/json" },
});
const res = await POST(req);
expect(res.status).toBe(403);
});
});
@@ -1,13 +1,115 @@
import { NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { getDatabase } from "@/lib/database";
import log from "@/lib/logger";
import { ensureUserSynced } from "@/lib/sync-user";
const AI_LINK_PREFIX = "[AI_LINKED]";
type GoalType =
| "weight_target"
| "strength_milestone"
| "endurance_target"
| "flexibility_goal"
| "habit_building"
| "custom";
interface ParsedPlanItem {
id: string;
title: string;
description: string;
goalType: GoalType;
}
function inferGoalType(text: string): GoalType {
const normalized = text.toLowerCase();
if (
normalized.includes("strength") ||
normalized.includes("bench") ||
normalized.includes("squat") ||
normalized.includes("deadlift") ||
normalized.includes("weights")
) {
return "strength_milestone";
}
if (
normalized.includes("run") ||
normalized.includes("cardio") ||
normalized.includes("endurance") ||
normalized.includes("cycle")
) {
return "endurance_target";
}
if (
normalized.includes("stretch") ||
normalized.includes("mobility") ||
normalized.includes("yoga") ||
normalized.includes("flexibility")
) {
return "flexibility_goal";
}
if (
normalized.includes("daily") ||
normalized.includes("routine") ||
normalized.includes("habit")
) {
return "habit_building";
}
return "custom";
}
function parseActivityPlanToItems(activityPlan: string): ParsedPlanItem[] {
const lines = activityPlan
.replace(/\r\n/g, "\n")
.split(/\n+/)
.flatMap((line) => line.split(/(?<=[.!?])\s+(?=[A-Z0-9])/g))
.map((line) => line.trim())
.filter(Boolean)
.map((line) => line.replace(/^[-*•\d.)\s]+/, "").trim())
.filter((line) => line.length > 10)
.slice(0, 8);
const uniqueLines = Array.from(new Set(lines));
return uniqueLines.map((line) => ({
id: crypto.randomUUID(),
title: line.length > 72 ? `${line.slice(0, 69)}...` : line,
description: line,
goalType: inferGoalType(line),
}));
}
function getDefaultPlanItems(): ParsedPlanItem[] {
const defaults = [
"Complete 3 strength sessions this week with progressive overload.",
"Add 2 cardio sessions of 25-30 minutes for endurance.",
"Do a 10-minute mobility routine daily after training.",
];
return defaults.map((line) => ({
id: crypto.randomUUID(),
title: line.length > 72 ? `${line.slice(0, 69)}...` : line,
description: line,
goalType: inferGoalType(line),
}));
}
export async function POST(req: Request) {
try {
const { userId: clerkUserId } = await auth();
if (!clerkUserId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const body = await req.json();
log.debug("Approve recommendation request body", { body });
const { recommendationId, status, approvedBy } = body;
const { recommendationId, status } = body;
if (!recommendationId || !status) {
log.error("Missing required fields", {
@@ -22,12 +124,52 @@ export async function POST(req: Request) {
}
const db = await getDatabase();
const currentUser = await ensureUserSynced(clerkUserId, db);
if (!currentUser) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const canApproveRecommendations =
currentUser.role === "superAdmin" ||
currentUser.role === "admin" ||
currentUser.role === "trainer";
if (!canApproveRecommendations) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const existingRecommendation = (await db.getAllRecommendations()).find(
(recommendation) => recommendation.id === recommendationId,
);
if (!existingRecommendation) {
return NextResponse.json(
{ error: "Recommendation not found" },
{ status: 404 },
);
}
if (currentUser.role !== "superAdmin") {
const targetUser = await db.getUserById(existingRecommendation.userId);
if (
!currentUser.gymId ||
!targetUser ||
targetUser.gymId !== currentUser.gymId
) {
return NextResponse.json(
{ error: "Forbidden - Cannot access users from other gyms" },
{ status: 403 },
);
}
}
// Update recommendation status
const updates: any = {
status,
approvedAt: status === "approved" ? new Date() : undefined,
approvedBy: status === "approved" ? approvedBy : undefined,
approvedBy: status === "approved" ? clerkUserId : undefined,
};
// Remove undefined keys
@@ -47,8 +189,103 @@ export async function POST(req: Request) {
);
}
// If approved, create a notification for the user
let pausedGoalsCount = 0;
let createdGoalsCount = 0;
// If approved, regenerate linked AI goals and create a notification for the user
if (status === "approved") {
try {
const existingActiveGoals = await db.getFitnessGoalsByUserId(
updatedRecommendation.userId,
"active",
);
const linkedGoals = existingActiveGoals.filter((goal) =>
goal.notes?.startsWith(AI_LINK_PREFIX),
);
pausedGoalsCount = linkedGoals.length;
await Promise.all(
linkedGoals.map((goal) =>
db.updateFitnessGoal(goal.id, {
status: "paused",
notes: `${goal.notes || ""}\nPaused due to recommendation approval on ${new Date().toISOString()}`,
}),
),
);
let planItems = parseActivityPlanToItems(
updatedRecommendation.activityPlan || "",
);
if (
planItems.length === 0 &&
updatedRecommendation.recommendationText
) {
planItems = parseActivityPlanToItems(
updatedRecommendation.recommendationText,
);
}
if (planItems.length === 0) {
planItems = getDefaultPlanItems();
}
const fitnessProfileId =
updatedRecommendation.fitnessProfileId ||
(await db.getFitnessProfileByUserId(updatedRecommendation.userId))
?.id;
if (!fitnessProfileId) {
log.warn("No fitness profile available for AI goal creation", {
recommendationId,
userId: updatedRecommendation.userId,
});
} else {
const createdGoals = await Promise.all(
planItems.map((item) =>
db.createFitnessGoal({
id: crypto.randomUUID(),
userId: updatedRecommendation.userId,
fitnessProfileId,
goalType: item.goalType,
title: item.title,
description: item.description,
targetValue: undefined,
currentValue: 0,
unit: undefined,
startDate: new Date(),
targetDate: undefined,
completedDate: undefined,
status: "active",
progress: 0,
priority: "medium",
notes: `${AI_LINK_PREFIX} recommendationId=${updatedRecommendation.id}; itemId=${item.id}`,
}),
),
);
createdGoalsCount = createdGoals.length;
}
log.info("Regenerated linked AI goals from approved recommendation", {
recommendationId: updatedRecommendation.id,
userId: updatedRecommendation.userId,
pausedGoals: pausedGoalsCount,
createdGoals: createdGoalsCount,
});
} catch (goalConversionError) {
log.error(
"Failed to regenerate linked goals for approved recommendation",
goalConversionError,
{
recommendationId,
userId: updatedRecommendation.userId,
},
);
}
try {
await db.createNotification({
id: crypto.randomUUID(),
@@ -75,6 +312,8 @@ export async function POST(req: Request) {
data: updatedRecommendation,
meta: {
timestamp: new Date().toISOString(),
pausedGoals: pausedGoalsCount,
createdGoals: createdGoalsCount,
},
});
} catch (error) {
@@ -0,0 +1,455 @@
import { NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { getDatabase } from "@/lib/database";
import { buildAIContext } from "@/lib/ai/ai-context";
import { buildEnhancedPrompt, buildBasicPrompt } from "@/lib/ai/prompt-builder";
import log from "@/lib/logger";
import { ensureUserSynced } from "@/lib/sync-user";
import { getUserMembershipContext } from "@/lib/membership/access";
const AI_LINK_PREFIX = "[AI_LINKED]";
interface ParsedPlanItem {
id: string;
title: string;
description: string;
goalType:
| "weight_target"
| "strength_milestone"
| "endurance_target"
| "flexibility_goal"
| "habit_building"
| "custom";
}
interface GeneratedPlanContent {
recommendationText?: string;
activityPlan?: string;
dietPlan?: string;
}
function buildFallbackPlan(profile: {
activityLevel?: string;
fitnessGoals?: string[] | string;
medicalConditions?: string;
}): GeneratedPlanContent {
const goals = Array.isArray(profile.fitnessGoals)
? profile.fitnessGoals
: typeof profile.fitnessGoals === "string" && profile.fitnessGoals
? [profile.fitnessGoals]
: ["general fitness"];
const primaryGoal = goals[0] || "general fitness";
const activityLevel = profile.activityLevel || "moderate";
const hasMedicalNotes = Boolean(profile.medicalConditions?.trim());
return {
recommendationText:
`Personalized starter plan focused on ${primaryGoal} with ${activityLevel} activity pacing.` +
(hasMedicalNotes
? " Medical notes detected, so keep intensity conservative and progress gradually."
: ""),
activityPlan:
"- 3 strength sessions per week (full-body, 35-45 min)\n" +
"- 2 cardio sessions per week (20-30 min brisk walk/run/cycle)\n" +
"- 10 minutes daily mobility/stretching after workouts\n" +
"- 1 full recovery day each week",
dietPlan:
"- Build meals around lean protein, vegetables, whole grains, and hydration\n" +
"- Keep portions consistent and avoid skipping meals\n" +
"- Track intake daily and adjust calories based on weekly progress",
};
}
function inferGoalType(text: string): ParsedPlanItem["goalType"] {
const normalized = text.toLowerCase();
if (
normalized.includes("strength") ||
normalized.includes("bench") ||
normalized.includes("squat") ||
normalized.includes("deadlift") ||
normalized.includes("weights")
) {
return "strength_milestone";
}
if (
normalized.includes("run") ||
normalized.includes("cardio") ||
normalized.includes("endurance") ||
normalized.includes("cycle")
) {
return "endurance_target";
}
if (
normalized.includes("stretch") ||
normalized.includes("mobility") ||
normalized.includes("yoga") ||
normalized.includes("flexibility")
) {
return "flexibility_goal";
}
if (
normalized.includes("daily") ||
normalized.includes("routine") ||
normalized.includes("habit")
) {
return "habit_building";
}
return "custom";
}
function parseActivityPlanToItems(activityPlan: string): ParsedPlanItem[] {
const lines = activityPlan
.replace(/\r\n/g, "\n")
.split(/\n+/)
.flatMap((line) => line.split(/(?<=[.!?])\s+(?=[A-Z0-9])/g))
.map((line) => line.trim())
.filter(Boolean)
.map((line) => line.replace(/^[-*•\d.)\s]+/, "").trim())
.filter((line) => line.length > 10)
.slice(0, 8);
const uniqueLines = Array.from(new Set(lines));
return uniqueLines.map((line) => ({
id: crypto.randomUUID(),
title: line.length > 72 ? `${line.slice(0, 69)}...` : line,
description: line,
goalType: inferGoalType(line),
}));
}
function getDefaultPlanItems(): ParsedPlanItem[] {
const defaults = [
"Complete 3 strength sessions this week with progressive overload.",
"Add 2 cardio sessions of 25-30 minutes for endurance.",
"Do a 10-minute mobility routine daily after training.",
];
return defaults.map((line) => ({
id: crypto.randomUUID(),
title: line.length > 72 ? `${line.slice(0, 69)}...` : line,
description: line,
goalType: inferGoalType(line),
}));
}
function parseJsonPayload(content: string): GeneratedPlanContent {
let cleanResponse = content.trim();
if (cleanResponse.startsWith("```json")) {
cleanResponse = cleanResponse
.replace(/^```json\s*/, "")
.replace(/\s*```$/, "");
} else if (cleanResponse.startsWith("```")) {
cleanResponse = cleanResponse.replace(/^```\s*/, "").replace(/\s*```$/, "");
}
const firstBrace = cleanResponse.indexOf("{");
const lastBrace = cleanResponse.lastIndexOf("}");
if (firstBrace !== -1 && lastBrace !== -1) {
cleanResponse = cleanResponse.substring(firstBrace, lastBrace + 1);
}
return JSON.parse(cleanResponse) as GeneratedPlanContent;
}
async function generateWithOpenAI(
openaiApiKey: string,
prompt: string,
): Promise<GeneratedPlanContent> {
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${openaiApiKey}`,
},
body: JSON.stringify({
model: "gpt-4o-mini",
messages: [
{
role: "system",
content:
'You are a professional fitness trainer and nutritionist. Always respond with valid JSON only, no markdown or code blocks. The response must have this exact structure: {"recommendationText": "string", "activityPlan": "string", "dietPlan": "string"}',
},
{
role: "user",
content: prompt,
},
],
temperature: 0.7,
max_tokens: 1500,
response_format: { type: "json_object" },
}),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`OpenAI failed: ${response.status} ${errorText}`);
}
const data = await response.json();
return parseJsonPayload(data.choices[0].message.content as string);
}
async function generateWithDeepSeek(
deepseekApiKey: string,
prompt: string,
): Promise<GeneratedPlanContent> {
const response = await fetch("https://api.deepseek.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${deepseekApiKey}`,
},
body: JSON.stringify({
model: "deepseek-chat",
messages: [
{
role: "system",
content:
"You are a professional fitness trainer and nutritionist. Always respond with valid JSON only, no markdown or code blocks.",
},
{
role: "user",
content: prompt,
},
],
temperature: 0.7,
max_tokens: 1200,
}),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`DeepSeek failed: ${response.status} ${errorText}`);
}
const data = await response.json();
return parseJsonPayload(data.choices[0].message.content as string);
}
async function generateWithOllama(
prompt: string,
): Promise<GeneratedPlanContent> {
const response = await fetch("http://localhost:11434/api/generate", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gemma3:latest",
prompt,
stream: false,
format: "json",
}),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Ollama failed: ${response.status} ${errorText}`);
}
const data = await response.json();
return parseJsonPayload(data.response as string);
}
export async function POST() {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const db = await getDatabase();
const currentUser = await ensureUserSynced(userId, db);
if (!currentUser) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const { membershipType, features } = await getUserMembershipContext(userId);
if (membershipType === "basic") {
return NextResponse.json(
{
error:
"AI plan generation is available on Premium and VIP memberships",
membershipType,
},
{ status: 403 },
);
}
if (features.recommendationsPerMonth > 0) {
const currentMonth = new Date();
const monthStart = new Date(
currentMonth.getFullYear(),
currentMonth.getMonth(),
1,
);
const monthEnd = new Date(
currentMonth.getFullYear(),
currentMonth.getMonth() + 1,
1,
);
const existingRecommendations =
await db.getRecommendationsByUserId(userId);
const recommendationsThisMonth = existingRecommendations.filter(
(recommendation) =>
recommendation.generatedAt >= monthStart &&
recommendation.generatedAt < monthEnd,
).length;
if (recommendationsThisMonth >= features.recommendationsPerMonth) {
return NextResponse.json(
{
error: `Your ${membershipType} plan includes ${features.recommendationsPerMonth} AI recommendation(s) per month`,
membershipType,
},
{ status: 403 },
);
}
}
const profile = await db.getFitnessProfileByUserId(userId);
if (!profile) {
return NextResponse.json(
{ error: "Complete your fitness profile before generating a plan" },
{ status: 404 },
);
}
let prompt: string;
try {
const context = await buildAIContext(userId);
prompt = buildEnhancedPrompt(context);
} catch (error) {
log.warn("Failed to build AI context for self-generate", {
userId,
error: error instanceof Error ? error.message : String(error),
});
prompt = buildBasicPrompt(profile);
}
const openaiApiKey = process.env.OPENAI_API_KEY;
const deepseekApiKey = process.env.DEEPSEEK_API_KEY;
let parsedResponse: GeneratedPlanContent;
let usedFallbackPlan = false;
try {
if (openaiApiKey) {
parsedResponse = await generateWithOpenAI(openaiApiKey, prompt);
} else if (deepseekApiKey) {
parsedResponse = await generateWithDeepSeek(deepseekApiKey, prompt);
} else {
parsedResponse = await generateWithOllama(prompt);
}
} catch (providerError) {
log.error("Self-generate provider failed", providerError, {
userId,
hasOpenAI: Boolean(openaiApiKey),
hasDeepSeek: Boolean(deepseekApiKey),
});
parsedResponse = buildFallbackPlan({
activityLevel: profile.activityLevel,
fitnessGoals: profile.fitnessGoals,
medicalConditions: profile.medicalConditions,
});
usedFallbackPlan = true;
}
const recommendation = await db.createRecommendation({
id: crypto.randomUUID(),
userId,
fitnessProfileId: profile.id,
recommendationText: parsedResponse.recommendationText || "",
activityPlan: parsedResponse.activityPlan || "",
dietPlan: parsedResponse.dietPlan || "",
status: "approved",
generatedAt: new Date(),
updatedAt: new Date(),
});
const existingActiveGoals = await db.getFitnessGoalsByUserId(
userId,
"active",
);
const linkedGoals = existingActiveGoals.filter((goal) =>
goal.notes?.startsWith(AI_LINK_PREFIX),
);
await Promise.all(
linkedGoals.map((goal) =>
db.updateFitnessGoal(goal.id, {
status: "paused",
notes: `${goal.notes || ""}\nPaused due to new AI plan generation on ${new Date().toISOString()}`,
}),
),
);
let planItems = parseActivityPlanToItems(parsedResponse.activityPlan || "");
if (planItems.length === 0 && parsedResponse.recommendationText) {
planItems = parseActivityPlanToItems(parsedResponse.recommendationText);
}
if (planItems.length === 0) {
planItems = getDefaultPlanItems();
}
log.debug("AI plan parsed into goal items", {
recommendationId: recommendation.id,
userId,
parsedItems: planItems.length,
});
const createdGoals = await Promise.all(
planItems.map((item) =>
db.createFitnessGoal({
id: crypto.randomUUID(),
userId,
fitnessProfileId: profile.id,
goalType: item.goalType,
title: item.title,
description: item.description,
targetValue: undefined,
currentValue: 0,
unit: undefined,
startDate: new Date(),
targetDate: undefined,
completedDate: undefined,
status: "active",
progress: 0,
priority: "medium",
notes: `${AI_LINK_PREFIX} recommendationId=${recommendation.id}; itemId=${item.id}`,
}),
),
);
return NextResponse.json({
success: true,
data: recommendation,
meta: {
timestamp: new Date().toISOString(),
createdGoals: createdGoals.length,
pausedGoals: linkedGoals.length,
usedFallbackPlan,
},
});
} catch (error) {
log.error("Failed to self-generate recommendation", error);
return NextResponse.json(
{ error: "Internal server error" },
{ status: 500 },
);
}
}
@@ -0,0 +1,85 @@
/**
* @jest-environment node
*/
import { POST } from "../route";
jest.mock("@clerk/nextjs/server", () => ({
auth: jest.fn(),
}));
jest.mock("@/lib/database", () => ({
getDatabase: jest.fn(),
}));
jest.mock("@/lib/sync-user", () => ({
ensureUserSynced: jest.fn(),
}));
jest.mock("@/lib/ai/ai-context", () => ({
buildAIContext: jest.fn(),
}));
jest.mock("@/lib/ai/prompt-builder", () => ({
buildEnhancedPrompt: jest.fn(),
buildBasicPrompt: jest.fn(),
}));
jest.mock("@/lib/logger", () => ({
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
debug: jest.fn(),
}));
describe("POST /api/recommendations/generate authz", () => {
const mockAuth = require("@clerk/nextjs/server").auth as jest.Mock;
const mockGetDatabase = require("@/lib/database").getDatabase as jest.Mock;
const mockEnsureUserSynced = require("@/lib/sync-user")
.ensureUserSynced as jest.Mock;
const mockDb = {
getUserById: jest.fn(),
getFitnessProfileByUserId: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
mockGetDatabase.mockResolvedValue(mockDb);
});
it("returns 401 when unauthenticated", async () => {
mockAuth.mockResolvedValue({ userId: null });
const req = new Request("http://localhost/api/recommendations/generate", {
method: "POST",
body: JSON.stringify({ userId: "client_1" }),
headers: { "Content-Type": "application/json" },
});
const res = await POST(req);
expect(res.status).toBe(401);
});
it("returns 403 when staff accesses user from another gym", async () => {
mockAuth.mockResolvedValue({ userId: "admin_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "admin_1",
role: "admin",
gymId: "gym_a",
});
mockDb.getUserById.mockResolvedValue({
id: "client_1",
gymId: "gym_b",
});
const req = new Request("http://localhost/api/recommendations/generate", {
method: "POST",
body: JSON.stringify({ userId: "client_1" }),
headers: { "Content-Type": "application/json" },
});
const res = await POST(req);
expect(res.status).toBe(403);
});
});
@@ -1,11 +1,19 @@
import { NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { getDatabase } from "@/lib/database";
import { buildAIContext } from "@/lib/ai/ai-context";
import { buildEnhancedPrompt, buildBasicPrompt } from "@/lib/ai/prompt-builder";
import log from "@/lib/logger";
import { ensureUserSynced } from "@/lib/sync-user";
import { getUserMembershipContext } from "@/lib/membership/access";
export async function POST(req: Request) {
try {
const { userId: clerkUserId } = await auth();
if (!clerkUserId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const { userId, useExternalModel, modelProvider } = await req.json();
if (!userId) {
@@ -22,6 +30,69 @@ export async function POST(req: Request) {
});
const db = await getDatabase();
const currentUser = await ensureUserSynced(clerkUserId, db);
if (!currentUser) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const canGenerateRecommendations =
currentUser.role === "superAdmin" ||
currentUser.role === "admin" ||
currentUser.role === "trainer";
if (!canGenerateRecommendations) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const targetUser = await db.getUserById(userId);
if (!targetUser) {
return NextResponse.json({ error: "User not found" }, { status: 404 });
}
const { membershipType, features } = await getUserMembershipContext(userId);
if (features.recommendationsPerMonth === 1) {
const currentMonth = new Date();
const monthStart = new Date(
currentMonth.getFullYear(),
currentMonth.getMonth(),
1,
);
const monthEnd = new Date(
currentMonth.getFullYear(),
currentMonth.getMonth() + 1,
1,
);
const existingRecommendations =
await db.getRecommendationsByUserId(userId);
const recommendationsThisMonth = existingRecommendations.filter(
(recommendation) =>
recommendation.generatedAt >= monthStart &&
recommendation.generatedAt < monthEnd,
).length;
if (recommendationsThisMonth >= 1) {
return NextResponse.json(
{
error:
"Basic membership includes 1 recommendation per month. Upgrade to Premium or VIP for unlimited recommendations.",
membershipType,
},
{ status: 403 },
);
}
}
if (currentUser.role !== "superAdmin") {
if (!currentUser.gymId || targetUser.gymId !== currentUser.gymId) {
return NextResponse.json(
{ error: "Forbidden - Cannot access users from other gyms" },
{ status: 403 },
);
}
}
// Fetch fitness profile
const profile = await db.getFitnessProfileByUserId(userId);
@@ -106,7 +106,7 @@ export async function POST(request: NextRequest) {
}
const db = await getDatabase();
const currentUser = await db.getUserById(currentUserId);
const currentUser = await ensureUserSynced(currentUserId, db);
const isStaff =
currentUser?.role === "admin" ||
currentUser?.role === "superAdmin" ||
@@ -140,6 +140,18 @@ export async function POST(request: NextRequest) {
content,
} = validation.data;
const targetUser = await db.getUserById(userId);
if (!targetUser) {
return badRequestResponse("Target user not found");
}
if (
currentUser?.role !== "superAdmin" &&
(!currentUser?.gymId || targetUser.gymId !== currentUser.gymId)
) {
return forbiddenResponse("Cannot create recommendations for other gyms");
}
// Handle AI Plan (Legacy/Specific)
if (recommendationText && activityPlan && dietPlan && fitnessProfileId) {
const recommendation = await db.createRecommendation({
@@ -198,6 +210,41 @@ export async function PUT(request: NextRequest) {
validation.data;
const db = await getDatabase();
const currentUser = await ensureUserSynced(currentUserId, db);
if (!currentUser) {
return forbiddenResponse("User not found");
}
const isStaff =
currentUser.role === "admin" ||
currentUser.role === "superAdmin" ||
currentUser.role === "trainer";
if (!isStaff) {
return forbiddenResponse();
}
const existingRecommendation = (await db.getAllRecommendations()).find(
(recommendation) => recommendation.id === id,
);
if (!existingRecommendation) {
return badRequestResponse("Recommendation not found");
}
if (currentUser.role !== "superAdmin") {
const targetUser = await db.getUserById(existingRecommendation.userId);
if (
!currentUser.gymId ||
!targetUser ||
targetUser.gymId !== currentUser.gymId
) {
return forbiddenResponse(
"Cannot modify recommendations for other gyms",
);
}
}
const updated = await db.updateRecommendation(id, {
...(status && { status }),
@@ -35,36 +35,37 @@ export async function DELETE(
const { id } = await params;
// Try to find the assignment by checking trainer's assignments first
const trainerAssignments = await db.getTrainerClientAssignments(
currentUser.id,
);
let assignment = trainerAssignments.find((a) => a.id === id);
const allAssignments = await db.getAllTrainerClientAssignments();
const assignment = allAssignments.find((a) => a.id === id);
if (!assignment) {
// Check all assignments to find the one with this ID
const allAssignments = await db.getAllTrainerClientAssignments();
assignment = allAssignments.find((a) => a.id === id);
return NextResponse.json(
{ error: "Assignment not found" },
{ status: 404 },
);
}
if (!assignment) {
return NextResponse.json(
{ error: "Assignment not found" },
{ status: 404 },
);
if (currentUser.role !== "superAdmin") {
if (!currentUser.gymId) {
return NextResponse.json({ error: "No gym assigned" }, { status: 403 });
}
// Deactivate the assignment
await db.deactivateTrainerClientAssignment(id);
const [trainer, client] = await Promise.all([
db.getUserById(assignment.trainerId),
db.getUserById(assignment.clientId),
]);
log.info("Trainer-client assignment deactivated", {
assignmentId: id,
deactivatedBy: currentUser.id,
});
return NextResponse.json({
success: true,
message: "Assignment deactivated successfully",
});
if (
!trainer ||
!client ||
trainer.gymId !== currentUser.gymId ||
client.gymId !== currentUser.gymId
) {
return NextResponse.json(
{ error: "Cannot modify assignments from other gyms" },
{ status: 403 },
);
}
}
// Deactivate the assignment
@@ -0,0 +1,89 @@
/**
* @jest-environment node
*/
import { NextRequest } from "next/server";
import { GET, POST } from "../route";
jest.mock("@clerk/nextjs/server", () => ({
auth: jest.fn(),
}));
jest.mock("@/lib/database", () => ({
getDatabase: jest.fn(),
}));
jest.mock("@/lib/sync-user", () => ({
ensureUserSynced: jest.fn(),
}));
jest.mock("@/lib/logger", () => ({
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
debug: jest.fn(),
}));
describe("/api/trainer-client authz", () => {
const mockAuth = require("@clerk/nextjs/server").auth as jest.Mock;
const mockGetDatabase = require("@/lib/database").getDatabase as jest.Mock;
const mockEnsureUserSynced = require("@/lib/sync-user")
.ensureUserSynced as jest.Mock;
const mockDb = {
getUserById: jest.fn(),
getTrainerClientAssignments: jest.fn(),
getAllTrainerClientAssignments: jest.fn(),
createTrainerClientAssignment: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
mockGetDatabase.mockResolvedValue(mockDb);
});
it("blocks non-admin users from listing assignments", async () => {
mockAuth.mockResolvedValue({ userId: "trainer_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "trainer_1",
role: "trainer",
gymId: "gym_a",
});
const request = new NextRequest("http://localhost/api/trainer-client");
const response = await GET(request);
expect(response.status).toBe(403);
});
it("blocks admins from creating assignments across gyms", async () => {
mockAuth.mockResolvedValue({ userId: "admin_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "admin_1",
role: "admin",
gymId: "gym_a",
});
mockDb.getUserById
.mockResolvedValueOnce({
id: "trainer_2",
role: "trainer",
gymId: "gym_b",
})
.mockResolvedValueOnce({
id: "client_2",
role: "client",
gymId: "gym_b",
});
const request = new NextRequest("http://localhost/api/trainer-client", {
method: "POST",
body: JSON.stringify({ trainerId: "trainer_2", clientId: "client_2" }),
});
const response = await POST(request);
expect(response.status).toBe(403);
expect(mockDb.createTrainerClientAssignment).not.toHaveBeenCalled();
});
});
+114 -9
View File
@@ -38,17 +38,110 @@ export async function GET(request: NextRequest) {
const trainerId = searchParams.get("trainerId");
const clientId = searchParams.get("clientId");
let assignments;
if (trainerId && clientId) {
const [trainer, client] = await Promise.all([
db.getUserById(trainerId),
db.getUserById(clientId),
]);
if (trainerId) {
assignments = await db.getTrainerClientAssignments(trainerId);
// Filter by clientId if provided
if (clientId) {
assignments = assignments.filter((a) => a.clientId === clientId);
if (!trainer || !client) {
return NextResponse.json({ error: "User not found" }, { status: 404 });
}
} else {
// Get all assignments (for admins, filtered by gym)
assignments = await db.getAllTrainerClientAssignments();
if (
currentUser.role !== "superAdmin" &&
(!currentUser.gymId ||
trainer.gymId !== currentUser.gymId ||
client.gymId !== currentUser.gymId)
) {
return NextResponse.json(
{ error: "Cannot access assignments from other gyms" },
{ status: 403 },
);
}
}
if (trainerId && !clientId) {
const trainer = await db.getUserById(trainerId);
if (!trainer) {
return NextResponse.json(
{ error: "Trainer not found" },
{ status: 404 },
);
}
if (
currentUser.role !== "superAdmin" &&
(!currentUser.gymId || trainer.gymId !== currentUser.gymId)
) {
return NextResponse.json(
{ error: "Cannot access assignments from other gyms" },
{ status: 403 },
);
}
}
if (!trainerId && clientId) {
const client = await db.getUserById(clientId);
if (!client) {
return NextResponse.json(
{ error: "Client not found" },
{ status: 404 },
);
}
if (
currentUser.role !== "superAdmin" &&
(!currentUser.gymId || client.gymId !== currentUser.gymId)
) {
return NextResponse.json(
{ error: "Cannot access assignments from other gyms" },
{ status: 403 },
);
}
}
let assignments = trainerId
? await db.getTrainerClientAssignments(trainerId)
: await db.getAllTrainerClientAssignments();
if (clientId) {
assignments = assignments.filter((a) => a.clientId === clientId);
}
if (currentUser.role !== "superAdmin") {
if (!currentUser.gymId) {
return NextResponse.json({ error: "No gym assigned" }, { status: 403 });
}
const involvedUserIds = Array.from(
new Set(
assignments.flatMap((assignment) => [
assignment.trainerId,
assignment.clientId,
]),
),
);
const users = await Promise.all(
involvedUserIds.map((userId) => db.getUserById(userId)),
);
const gymByUserId = new Map(
users
.filter((user): user is NonNullable<typeof user> => !!user)
.map((user) => [user.id, user.gymId]),
);
assignments = assignments.filter((assignment) => {
const trainerGymId = gymByUserId.get(assignment.trainerId);
const clientGymId = gymByUserId.get(assignment.clientId);
return (
trainerGymId === currentUser.gymId &&
clientGymId === currentUser.gymId
);
});
}
return NextResponse.json({ assignments });
@@ -121,6 +214,18 @@ export async function POST(request: NextRequest) {
);
}
if (
currentUser.role !== "superAdmin" &&
(!currentUser.gymId ||
trainer.gymId !== currentUser.gymId ||
client.gymId !== currentUser.gymId)
) {
return NextResponse.json(
{ error: "Cannot assign users from other gyms" },
{ status: 403 },
);
}
// Check if assignment already exists
const existingAssignments = await db.getTrainerClientAssignments(trainerId);
const existingAssignment = existingAssignments.find(
@@ -0,0 +1,109 @@
/**
* @jest-environment node
*/
import { NextRequest } from "next/server";
import { DELETE } from "../route";
jest.mock("@clerk/nextjs/server", () => ({
auth: jest.fn(),
clerkClient: jest.fn(),
}));
jest.mock("@/lib/database/index", () => ({
getDatabase: jest.fn(),
}));
jest.mock("@/lib/sync-user", () => ({
ensureUserSynced: jest.fn(),
}));
jest.mock("@fitai/database", () => ({
db: {
all: jest.fn(),
get: jest.fn(),
run: jest.fn(),
},
sql: jest.fn((strings: TemplateStringsArray) => strings.join("")),
}));
jest.mock("@/lib/logger", () => ({
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
debug: jest.fn(),
}));
describe("DELETE /api/users authz", () => {
const mockAuth = require("@clerk/nextjs/server").auth as jest.Mock;
const mockGetDatabase = require("@/lib/database/index")
.getDatabase as jest.Mock;
const mockEnsureUserSynced = require("@/lib/sync-user")
.ensureUserSynced as jest.Mock;
const mockDb = {
getUserById: jest.fn(),
deleteUser: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
mockGetDatabase.mockResolvedValue(mockDb);
});
it("blocks self deletion", async () => {
mockAuth.mockResolvedValue({ userId: "admin_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "admin_1",
role: "admin",
gymId: "gym_a",
});
const request = new NextRequest("http://localhost/api/users?id=admin_1", {
method: "DELETE",
});
const response = await DELETE(request);
expect(response.status).toBe(403);
expect(mockDb.deleteUser).not.toHaveBeenCalled();
});
it("blocks cross-gym deletion for admin", async () => {
mockAuth.mockResolvedValue({ userId: "admin_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "admin_1",
role: "admin",
gymId: "gym_a",
});
mockDb.getUserById.mockResolvedValue({ id: "user_2", gymId: "gym_b" });
const request = new NextRequest("http://localhost/api/users?id=user_2", {
method: "DELETE",
});
const response = await DELETE(request);
expect(response.status).toBe(403);
expect(mockDb.deleteUser).not.toHaveBeenCalled();
});
it("allows superAdmin cross-gym deletion", async () => {
mockAuth.mockResolvedValue({ userId: "super_1" });
mockEnsureUserSynced.mockResolvedValue({
id: "super_1",
role: "superAdmin",
gymId: null,
});
mockDb.getUserById.mockResolvedValue({ id: "user_2", gymId: "gym_b" });
const request = new NextRequest("http://localhost/api/users?id=user_2", {
method: "DELETE",
});
const response = await DELETE(request);
const payload = await response.json();
expect(response.status).toBe(200);
expect(payload.success).toBe(true);
expect(mockDb.deleteUser).toHaveBeenCalledWith("user_2");
});
});
+70
View File
@@ -1,8 +1,78 @@
import { NextResponse } from "next/server";
import { auth } from "@clerk/nextjs/server";
import { db, users as usersTable, eq, sql } from "@fitai/database";
import { ensureGymsGeofenceColumns } from "@/lib/geofence";
import log from "@/lib/logger";
export async function GET() {
try {
const { userId } = await auth();
if (!userId) return new NextResponse("Unauthorized", { status: 401 });
const user = await db
.select()
.from(usersTable)
.where(eq(usersTable.id, userId))
.get();
if (!user) {
return new NextResponse("User not found", { status: 404 });
}
if (!user.gymId) {
return NextResponse.json({ gymId: null, gym: null });
}
await ensureGymsGeofenceColumns();
const rows = await db.all(sql`
SELECT
id,
name,
location,
latitude,
longitude,
geofence_radius_meters as geofenceRadiusMeters,
geofence_enabled as geofenceEnabled,
status
FROM gyms
WHERE id = ${user.gymId}
LIMIT 1
`);
const gym = rows?.[0] as
| {
id: string;
name: string;
location: string | null;
latitude: number | null;
longitude: number | null;
geofenceRadiusMeters: number | null;
geofenceEnabled: number | boolean | null;
status: "active" | "inactive";
}
| undefined;
if (!gym || gym.status !== "active") {
return NextResponse.json({ gymId: user.gymId, gym: null });
}
return NextResponse.json({
gymId: user.gymId,
gym: {
...gym,
geofenceEnabled:
typeof gym.geofenceEnabled === "boolean"
? gym.geofenceEnabled
: Boolean(gym.geofenceEnabled),
},
});
} catch (error) {
log.error("Failed to fetch current user gym", error);
return new NextResponse("Internal Server Error", { status: 500 });
}
}
/**
* PATCH /api/users/gym
* Body: { gymId: string | null }
+59 -9
View File
@@ -580,26 +580,76 @@ export async function PUT(request: NextRequest) {
export async function DELETE(request: NextRequest) {
try {
const { userId: clerkUserId } = await auth();
if (!clerkUserId) {
return unauthorizedResponse();
}
const db = await getDatabase();
const currentUser = await ensureUserSynced(clerkUserId, db);
if (!currentUser) {
return forbiddenResponse("Current user not found");
}
const canDeleteUsers =
currentUser.role === "admin" || currentUser.role === "superAdmin";
if (!canDeleteUsers) {
return forbiddenResponse("Only admins can delete users");
}
const { searchParams } = new URL(request.url);
const id = searchParams.get("id");
const body = await request.json().catch(() => ({}));
const { ids } = body;
const targetIds: string[] = Array.isArray(ids)
? ids.filter(
(userId: unknown): userId is string => typeof userId === "string",
)
: id
? [id]
: [];
if (targetIds.length === 0) {
return badRequestResponse("User ID or IDs array required");
}
if (targetIds.includes(clerkUserId)) {
return forbiddenResponse("Cannot delete your own account");
}
const targetUsers = await Promise.all(
targetIds.map((targetId) => db.getUserById(targetId)),
);
const missingTargets = targetUsers.filter((user) => !user).length;
if (missingTargets > 0) {
return notFoundResponse("One or more users were not found");
}
if (currentUser.role !== "superAdmin") {
if (!currentUser.gymId) {
return forbiddenResponse("No gym assigned to current user");
}
const hasCrossGymTarget = targetUsers.some(
(targetUser) => targetUser && targetUser.gymId !== currentUser.gymId,
);
if (hasCrossGymTarget) {
return forbiddenResponse("Cannot delete users from other gyms");
}
}
if (ids && Array.isArray(ids)) {
// Bulk delete
await Promise.all(ids.map((userId: string) => db.deleteUser(userId)));
return successResponse({ deleted: ids.length });
} else if (id) {
// Single delete
const user = await db.getUserById(id);
if (!user) {
return notFoundResponse("User not found");
}
await db.deleteUser(id);
return successResponse({ deleted: 1 });
} else {
return badRequestResponse("User ID or IDs array required");
await db.deleteUser(id as string);
return successResponse({ deleted: 1 });
}
} catch (error) {
log.error("Failed to delete user(s)", error);
+637
View File
@@ -0,0 +1,637 @@
"use client";
import { useAuth } from "@clerk/nextjs";
import { useEffect, useMemo, useRef, useState } from "react";
import { io, type Socket } from "socket.io-client";
import axios from "axios";
import { PageHeader } from "@/components/ui/PageHeader";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
interface ChatThread {
id: string;
type: "gym" | "dm";
unreadCount: number;
lastMessageBody: string | null;
lastMessageAt: string | null;
}
interface ChatMessage {
id: string;
threadId: string;
senderUserId: string;
body: string;
createdAt: string;
clientMessageId: string | null;
kind?: "text" | "system";
attachments?: Array<{ url: string; type: string; name?: string }>;
editedAt?: string | null;
deletedAt?: string | null;
}
interface ReadEvent {
threadId?: string;
userId?: string;
lastReadMessageId?: string | null;
}
export default function AdminChatPage() {
const { getToken, userId } = useAuth();
const [threads, setThreads] = useState<ChatThread[]>([]);
const [activeThreadId, setActiveThreadId] = useState<string | null>(null);
const [messagesByThreadId, setMessagesByThreadId] = useState<
Record<string, ChatMessage[]>
>({});
const [draft, setDraft] = useState("");
const [loadingThreads, setLoadingThreads] = useState(false);
const [loadingMessages, setLoadingMessages] = useState(false);
const [socketConnected, setSocketConnected] = useState(false);
const [typingByThreadId, setTypingByThreadId] = useState<
Record<string, string[]>
>({});
const [readByThreadId, setReadByThreadId] = useState<
Record<string, Record<string, string>>
>({});
const typingTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const socketRef = useRef<Socket | null>(null);
const getTokenRef = useRef(getToken);
const activeThreadIdRef = useRef<string | null>(null);
const subscribedThreadIdsRef = useRef<Set<string>>(new Set());
const loadingThreadsRef = useRef(false);
const loadingMessagesRef = useRef<Record<string, boolean>>({});
const activeMessages = useMemo(
() => (activeThreadId ? (messagesByThreadId[activeThreadId] ?? []) : []),
[activeThreadId, messagesByThreadId],
);
const typingUsers = useMemo(
() => (activeThreadId ? (typingByThreadId[activeThreadId] ?? []) : []),
[activeThreadId, typingByThreadId],
);
useEffect(() => {
getTokenRef.current = getToken;
}, [getToken]);
useEffect(() => {
activeThreadIdRef.current = activeThreadId;
}, [activeThreadId]);
const loadThreads = async (showLoader = true) => {
if (loadingThreadsRef.current) {
return;
}
const token = await getTokenRef.current();
if (!token) {
return;
}
loadingThreadsRef.current = true;
try {
if (showLoader) {
setLoadingThreads(true);
}
const response = await axios.get<{ threads: ChatThread[] }>(
"/api/chat/threads",
{
headers: { Authorization: `Bearer ${token}` },
},
);
setThreads(response.data.threads);
setActiveThreadId((prev) => prev ?? response.data.threads[0]?.id ?? null);
setReadByThreadId({});
} finally {
if (showLoader) {
setLoadingThreads(false);
}
loadingThreadsRef.current = false;
}
};
const loadMessages = async (threadId: string, showLoader = true) => {
if (loadingMessagesRef.current[threadId]) {
return;
}
const token = await getTokenRef.current();
if (!token) {
return;
}
loadingMessagesRef.current[threadId] = true;
try {
if (showLoader) {
setLoadingMessages(true);
}
const response = await axios.get<{ messages: ChatMessage[] }>(
`/api/chat/threads/${threadId}/messages`,
{
headers: { Authorization: `Bearer ${token}` },
},
);
setMessagesByThreadId((prev) => ({
...prev,
[threadId]: response.data.messages,
}));
} finally {
if (showLoader) {
setLoadingMessages(false);
}
loadingMessagesRef.current[threadId] = false;
}
};
useEffect(() => {
void loadThreads(true);
}, []);
useEffect(() => {
if (!activeThreadId) {
return;
}
void loadMessages(activeThreadId, true);
}, [activeThreadId]);
useEffect(() => {
let mounted = true;
const setupSocket = async () => {
const token = await getTokenRef.current();
if (!token || !mounted) {
return;
}
const realtimeBase =
process.env.NEXT_PUBLIC_REALTIME_URL ?? window.location.origin;
const socket = io(realtimeBase, {
path: process.env.NEXT_PUBLIC_REALTIME_PATH ?? "/socket.io",
transports: ["websocket"],
auth: { token },
});
socketRef.current = socket;
socket.on("connect", () => {
setSocketConnected(true);
subscribedThreadIdsRef.current.clear();
void loadThreads(false);
});
socket.on("disconnect", () => {
setSocketConnected(false);
setTypingByThreadId({});
subscribedThreadIdsRef.current.clear();
});
socket.on(
"chat:message:new",
(event: { threadId: string; message: ChatMessage }) => {
setMessagesByThreadId((prev) => {
const current = prev[event.threadId] ?? [];
if (current.some((message) => message.id === event.message.id)) {
return prev;
}
return {
...prev,
[event.threadId]: [...current, event.message],
};
});
setThreads((prev) =>
prev
.map((thread) =>
thread.id === event.threadId
? {
...thread,
lastMessageAt: event.message.createdAt,
lastMessageBody: event.message.body,
unreadCount:
event.threadId === activeThreadIdRef.current ||
event.message.senderUserId === userId
? thread.unreadCount
: thread.unreadCount + 1,
}
: thread,
)
.sort((a, b) => {
const aTime = a.lastMessageAt ?? "1970-01-01T00:00:00.000Z";
const bTime = b.lastMessageAt ?? "1970-01-01T00:00:00.000Z";
return new Date(bTime).getTime() - new Date(aTime).getTime();
}),
);
},
);
socket.on(
"chat:message:ack",
(event: {
threadId: string;
message: ChatMessage;
clientMessageId?: string | null;
}) => {
setMessagesByThreadId((prev) => ({
...prev,
[event.threadId]: (prev[event.threadId] ?? []).map((message) =>
event.clientMessageId
? message.clientMessageId === event.clientMessageId
? event.message
: message
: message.id === event.message.id
? event.message
: message,
),
}));
},
);
socket.on(
"chat:typing",
(event: { threadId?: string; userId?: string; isTyping?: boolean }) => {
if (!event.threadId || !event.userId || event.userId === userId) {
return;
}
setTypingByThreadId((prev) => {
const current = prev[event.threadId!] ?? [];
if (event.isTyping) {
if (current.includes(event.userId!)) {
return prev;
}
return {
...prev,
[event.threadId!]: [...current, event.userId!],
};
}
return {
...prev,
[event.threadId!]: current.filter((id) => id !== event.userId),
};
});
},
);
socket.on("chat:read:update", (event: ReadEvent) => {
if (!event.threadId || !event.userId || !event.lastReadMessageId) {
return;
}
setReadByThreadId((prev) => ({
...prev,
[event.threadId!]: {
...(prev[event.threadId!] ?? {}),
[event.userId!]: event.lastReadMessageId!,
},
}));
void loadThreads(false);
});
socket.on("chat:error", (event: { code?: string; message?: string }) => {
if (event.code === "rate_limited") {
console.warn(event.message ?? "Chat action rate-limited");
}
});
};
void setupSocket();
return () => {
mounted = false;
if (typingTimeoutRef.current) {
clearTimeout(typingTimeoutRef.current);
}
if (socketRef.current) {
socketRef.current.disconnect();
socketRef.current = null;
}
};
}, [userId]);
useEffect(() => {
if (!socketRef.current || !socketConnected) {
return;
}
const socket = socketRef.current;
const nextIds = new Set(threads.map((thread) => thread.id));
threads.forEach((thread) => {
if (!subscribedThreadIdsRef.current.has(thread.id)) {
socket.emit("chat:subscribe", { threadId: thread.id });
subscribedThreadIdsRef.current.add(thread.id);
}
});
Array.from(subscribedThreadIdsRef.current).forEach((threadId) => {
if (!nextIds.has(threadId)) {
socket.emit("chat:unsubscribe", { threadId });
subscribedThreadIdsRef.current.delete(threadId);
}
});
}, [socketConnected, threads]);
useEffect(() => {
if (!activeThreadId) {
return;
}
setThreads((prev) =>
prev.map((thread) =>
thread.id === activeThreadId ? { ...thread, unreadCount: 0 } : thread,
),
);
}, [activeThreadId]);
useEffect(() => {
if (!socketConnected) {
const interval = setInterval(() => {
void loadThreads(false);
if (activeThreadIdRef.current) {
void loadMessages(activeThreadIdRef.current, false);
}
}, 5000);
return () => {
clearInterval(interval);
};
}
}, [socketConnected]);
useEffect(() => {
if (!activeThreadId) {
return;
}
const messages = messagesByThreadId[activeThreadId] ?? [];
const lastMessage = messages[messages.length - 1];
if (!lastMessage || lastMessage.senderUserId === userId) {
return;
}
const markRead = async () => {
const token = await getTokenRef.current();
if (!token) {
return;
}
await axios.post(
`/api/chat/threads/${activeThreadId}/read`,
{ lastReadMessageId: lastMessage.id },
{
headers: { Authorization: `Bearer ${token}` },
},
);
if (socketRef.current && socketConnected) {
socketRef.current.emit("chat:read", {
threadId: activeThreadId,
lastReadMessageId: lastMessage.id,
});
}
if (userId) {
setReadByThreadId((prev) => ({
...prev,
[activeThreadId]: {
...(prev[activeThreadId] ?? {}),
[userId]: lastMessage.id,
},
}));
}
};
void markRead();
}, [activeThreadId, messagesByThreadId, socketConnected, userId]);
const onChangeDraft = (value: string) => {
setDraft(value);
if (!activeThreadId || !socketRef.current || !socketConnected) {
return;
}
const isTyping = value.trim().length > 0;
socketRef.current.emit("chat:typing", {
threadId: activeThreadId,
isTyping,
});
if (typingTimeoutRef.current) {
clearTimeout(typingTimeoutRef.current);
}
typingTimeoutRef.current = setTimeout(() => {
if (activeThreadId && socketRef.current) {
socketRef.current.emit("chat:typing", {
threadId: activeThreadId,
isTyping: false,
});
}
}, 1200);
};
const onSend = async () => {
if (!activeThreadId) {
return;
}
const body = draft.trim();
if (!body) {
return;
}
const clientMessageId = `admin_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
const optimistic: ChatMessage = {
id: clientMessageId,
threadId: activeThreadId,
senderUserId: userId ?? "",
body,
createdAt: new Date().toISOString(),
clientMessageId,
kind: "text",
attachments: [],
editedAt: null,
deletedAt: null,
};
setMessagesByThreadId((prev) => ({
...prev,
[activeThreadId]: [...(prev[activeThreadId] ?? []), optimistic],
}));
setDraft("");
if (socketRef.current && socketConnected) {
socketRef.current.emit("chat:send", {
threadId: activeThreadId,
body,
clientMessageId,
});
return;
}
const token = await getTokenRef.current();
if (!token) {
return;
}
try {
const response = await axios.post<{ message: ChatMessage }>(
`/api/chat/threads/${activeThreadId}/messages`,
{ body, clientMessageId },
{
headers: { Authorization: `Bearer ${token}` },
},
);
setMessagesByThreadId((prev) => ({
...prev,
[activeThreadId]: (prev[activeThreadId] ?? []).map((message) =>
message.id === clientMessageId ? response.data.message : message,
),
}));
} catch {
setMessagesByThreadId((prev) => ({
...prev,
[activeThreadId]: (prev[activeThreadId] ?? []).filter(
(message) => message.id !== clientMessageId,
),
}));
}
};
return (
<div className="space-y-6">
<PageHeader
title="Chat"
description="Gym room and private trainer-client messaging"
breadcrumbs={[{ label: "Chat" }]}
/>
<div className="text-sm text-slate-500">
{socketConnected ? "Realtime connected" : "Realtime disconnected"}
</div>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-[320px_1fr]">
<Card>
<CardHeader>
<CardTitle>Threads</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
{loadingThreads ? (
<div className="text-sm text-slate-500">Loading threads...</div>
) : (
threads.map((thread) => (
<button
key={thread.id}
type="button"
onClick={() => setActiveThreadId(thread.id)}
className={`w-full rounded-lg border p-3 text-left transition ${
thread.id === activeThreadId
? "border-blue-500 bg-blue-50"
: "border-slate-200 hover:bg-slate-50"
}`}
>
<div className="flex items-center justify-between">
<p className="font-medium text-slate-900">
{thread.type === "gym" ? "Gym Room" : "Trainer DM"}
</p>
{thread.unreadCount > 0 && (
<span className="rounded-full bg-red-500 px-2 py-0.5 text-xs font-semibold text-white">
{thread.unreadCount > 99 ? "99+" : thread.unreadCount}
</span>
)}
</div>
<p className="mt-1 truncate text-sm text-slate-500">
{thread.lastMessageBody ?? "No messages yet"}
</p>
</button>
))
)}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Messages</CardTitle>
</CardHeader>
<CardContent>
{!activeThreadId ? (
<p className="text-sm text-slate-500">
Select a thread to start chatting.
</p>
) : (
<div className="space-y-3">
<div className="max-h-[460px] space-y-2 overflow-y-auto rounded-md border border-slate-200 p-3">
{loadingMessages ? (
<p className="text-sm text-slate-500">
Loading messages...
</p>
) : (
activeMessages.map((message) => {
const own = message.senderUserId === userId;
const threadReads = activeThreadId
? (readByThreadId[activeThreadId] ?? {})
: {};
const seen = Object.entries(threadReads).some(
([readerUserId, readMessageId]) =>
readerUserId !== userId &&
readMessageId === message.id,
);
return (
<div
key={message.id}
className={`max-w-[80%] rounded-lg px-3 py-2 text-sm ${
own
? "ml-auto bg-blue-600 text-white"
: "bg-slate-100 text-slate-900"
}`}
>
<div>{message.body}</div>
{own && seen && (
<div className="mt-1 text-[10px] text-blue-100">
Seen
</div>
)}
</div>
);
})
)}
</div>
{typingUsers.length > 0 && (
<p className="text-xs text-slate-500">
{typingUsers.length === 1
? "Someone is typing..."
: `${typingUsers.length} people are typing...`}
</p>
)}
<div className="flex items-center gap-2">
<Input
value={draft}
onChange={(event) => onChangeDraft(event.target.value)}
placeholder="Type a message"
/>
<Button onClick={onSend}>Send</Button>
</div>
</div>
)}
</CardContent>
</Card>
</div>
</div>
);
}
+279
View File
@@ -17,6 +17,7 @@ import {
import { Button } from "@/components/ui/button";
import { useUser } from "@clerk/nextjs";
import log from "@/lib/logger";
import { MEMBERSHIP_FEATURES } from "@/lib/membership/features";
interface Backup {
name: string;
@@ -28,6 +29,10 @@ interface Gym {
id: string;
name: string;
location?: string | null;
latitude?: number | null;
longitude?: number | null;
geofenceRadiusMeters?: number | null;
geofenceEnabled?: boolean;
status: "active" | "inactive";
adminUserId: string;
createdAt?: number;
@@ -71,6 +76,11 @@ export default function SettingsPage() {
const [gymStats, setGymStats] = useState<GymStats | null>(null);
const [statsLoading, setStatsLoading] = useState(false);
const [deletingGym, setDeletingGym] = useState(false);
const [savingGeofence, setSavingGeofence] = useState(false);
const [geofenceLatitude, setGeofenceLatitude] = useState("");
const [geofenceLongitude, setGeofenceLongitude] = useState("");
const [geofenceRadiusMeters, setGeofenceRadiusMeters] = useState("30");
const [geofenceEnabled, setGeofenceEnabled] = useState(true);
// Create Gym modal state
const [showCreateGym, setShowCreateGym] = useState(false);
@@ -185,6 +195,87 @@ export default function SettingsPage() {
const handleSelectGym = async (gym: Gym | null) => {
setSelectedGym(gym);
setGymStats(null);
if (gym) {
setGeofenceLatitude(
gym.latitude !== null && gym.latitude !== undefined
? String(gym.latitude)
: "",
);
setGeofenceLongitude(
gym.longitude !== null && gym.longitude !== undefined
? String(gym.longitude)
: "",
);
setGeofenceRadiusMeters(String(gym.geofenceRadiusMeters ?? 30));
setGeofenceEnabled(gym.geofenceEnabled ?? true);
}
};
const handleSaveGeofence = async () => {
if (!selectedGym) return;
const latitude =
geofenceLatitude.trim() === "" ? null : Number(geofenceLatitude);
const longitude =
geofenceLongitude.trim() === "" ? null : Number(geofenceLongitude);
const radius = Number(geofenceRadiusMeters);
if (
latitude !== null &&
(!Number.isFinite(latitude) || latitude < -90 || latitude > 90)
) {
setGymMessage({
type: "error",
text: "Latitude must be between -90 and 90",
});
return;
}
if (
longitude !== null &&
(!Number.isFinite(longitude) || longitude < -180 || longitude > 180)
) {
setGymMessage({
type: "error",
text: "Longitude must be between -180 and 180",
});
return;
}
if (!Number.isFinite(radius) || radius <= 0) {
setGymMessage({
type: "error",
text: "Radius must be a positive number",
});
return;
}
setSavingGeofence(true);
setGymMessage(null);
try {
const response = await axios.patch(`/api/gyms/${selectedGym.id}`, {
latitude,
longitude,
geofenceRadiusMeters: radius,
geofenceEnabled,
});
setGymMessage({ type: "success", text: "Geofence settings updated" });
const updatedGym = response.data as Gym;
setSelectedGym(updatedGym);
setGyms((prev) =>
prev.map((gym) => (gym.id === updatedGym.id ? updatedGym : gym)),
);
} catch (error) {
log.error("Failed to update geofence settings", error);
setGymMessage({
type: "error",
text: "Failed to update geofence settings",
});
} finally {
setSavingGeofence(false);
}
};
const handleDeleteGym = async (gymId: string) => {
@@ -474,6 +565,91 @@ export default function SettingsPage() {
{selectedGym.status}
</p>
</div>
<div>
<p className="text-xs text-slate-500">Geofence</p>
<p className="font-medium">
{selectedGym.geofenceEnabled === false
? "Disabled"
: `${selectedGym.geofenceRadiusMeters ?? 30}m`}
</p>
</div>
</div>
{/* Geofence Settings */}
<div className="p-4 border rounded-lg space-y-3">
<div className="flex items-center justify-between">
<h5 className="text-sm font-medium text-slate-700">
Attendance Geofence
</h5>
<label className="inline-flex items-center gap-2 text-sm text-slate-700">
<input
type="checkbox"
checked={geofenceEnabled}
onChange={(e) => setGeofenceEnabled(e.target.checked)}
/>
Enabled
</label>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
<div>
<label className="block text-xs text-slate-500 mb-1">
Latitude
</label>
<input
type="number"
step="any"
value={geofenceLatitude}
onChange={(e) => setGeofenceLatitude(e.target.value)}
className="w-full border border-gray-300 rounded px-3 py-2"
placeholder="e.g. 37.7749"
/>
</div>
<div>
<label className="block text-xs text-slate-500 mb-1">
Longitude
</label>
<input
type="number"
step="any"
value={geofenceLongitude}
onChange={(e) => setGeofenceLongitude(e.target.value)}
className="w-full border border-gray-300 rounded px-3 py-2"
placeholder="e.g. -122.4194"
/>
</div>
<div>
<label className="block text-xs text-slate-500 mb-1">
Radius (meters)
</label>
<input
type="number"
min="1"
value={geofenceRadiusMeters}
onChange={(e) =>
setGeofenceRadiusMeters(e.target.value)
}
className="w-full border border-gray-300 rounded px-3 py-2"
/>
</div>
</div>
<div className="flex items-center justify-between">
<p className="text-xs text-slate-500">
Default radius is 30m and geofence is enabled by default.
</p>
<Button
size="sm"
onClick={handleSaveGeofence}
disabled={savingGeofence}
>
{savingGeofence ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
"Save Geofence"
)}
</Button>
</div>
</div>
{/* Stats */}
@@ -558,6 +734,109 @@ export default function SettingsPage() {
</div>
</div>
)}
{/* Membership Feature Access */}
<div className="mt-6">
<h5 className="text-sm font-medium text-slate-700 mb-2">
Membership Feature Access
</h5>
<div className="overflow-x-auto border rounded-lg">
<table className="w-full text-left text-sm">
<thead className="bg-slate-50 border-b">
<tr>
<th className="px-4 py-3 font-semibold text-slate-900">
Feature
</th>
<th className="px-4 py-3 font-semibold text-slate-900">
Basic
</th>
<th className="px-4 py-3 font-semibold text-slate-900">
Premium
</th>
<th className="px-4 py-3 font-semibold text-slate-900">
VIP
</th>
</tr>
</thead>
<tbody className="divide-y">
<tr>
<td className="px-4 py-3 text-slate-700">
Recommendations per month
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.basic.recommendationsPerMonth}
</td>
<td className="px-4 py-3 text-slate-700">
Unlimited
</td>
<td className="px-4 py-3 text-slate-700">
Unlimited
</td>
</tr>
<tr>
<td className="px-4 py-3 text-slate-700">
Nutrition tracking
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.basic.nutritionTracking
? "Yes"
: "No"}
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.premium.nutritionTracking
? "Yes"
: "No"}
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.vip.nutritionTracking
? "Yes"
: "No"}
</td>
</tr>
<tr>
<td className="px-4 py-3 text-slate-700">
Hydration tracking
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.basic.hydrationTracking
? "Yes"
: "No"}
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.premium.hydrationTracking
? "Yes"
: "No"}
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.vip.hydrationTracking
? "Yes"
: "No"}
</td>
</tr>
<tr>
<td className="px-4 py-3 text-slate-700">
Advanced statistics
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.basic.advancedStatistics
? "Yes"
: "No"}
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.premium.advancedStatistics
? "Yes"
: "No"}
</td>
<td className="px-4 py-3 text-slate-700">
{MEMBERSHIP_FEATURES.vip.advancedStatistics
? "Yes"
: "No"}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
) : (
<div className="flex items-center justify-center h-64 bg-slate-50 rounded-lg">
+2
View File
@@ -12,6 +12,7 @@ import {
Brain,
ChevronLeft,
Activity,
MessageCircle,
} from "lucide-react";
import { UserButton, useUser } from "@clerk/nextjs";
import { usePendingRecommendationsCount } from "@/hooks/use-api";
@@ -52,6 +53,7 @@ export function Sidebar() {
href: "/recommendations",
badge: pendingCount > 0 ? pendingCount : undefined,
},
{ icon: MessageCircle, label: "Chat", href: "/chat" },
{ icon: CalendarCheck, label: "Attendance", href: "/attendance" },
{ icon: CreditCard, label: "Payments", href: "/payments" },
{ icon: Settings, label: "Settings", href: "/settings" },
+824
View File
@@ -0,0 +1,824 @@
import { db, sql } from "@fitai/database";
import type { UserRole } from "@fitai/shared";
import log from "@/lib/logger";
export interface ChatUserContext {
id: string;
role: UserRole;
gymId?: string | null;
}
export interface ChatThreadSummary {
id: string;
type: "gym" | "dm";
gymId: string | null;
trainerId: string | null;
clientId: string | null;
createdBy: string;
lastMessageAt: Date | null;
createdAt: Date;
updatedAt: Date;
archivedAt: Date | null;
unreadCount: number;
lastMessageBody: string | null;
}
export interface ChatMessageRecord {
id: string;
threadId: string;
senderUserId: string;
body: string;
kind: "text" | "system";
attachments: Array<{ url: string; type: string; name?: string }>;
clientMessageId: string | null;
createdAt: Date;
editedAt: Date | null;
deletedAt: Date | null;
}
class ChatError extends Error {
status: number;
constructor(message: string, status: number) {
super(message);
this.status = status;
}
}
let tablesEnsured = false;
export async function ensureChatTables(): Promise<void> {
if (tablesEnsured) {
return;
}
await db.run(sql`
CREATE TABLE IF NOT EXISTS chat_threads (
id TEXT PRIMARY KEY,
type TEXT NOT NULL CHECK (type IN ('gym', 'dm')),
gym_id TEXT,
trainer_id TEXT,
client_id TEXT,
created_by TEXT NOT NULL,
last_message_at INTEGER,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
archived_at INTEGER,
FOREIGN KEY (gym_id) REFERENCES gyms(id) ON DELETE CASCADE,
FOREIGN KEY (trainer_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (client_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE CASCADE
)
`);
await db.run(sql`
CREATE TABLE IF NOT EXISTS chat_thread_members (
id TEXT PRIMARY KEY,
thread_id TEXT NOT NULL,
user_id TEXT NOT NULL,
role_in_thread TEXT NOT NULL CHECK (role_in_thread IN ('superAdmin', 'admin', 'trainer', 'client')),
joined_at INTEGER NOT NULL,
left_at INTEGER,
muted INTEGER NOT NULL DEFAULT 0,
last_read_message_id TEXT,
last_read_at INTEGER,
FOREIGN KEY (thread_id) REFERENCES chat_threads(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE(thread_id, user_id)
)
`);
await db.run(sql`
CREATE TABLE IF NOT EXISTS chat_messages (
id TEXT PRIMARY KEY,
thread_id TEXT NOT NULL,
sender_user_id TEXT NOT NULL,
body TEXT NOT NULL,
kind TEXT NOT NULL CHECK (kind IN ('text', 'system')) DEFAULT 'text',
attachments_json TEXT,
client_message_id TEXT,
created_at INTEGER NOT NULL,
edited_at INTEGER,
deleted_at INTEGER,
FOREIGN KEY (thread_id) REFERENCES chat_threads(id) ON DELETE CASCADE,
FOREIGN KEY (sender_user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE(sender_user_id, client_message_id)
)
`);
await db.run(sql`
CREATE TABLE IF NOT EXISTS chat_message_reads (
id TEXT PRIMARY KEY,
message_id TEXT NOT NULL,
user_id TEXT NOT NULL,
read_at INTEGER NOT NULL,
FOREIGN KEY (message_id) REFERENCES chat_messages(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE(message_id, user_id)
)
`);
await db.run(
sql`CREATE INDEX IF NOT EXISTS chat_threads_gym_type_last_msg_idx ON chat_threads(gym_id, type, last_message_at)`,
);
await db.run(
sql`CREATE INDEX IF NOT EXISTS chat_threads_type_idx ON chat_threads(type)`,
);
await db.run(
sql`CREATE UNIQUE INDEX IF NOT EXISTS chat_threads_trainer_client_type_idx ON chat_threads(trainer_id, client_id, type)`,
);
await db.run(
sql`CREATE INDEX IF NOT EXISTS chat_thread_members_user_thread_idx ON chat_thread_members(user_id, thread_id)`,
);
await db.run(
sql`CREATE INDEX IF NOT EXISTS chat_messages_thread_created_at_idx ON chat_messages(thread_id, created_at)`,
);
await db.run(
sql`CREATE INDEX IF NOT EXISTS chat_message_reads_user_read_at_idx ON chat_message_reads(user_id, read_at)`,
);
tablesEnsured = true;
}
export async function getOrCreateGymRoomThread(
currentUser: ChatUserContext,
): Promise<ChatThreadSummary> {
await ensureChatTables();
if (!currentUser.gymId) {
throw new ChatError("No gym assigned", 400);
}
const rows = await db.all(sql`
SELECT
id,
type,
gym_id as gymId,
trainer_id as trainerId,
client_id as clientId
FROM chat_threads
WHERE type = 'gym'
AND gym_id = ${currentUser.gymId}
AND archived_at IS NULL
LIMIT 1
`);
let thread = rows?.[0] as ThreadRow | undefined;
if (!thread) {
const now = nowEpochSeconds();
const newThreadId = generateId("thread");
await db.run(sql`
INSERT INTO chat_threads (
id,
type,
gym_id,
created_by,
created_at,
updated_at
) VALUES (
${newThreadId},
${"gym"},
${currentUser.gymId},
${currentUser.id},
${now},
${now}
)
`);
const createdRows = await db.all(sql`
SELECT
id,
type,
gym_id as gymId,
trainer_id as trainerId,
client_id as clientId
FROM chat_threads
WHERE id = ${newThreadId}
LIMIT 1
`);
thread = createdRows?.[0] as ThreadRow | undefined;
}
if (!thread) {
throw new ChatError("Failed to initialize gym room", 500);
}
await ensureThreadMember(thread.id, currentUser.id, currentUser.role);
return await getThreadSummaryForUser(currentUser.id, thread.id);
}
export async function getOrCreateDmThread(params: {
trainerId: string;
clientId: string;
createdBy: ChatUserContext;
}): Promise<ChatThreadSummary> {
await ensureChatTables();
const trainerRows = await db.all(sql`
SELECT id, role, gym_id as gymId
FROM users
WHERE id = ${params.trainerId}
LIMIT 1
`);
const clientRows = await db.all(sql`
SELECT id, role, gym_id as gymId
FROM users
WHERE id = ${params.clientId}
LIMIT 1
`);
const trainer = trainerRows?.[0] as
| { id: string; role: UserRole; gymId: string | null }
| undefined;
const client = clientRows?.[0] as
| { id: string; role: UserRole; gymId: string | null }
| undefined;
if (!trainer || !client) {
throw new ChatError("Trainer or client not found", 404);
}
if (client.role !== "client") {
throw new ChatError("Direct message target must be a client", 400);
}
if (trainer.role !== "trainer" && trainer.role !== "admin") {
throw new ChatError("Direct message trainer is invalid", 400);
}
if (!trainer.gymId || trainer.gymId !== client.gymId) {
throw new ChatError("Trainer and client must belong to same gym", 400);
}
const assignmentRows = await db.all(sql`
SELECT id
FROM trainer_client_assignments
WHERE trainer_id = ${params.trainerId}
AND client_id = ${params.clientId}
AND is_active = 1
LIMIT 1
`);
if (!assignmentRows?.[0]) {
throw new ChatError("Trainer is not assigned to this client", 403);
}
const rows = await db.all(sql`
SELECT
id,
type,
gym_id as gymId,
trainer_id as trainerId,
client_id as clientId
FROM chat_threads
WHERE type = 'dm'
AND trainer_id = ${params.trainerId}
AND client_id = ${params.clientId}
AND archived_at IS NULL
LIMIT 1
`);
let thread = rows?.[0] as ThreadRow | undefined;
if (!thread) {
const now = nowEpochSeconds();
const threadId = generateId("thread");
await db.run(sql`
INSERT INTO chat_threads (
id,
type,
gym_id,
trainer_id,
client_id,
created_by,
created_at,
updated_at
) VALUES (
${threadId},
${"dm"},
${trainer.gymId},
${params.trainerId},
${params.clientId},
${params.createdBy.id},
${now},
${now}
)
`);
await ensureThreadMember(threadId, params.trainerId, trainer.role);
await ensureThreadMember(threadId, params.clientId, client.role);
const createdRows = await db.all(sql`
SELECT
id,
type,
gym_id as gymId,
trainer_id as trainerId,
client_id as clientId
FROM chat_threads
WHERE id = ${threadId}
LIMIT 1
`);
thread = createdRows?.[0] as ThreadRow | undefined;
}
if (!thread) {
throw new ChatError("Failed to initialize direct message", 500);
}
await ensureThreadMember(
thread.id,
params.createdBy.id,
params.createdBy.role,
);
return await getThreadSummaryForUser(params.createdBy.id, thread.id);
}
export async function listThreadsForUser(
currentUser: ChatUserContext,
): Promise<ChatThreadSummary[]> {
await ensureChatTables();
const rows = await db.all(sql`
SELECT
t.id,
t.type,
t.gym_id as gymId,
t.trainer_id as trainerId,
t.client_id as clientId,
t.created_by as createdBy,
t.last_message_at as lastMessageAt,
t.created_at as createdAt,
t.updated_at as updatedAt,
t.archived_at as archivedAt,
(
SELECT m.body
FROM chat_messages m
WHERE m.thread_id = t.id
AND m.deleted_at IS NULL
ORDER BY m.created_at DESC
LIMIT 1
) as lastMessageBody,
(
SELECT COUNT(*)
FROM chat_messages m
WHERE m.thread_id = t.id
AND m.deleted_at IS NULL
AND m.sender_user_id != ${currentUser.id}
AND m.created_at > COALESCE(mem.last_read_at, 0)
) as unreadCount
FROM chat_threads t
JOIN chat_thread_members mem
ON mem.thread_id = t.id
WHERE mem.user_id = ${currentUser.id}
AND mem.left_at IS NULL
AND t.archived_at IS NULL
ORDER BY COALESCE(t.last_message_at, t.created_at) DESC
`);
return (rows as ThreadSummaryRow[]).map(mapThreadSummaryRow);
}
export async function getThreadMessages(params: {
currentUser: ChatUserContext;
threadId: string;
cursor?: number;
limit?: number;
}): Promise<{ messages: ChatMessageRecord[]; nextCursor: number | null }> {
await ensureChatTables();
await assertThreadAccess(params.currentUser, params.threadId);
const pageSize = Math.max(1, Math.min(100, params.limit ?? 30));
const cursor = params.cursor;
const rows = await db.all(sql`
SELECT
id,
thread_id as threadId,
sender_user_id as senderUserId,
body,
kind,
attachments_json as attachmentsJson,
client_message_id as clientMessageId,
created_at as createdAt,
edited_at as editedAt,
deleted_at as deletedAt
FROM chat_messages
WHERE thread_id = ${params.threadId}
AND deleted_at IS NULL
${cursor ? sql`AND created_at < ${cursor}` : sql``}
ORDER BY created_at DESC
LIMIT ${pageSize + 1}
`);
const data = rows as MessageRow[];
const hasNextPage = data.length > pageSize;
const pageRows = hasNextPage ? data.slice(0, pageSize) : data;
const messages = pageRows.map(mapMessageRow).reverse();
const nextCursor =
hasNextPage && pageRows.length > 0
? Number(pageRows[pageRows.length - 1].createdAt)
: null;
return { messages, nextCursor };
}
export async function createThreadMessage(params: {
currentUser: ChatUserContext;
threadId: string;
body: string;
clientMessageId?: string;
attachments?: Array<{ url: string; type: string; name?: string }>;
}): Promise<ChatMessageRecord> {
await ensureChatTables();
await assertThreadAccess(params.currentUser, params.threadId);
const body = params.body.trim();
if (!body) {
throw new ChatError("Message body is required", 400);
}
if (body.length > 2000) {
throw new ChatError("Message is too long", 400);
}
if (params.clientMessageId) {
const existingRows = await db.all(sql`
SELECT
id,
thread_id as threadId,
sender_user_id as senderUserId,
body,
kind,
attachments_json as attachmentsJson,
client_message_id as clientMessageId,
created_at as createdAt,
edited_at as editedAt,
deleted_at as deletedAt
FROM chat_messages
WHERE sender_user_id = ${params.currentUser.id}
AND client_message_id = ${params.clientMessageId}
LIMIT 1
`);
if (existingRows?.[0]) {
return mapMessageRow(existingRows[0] as MessageRow);
}
}
const messageId = generateId("msg");
const now = nowEpochSeconds();
await db.run(sql`
INSERT INTO chat_messages (
id,
thread_id,
sender_user_id,
body,
kind,
attachments_json,
client_message_id,
created_at
) VALUES (
${messageId},
${params.threadId},
${params.currentUser.id},
${body},
${"text"},
${params.attachments?.length ? JSON.stringify(params.attachments) : null},
${params.clientMessageId ?? null},
${now}
)
`);
await db.run(sql`
UPDATE chat_threads
SET
last_message_at = ${now},
updated_at = ${now}
WHERE id = ${params.threadId}
`);
const messageRows = await db.all(sql`
SELECT
id,
thread_id as threadId,
sender_user_id as senderUserId,
body,
kind,
attachments_json as attachmentsJson,
client_message_id as clientMessageId,
created_at as createdAt,
edited_at as editedAt,
deleted_at as deletedAt
FROM chat_messages
WHERE id = ${messageId}
LIMIT 1
`);
const message = messageRows?.[0] as MessageRow | undefined;
if (!message) {
throw new ChatError("Failed to send message", 500);
}
return mapMessageRow(message);
}
export async function markThreadAsRead(params: {
currentUser: ChatUserContext;
threadId: string;
lastReadMessageId?: string;
}): Promise<void> {
await ensureChatTables();
await assertThreadAccess(params.currentUser, params.threadId);
let targetMessageId = params.lastReadMessageId ?? null;
if (!targetMessageId) {
const latestRows = await db.all(sql`
SELECT id
FROM chat_messages
WHERE thread_id = ${params.threadId}
AND deleted_at IS NULL
ORDER BY created_at DESC
LIMIT 1
`);
const latest = latestRows?.[0] as { id?: string } | undefined;
targetMessageId = latest?.id ?? null;
}
const now = nowEpochSeconds();
await db.run(sql`
UPDATE chat_thread_members
SET
last_read_message_id = ${targetMessageId},
last_read_at = ${now}
WHERE thread_id = ${params.threadId}
AND user_id = ${params.currentUser.id}
`);
}
export function isChatError(error: unknown): error is ChatError {
return error instanceof ChatError;
}
async function assertThreadAccess(
currentUser: ChatUserContext,
threadId: string,
): Promise<ThreadRow> {
const rows = await db.all(sql`
SELECT
id,
type,
gym_id as gymId,
trainer_id as trainerId,
client_id as clientId
FROM chat_threads
WHERE id = ${threadId}
AND archived_at IS NULL
LIMIT 1
`);
const thread = rows?.[0] as ThreadRow | undefined;
if (!thread) {
throw new ChatError("Thread not found", 404);
}
if (currentUser.role === "superAdmin") {
return thread;
}
if (thread.type === "gym") {
if (!currentUser.gymId || currentUser.gymId !== thread.gymId) {
throw new ChatError("Forbidden", 403);
}
await ensureThreadMember(thread.id, currentUser.id, currentUser.role);
return thread;
}
const isParticipant =
thread.trainerId === currentUser.id || thread.clientId === currentUser.id;
if (!isParticipant) {
throw new ChatError("Forbidden", 403);
}
if (thread.trainerId && thread.clientId) {
const assignmentRows = await db.all(sql`
SELECT id
FROM trainer_client_assignments
WHERE trainer_id = ${thread.trainerId}
AND client_id = ${thread.clientId}
AND is_active = 1
LIMIT 1
`);
if (!assignmentRows?.[0]) {
throw new ChatError("Trainer-client assignment is not active", 403);
}
}
await ensureThreadMember(thread.id, currentUser.id, currentUser.role);
return thread;
}
async function getThreadSummaryForUser(
userId: string,
threadId: string,
): Promise<ChatThreadSummary> {
const rows = await db.all(sql`
SELECT
t.id,
t.type,
t.gym_id as gymId,
t.trainer_id as trainerId,
t.client_id as clientId,
t.created_by as createdBy,
t.last_message_at as lastMessageAt,
t.created_at as createdAt,
t.updated_at as updatedAt,
t.archived_at as archivedAt,
(
SELECT m.body
FROM chat_messages m
WHERE m.thread_id = t.id
AND m.deleted_at IS NULL
ORDER BY m.created_at DESC
LIMIT 1
) as lastMessageBody,
(
SELECT COUNT(*)
FROM chat_messages m
WHERE m.thread_id = t.id
AND m.deleted_at IS NULL
AND m.sender_user_id != ${userId}
AND m.created_at > COALESCE(mem.last_read_at, 0)
) as unreadCount
FROM chat_threads t
JOIN chat_thread_members mem
ON mem.thread_id = t.id
AND mem.user_id = ${userId}
AND mem.left_at IS NULL
WHERE t.id = ${threadId}
LIMIT 1
`);
const row = rows?.[0] as ThreadSummaryRow | undefined;
if (!row) {
throw new ChatError("Thread not found", 404);
}
return mapThreadSummaryRow(row);
}
async function ensureThreadMember(
threadId: string,
userId: string,
role: UserRole,
): Promise<void> {
const now = nowEpochSeconds();
await db.run(sql`
INSERT OR IGNORE INTO chat_thread_members (
id,
thread_id,
user_id,
role_in_thread,
joined_at,
muted
) VALUES (
${generateId("member")},
${threadId},
${userId},
${role},
${now},
${0}
)
`);
await db.run(sql`
UPDATE chat_thread_members
SET left_at = NULL
WHERE thread_id = ${threadId}
AND user_id = ${userId}
`);
}
function mapThreadSummaryRow(row: ThreadSummaryRow): ChatThreadSummary {
return {
id: row.id,
type: row.type,
gymId: row.gymId ?? null,
trainerId: row.trainerId ?? null,
clientId: row.clientId ?? null,
createdBy: row.createdBy,
lastMessageAt: toDate(row.lastMessageAt),
createdAt: toDate(row.createdAt) ?? new Date(),
updatedAt: toDate(row.updatedAt) ?? new Date(),
archivedAt: toDate(row.archivedAt),
unreadCount: Number(row.unreadCount ?? 0),
lastMessageBody: row.lastMessageBody ?? null,
};
}
function mapMessageRow(row: MessageRow): ChatMessageRecord {
return {
id: row.id,
threadId: row.threadId,
senderUserId: row.senderUserId,
body: row.body,
kind: row.kind,
attachments: parseAttachments(row.attachmentsJson),
clientMessageId: row.clientMessageId ?? null,
createdAt: toDate(row.createdAt) ?? new Date(),
editedAt: toDate(row.editedAt),
deletedAt: toDate(row.deletedAt),
};
}
function parseAttachments(
value: string | null,
): Array<{ url: string; type: string; name?: string }> {
if (!value) {
return [];
}
try {
const parsed = JSON.parse(value);
if (!Array.isArray(parsed)) {
return [];
}
return parsed.filter(
(item): item is { url: string; type: string; name?: string } =>
item &&
typeof item === "object" &&
typeof item.url === "string" &&
typeof item.type === "string",
);
} catch (error) {
log.warn("Failed to parse chat attachments JSON", { value, error });
return [];
}
}
function toDate(value: number | string | null | undefined): Date | null {
if (value === null || value === undefined) {
return null;
}
const numberValue = Number(value);
if (!Number.isFinite(numberValue)) {
return null;
}
return new Date(numberValue * 1000);
}
function generateId(prefix: string): string {
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
}
function nowEpochSeconds(): number {
return Math.floor(Date.now() / 1000);
}
interface ThreadRow {
id: string;
type: "gym" | "dm";
gymId: string | null;
trainerId: string | null;
clientId: string | null;
}
interface ThreadSummaryRow {
id: string;
type: "gym" | "dm";
gymId: string | null;
trainerId: string | null;
clientId: string | null;
createdBy: string;
lastMessageAt: number | null;
createdAt: number;
updatedAt: number;
archivedAt: number | null;
unreadCount: number;
lastMessageBody: string | null;
}
interface MessageRow {
id: string;
threadId: string;
senderUserId: string;
body: string;
kind: "text" | "system";
attachmentsJson: string | null;
clientMessageId: string | null;
createdAt: number;
editedAt: number | null;
deletedAt: number | null;
}
+4 -6
View File
@@ -1,11 +1,7 @@
import { clerkClient } from "@clerk/nextjs/server";
import { type UserRole } from "@fitai/shared";
import log from "./logger";
/**
* User roles available in the application
*/
export type UserRole = "admin" | "trainer" | "client";
/**
* Set a user's role in Clerk public metadata
* This will trigger a webhook that syncs the role to the database
@@ -71,7 +67,8 @@ export async function hasRole(
* const isAdmin = await isAdmin('user_abc123');
*/
export async function isAdmin(userId: string): Promise<boolean> {
return hasRole(userId, "admin");
const role = await getUserRole(userId);
return role === "admin" || role === "superAdmin";
}
/**
@@ -161,6 +158,7 @@ export async function getUserCountByRole(): Promise<Record<UserRole, number>> {
const { data: users } = await client.users.getUserList();
const counts: Record<UserRole, number> = {
superAdmin: 0,
admin: 0,
trainer: 0,
client: 0,
+1
View File
@@ -154,6 +154,7 @@ export interface IDatabase {
getRecommendationsByUserId(userId: string): Promise<Recommendation[]>;
getRecommendationsByUserIds(userIds: string[]): Promise<Recommendation[]>;
getAllRecommendations(): Promise<Recommendation[]>;
getRecommendationById(id: string): Promise<Recommendation | null>;
updateRecommendation(
id: string,
updates: Partial<Recommendation>,
+277
View File
@@ -0,0 +1,277 @@
import { db, eq, sql, users } from "@fitai/database";
export const DEFAULT_GEOFENCE_RADIUS_METERS = 30;
export const MAX_LOCATION_ACCURACY_METERS = 50;
export const MAX_FALLBACK_ACCURACY_MARGIN_METERS = 120;
export interface UserLocation {
latitude: number;
longitude: number;
accuracy: number;
}
export async function ensureGymsGeofenceColumns(): Promise<void> {
const rows = await db.all(sql`PRAGMA table_info('gyms')`);
const columns = new Set(
(rows as Array<{ name?: string }>).map((row) => row.name).filter(Boolean),
);
if (!columns.has("latitude")) {
await db.run(sql`ALTER TABLE gyms ADD COLUMN latitude REAL`);
}
if (!columns.has("longitude")) {
await db.run(sql`ALTER TABLE gyms ADD COLUMN longitude REAL`);
}
if (!columns.has("geofence_radius_meters")) {
await db.run(
sql`ALTER TABLE gyms ADD COLUMN geofence_radius_meters REAL NOT NULL DEFAULT 30`,
);
}
if (!columns.has("geofence_enabled")) {
await db.run(
sql`ALTER TABLE gyms ADD COLUMN geofence_enabled INTEGER NOT NULL DEFAULT 1`,
);
}
}
interface GymGeofenceConfig {
id: string;
name: string;
latitude: number | null;
longitude: number | null;
geofenceRadiusMeters: number | null;
geofenceEnabled: boolean | null;
}
export async function getUserGymGeofence(
userId: string,
): Promise<GymGeofenceConfig | null> {
await ensureGymsGeofenceColumns();
const user = await db.select().from(users).where(eq(users.id, userId)).get();
if (!user?.gymId) {
return null;
}
const rows = await db.all(sql`
SELECT
id,
name,
latitude,
longitude,
geofence_radius_meters as geofenceRadiusMeters,
geofence_enabled as geofenceEnabled
FROM gyms
WHERE id = ${user.gymId}
LIMIT 1
`);
const gym = rows?.[0] as
| {
id: string;
name: string;
latitude: number | null;
longitude: number | null;
geofenceRadiusMeters: number | null;
geofenceEnabled: number | boolean | null;
}
| undefined;
if (!gym) {
return null;
}
return {
id: gym.id,
name: gym.name,
latitude: gym.latitude,
longitude: gym.longitude,
geofenceRadiusMeters: gym.geofenceRadiusMeters,
geofenceEnabled:
typeof gym.geofenceEnabled === "boolean"
? gym.geofenceEnabled
: gym.geofenceEnabled === null
? null
: Boolean(gym.geofenceEnabled),
};
}
export function parseUserLocation(payload: unknown): UserLocation | null {
if (!payload || typeof payload !== "object") {
return null;
}
const raw = payload as Record<string, unknown>;
const latitude = Number(raw.latitude);
const longitude = Number(raw.longitude);
const accuracy = Number(raw.accuracy);
if (
!Number.isFinite(latitude) ||
!Number.isFinite(longitude) ||
!Number.isFinite(accuracy)
) {
return null;
}
return { latitude, longitude, accuracy };
}
export function validateGeofence(
gym: GymGeofenceConfig,
location: UserLocation | null,
): { ok: true } | { ok: false; status: number; error: string } {
const geofenceEnabled = gym.geofenceEnabled ?? true;
if (!geofenceEnabled) {
return { ok: true };
}
if (!location) {
return {
ok: false,
status: 400,
error: "Location is required for gym check-in/check-out",
};
}
if (location.accuracy > MAX_LOCATION_ACCURACY_METERS) {
return {
ok: false,
status: 400,
error: `Location accuracy too low (${Math.round(location.accuracy)}m). Move to an open area and try again.`,
};
}
if (gym.latitude === null || gym.longitude === null) {
return {
ok: false,
status: 400,
error: "Gym geofence is enabled but gym coordinates are not configured",
};
}
const radius = gym.geofenceRadiusMeters ?? DEFAULT_GEOFENCE_RADIUS_METERS;
const distanceMeters = haversineDistanceMeters(
gym.latitude,
gym.longitude,
location.latitude,
location.longitude,
);
if (distanceMeters > radius) {
return {
ok: false,
status: 403,
error: `You are outside the gym geofence (${Math.round(distanceMeters)}m away, allowed ${Math.round(radius)}m).`,
};
}
return { ok: true };
}
export function validateGeofenceWithFallback(
gym: GymGeofenceConfig,
location: UserLocation | null,
fallbackRequested: boolean,
): { ok: true } | { ok: false; status: number; error: string } {
const geofenceEnabled = gym.geofenceEnabled ?? true;
if (!geofenceEnabled) {
return { ok: true };
}
if (!location) {
return {
ok: false,
status: 400,
error: "Location is required for gym check-in/check-out",
};
}
if (gym.latitude === null || gym.longitude === null) {
return {
ok: false,
status: 400,
error: "Gym geofence is enabled but gym coordinates are not configured",
};
}
const radius = gym.geofenceRadiusMeters ?? DEFAULT_GEOFENCE_RADIUS_METERS;
const distanceMeters = haversineDistanceMeters(
gym.latitude,
gym.longitude,
location.latitude,
location.longitude,
);
if (location.accuracy <= MAX_LOCATION_ACCURACY_METERS) {
if (distanceMeters > radius) {
return {
ok: false,
status: 403,
error: `You are outside the gym geofence (${Math.round(distanceMeters)}m away, allowed ${Math.round(radius)}m).`,
};
}
return { ok: true };
}
if (!fallbackRequested) {
return {
ok: false,
status: 400,
error: `Location accuracy too low (${Math.round(location.accuracy)}m). Move to an open area and try again.`,
};
}
const fallbackMargin = Math.min(
location.accuracy,
MAX_FALLBACK_ACCURACY_MARGIN_METERS,
);
const fallbackAllowedDistance = radius + fallbackMargin;
if (distanceMeters > fallbackAllowedDistance) {
return {
ok: false,
status: 403,
error: `You are outside the gym geofence (${Math.round(distanceMeters)}m away, fallback allowed ${Math.round(fallbackAllowedDistance)}m).`,
};
}
return { ok: true };
}
export function validateCheckInGeofence(
gym: GymGeofenceConfig,
location: UserLocation | null,
fallbackRequested: boolean,
): { ok: true } | { ok: false; status: number; error: string } {
return validateGeofenceWithFallback(gym, location, fallbackRequested);
}
function haversineDistanceMeters(
latitude1: number,
longitude1: number,
latitude2: number,
longitude2: number,
): number {
const earthRadiusMeters = 6371000;
const dLat = toRadians(latitude2 - latitude1);
const dLng = toRadians(longitude2 - longitude1);
const lat1Rad = toRadians(latitude1);
const lat2Rad = toRadians(latitude2);
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.sin(dLng / 2) *
Math.sin(dLng / 2) *
Math.cos(lat1Rad) *
Math.cos(lat2Rad);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return earthRadiusMeters * c;
}
function toRadians(degrees: number): number {
return (degrees * Math.PI) / 180;
}
+26
View File
@@ -0,0 +1,26 @@
import { getDatabase } from "@/lib/database";
import { getMembershipFeatures } from "./features";
export async function getUserMembershipContext(userId: string): Promise<{
membershipType: "basic" | "premium" | "vip";
features: ReturnType<typeof getMembershipFeatures>;
}> {
const db = await getDatabase();
const user = await db.getUserById(userId);
if (!user || user.role !== "client") {
const membershipType = "vip" as const;
return {
membershipType,
features: getMembershipFeatures(membershipType),
};
}
const client = await db.getClientByUserId(userId);
const membershipType = client?.membershipType ?? "basic";
return {
membershipType,
features: getMembershipFeatures(membershipType),
};
}
+35
View File
@@ -0,0 +1,35 @@
import type { MembershipType } from "@/lib/validation/schemas";
export interface MembershipFeatures {
recommendationsPerMonth: number;
hydrationTracking: boolean;
nutritionTracking: boolean;
advancedStatistics: boolean;
}
export const MEMBERSHIP_FEATURES: Record<MembershipType, MembershipFeatures> = {
basic: {
recommendationsPerMonth: 1,
hydrationTracking: false,
nutritionTracking: false,
advancedStatistics: false,
},
premium: {
recommendationsPerMonth: -1,
hydrationTracking: true,
nutritionTracking: true,
advancedStatistics: true,
},
vip: {
recommendationsPerMonth: -1,
hydrationTracking: true,
nutritionTracking: true,
advancedStatistics: true,
},
};
export function getMembershipFeatures(
membershipType: MembershipType,
): MembershipFeatures {
return MEMBERSHIP_FEATURES[membershipType];
}
-9
View File
@@ -25,14 +25,6 @@ const isPublicRoute = createRouteMatcher([
const isApiRoute = createRouteMatcher(["/api/(.*)"]);
export default clerkMiddleware(async (auth, req) => {
// Log for debugging
const authHeader = req.headers.get("authorization");
if (authHeader) {
log.debug("Authorization header present", {
preview: authHeader.substring(0, 20) + "...",
});
}
// Don't protect public routes
if (isPublicRoute(req)) {
log.debug("Public route, skipping auth");
@@ -42,7 +34,6 @@ export default clerkMiddleware(async (auth, req) => {
// For API routes, let the route handler check auth
// This allows API routes to handle both web sessions and mobile Bearer tokens
if (isApiRoute(req)) {
log.debug("API route, auth will be checked in handler");
return;
}
+5
View File
@@ -34,6 +34,11 @@ EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_your_publishable_key_here
#
EXPO_PUBLIC_API_URL=http://localhost:3000
# Optional realtime override (defaults to EXPO_PUBLIC_API_URL host)
# Set this when your Socket.IO service runs on a different origin/port.
EXPO_PUBLIC_REALTIME_URL=http://localhost:3001
EXPO_PUBLIC_REALTIME_PATH=/socket.io
# =============================================================================
# App Configuration (Optional)
# =============================================================================
@@ -1,31 +0,0 @@
# Values used to calculate the hash in this folder name.
# Should not depend on the absolute path of the project itself.
# - AGP: 8.11.0.
# - $NDK is the path to NDK 27.1.12297006.
# - $PROJECT is the path to the parent folder of the root Gradle build file.
# - $ABI is the ABI to be built with. The specific value doesn't contribute to the value of the hash.
# - $HASH is the hash value computed from this text.
# - $CMAKE is the path to CMake 3.22.1.
# - $NINJA is the path to Ninja.
-H/home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactAndroid/cmake-utils/default-app-setup
-DCMAKE_SYSTEM_NAME=Android
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
-DCMAKE_SYSTEM_VERSION=26
-DANDROID_PLATFORM=android-26
-DANDROID_ABI=$ABI
-DCMAKE_ANDROID_ARCH_ABI=$ABI
-DANDROID_NDK=$NDK
-DCMAKE_ANDROID_NDK=$NDK
-DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake
-DCMAKE_MAKE_PROGRAM=$NINJA
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=$PROJECT/app/build/intermediates/cxx/Debug/$HASH/obj/$ABI
-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=$PROJECT/app/build/intermediates/cxx/Debug/$HASH/obj/$ABI
-DCMAKE_BUILD_TYPE=Debug
-DCMAKE_FIND_ROOT_PATH=$PROJECT/app/.cxx/Debug/$HASH/prefab/$ABI/prefab
-B$PROJECT/app/.cxx/Debug/$HASH/$ABI
-GNinja
-DPROJECT_BUILD_DIR=$PROJECT/app/build
-DPROJECT_ROOT_DIR=$PROJECT
-DREACT_ANDROID_DIR=/home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactAndroid
-DANDROID_STL=c++_shared
-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON
@@ -1,27 +0,0 @@
if(NOT TARGET ReactAndroid::hermestooling)
add_library(ReactAndroid::hermestooling SHARED IMPORTED)
set_target_properties(ReactAndroid::hermestooling PROPERTIES
IMPORTED_LOCATION "/home/echo/.gradle/caches/8.14.3/transforms/e5d515112484fe4dddc05785469cc380/transformed/react-android-0.81.5-debug/prefab/modules/hermestooling/libs/android.x86/libhermestooling.so"
INTERFACE_INCLUDE_DIRECTORIES "/home/echo/.gradle/caches/8.14.3/transforms/e5d515112484fe4dddc05785469cc380/transformed/react-android-0.81.5-debug/prefab/modules/hermestooling/include"
INTERFACE_LINK_LIBRARIES ""
)
endif()
if(NOT TARGET ReactAndroid::jsi)
add_library(ReactAndroid::jsi SHARED IMPORTED)
set_target_properties(ReactAndroid::jsi PROPERTIES
IMPORTED_LOCATION "/home/echo/.gradle/caches/8.14.3/transforms/e5d515112484fe4dddc05785469cc380/transformed/react-android-0.81.5-debug/prefab/modules/jsi/libs/android.x86/libjsi.so"
INTERFACE_INCLUDE_DIRECTORIES "/home/echo/.gradle/caches/8.14.3/transforms/e5d515112484fe4dddc05785469cc380/transformed/react-android-0.81.5-debug/prefab/modules/jsi/include"
INTERFACE_LINK_LIBRARIES ""
)
endif()
if(NOT TARGET ReactAndroid::reactnative)
add_library(ReactAndroid::reactnative SHARED IMPORTED)
set_target_properties(ReactAndroid::reactnative PROPERTIES
IMPORTED_LOCATION "/home/echo/.gradle/caches/8.14.3/transforms/e5d515112484fe4dddc05785469cc380/transformed/react-android-0.81.5-debug/prefab/modules/reactnative/libs/android.x86/libreactnative.so"
INTERFACE_INCLUDE_DIRECTORIES "/home/echo/.gradle/caches/8.14.3/transforms/e5d515112484fe4dddc05785469cc380/transformed/react-android-0.81.5-debug/prefab/modules/reactnative/include"
INTERFACE_LINK_LIBRARIES ""
)
endif()
@@ -1,9 +0,0 @@
set(PACKAGE_VERSION 0.81.5)
if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
if("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_EXACT TRUE)
endif()
endif()
@@ -1,9 +0,0 @@
if(NOT TARGET fbjni::fbjni)
add_library(fbjni::fbjni SHARED IMPORTED)
set_target_properties(fbjni::fbjni PROPERTIES
IMPORTED_LOCATION "/home/echo/.gradle/caches/8.14.3/transforms/488fe6e480efb97ec75859f6e274282f/transformed/fbjni-0.7.0/prefab/modules/fbjni/libs/android.x86/libfbjni.so"
INTERFACE_INCLUDE_DIRECTORIES "/home/echo/.gradle/caches/8.14.3/transforms/488fe6e480efb97ec75859f6e274282f/transformed/fbjni-0.7.0/prefab/modules/fbjni/include"
INTERFACE_LINK_LIBRARIES ""
)
endif()
@@ -1,9 +0,0 @@
set(PACKAGE_VERSION 3.22.1)
if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
if("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_EXACT TRUE)
endif()
endif()
@@ -1,9 +0,0 @@
if(NOT TARGET hermes-engine::libhermes)
add_library(hermes-engine::libhermes SHARED IMPORTED)
set_target_properties(hermes-engine::libhermes PROPERTIES
IMPORTED_LOCATION "/home/echo/.gradle/caches/8.14.3/transforms/f3d279cf13499f4c93e2b512ac9499e6/transformed/hermes-android-0.81.5-debug/prefab/modules/libhermes/libs/android.x86/libhermes.so"
INTERFACE_INCLUDE_DIRECTORIES "/home/echo/.gradle/caches/8.14.3/transforms/f3d279cf13499f4c93e2b512ac9499e6/transformed/hermes-android-0.81.5-debug/prefab/modules/libhermes/include"
INTERFACE_LINK_LIBRARIES ""
)
endif()
@@ -1,9 +0,0 @@
set(PACKAGE_VERSION 0.81.5)
if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
if("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_EXACT TRUE)
endif()
endif()
@@ -1,27 +0,0 @@
if(NOT TARGET ReactAndroid::hermestooling)
add_library(ReactAndroid::hermestooling SHARED IMPORTED)
set_target_properties(ReactAndroid::hermestooling PROPERTIES
IMPORTED_LOCATION "/home/echo/.gradle/caches/8.14.3/transforms/e5d515112484fe4dddc05785469cc380/transformed/react-android-0.81.5-debug/prefab/modules/hermestooling/libs/android.x86_64/libhermestooling.so"
INTERFACE_INCLUDE_DIRECTORIES "/home/echo/.gradle/caches/8.14.3/transforms/e5d515112484fe4dddc05785469cc380/transformed/react-android-0.81.5-debug/prefab/modules/hermestooling/include"
INTERFACE_LINK_LIBRARIES ""
)
endif()
if(NOT TARGET ReactAndroid::jsi)
add_library(ReactAndroid::jsi SHARED IMPORTED)
set_target_properties(ReactAndroid::jsi PROPERTIES
IMPORTED_LOCATION "/home/echo/.gradle/caches/8.14.3/transforms/e5d515112484fe4dddc05785469cc380/transformed/react-android-0.81.5-debug/prefab/modules/jsi/libs/android.x86_64/libjsi.so"
INTERFACE_INCLUDE_DIRECTORIES "/home/echo/.gradle/caches/8.14.3/transforms/e5d515112484fe4dddc05785469cc380/transformed/react-android-0.81.5-debug/prefab/modules/jsi/include"
INTERFACE_LINK_LIBRARIES ""
)
endif()
if(NOT TARGET ReactAndroid::reactnative)
add_library(ReactAndroid::reactnative SHARED IMPORTED)
set_target_properties(ReactAndroid::reactnative PROPERTIES
IMPORTED_LOCATION "/home/echo/.gradle/caches/8.14.3/transforms/e5d515112484fe4dddc05785469cc380/transformed/react-android-0.81.5-debug/prefab/modules/reactnative/libs/android.x86_64/libreactnative.so"
INTERFACE_INCLUDE_DIRECTORIES "/home/echo/.gradle/caches/8.14.3/transforms/e5d515112484fe4dddc05785469cc380/transformed/react-android-0.81.5-debug/prefab/modules/reactnative/include"
INTERFACE_LINK_LIBRARIES ""
)
endif()
@@ -1,9 +0,0 @@
set(PACKAGE_VERSION 0.81.5)
if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
if("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_EXACT TRUE)
endif()
endif()
@@ -1,9 +0,0 @@
if(NOT TARGET fbjni::fbjni)
add_library(fbjni::fbjni SHARED IMPORTED)
set_target_properties(fbjni::fbjni PROPERTIES
IMPORTED_LOCATION "/home/echo/.gradle/caches/8.14.3/transforms/488fe6e480efb97ec75859f6e274282f/transformed/fbjni-0.7.0/prefab/modules/fbjni/libs/android.x86_64/libfbjni.so"
INTERFACE_INCLUDE_DIRECTORIES "/home/echo/.gradle/caches/8.14.3/transforms/488fe6e480efb97ec75859f6e274282f/transformed/fbjni-0.7.0/prefab/modules/fbjni/include"
INTERFACE_LINK_LIBRARIES ""
)
endif()
@@ -1,9 +0,0 @@
set(PACKAGE_VERSION 3.22.1)
if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
if("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_EXACT TRUE)
endif()
endif()
@@ -1,9 +0,0 @@
if(NOT TARGET hermes-engine::libhermes)
add_library(hermes-engine::libhermes SHARED IMPORTED)
set_target_properties(hermes-engine::libhermes PROPERTIES
IMPORTED_LOCATION "/home/echo/.gradle/caches/8.14.3/transforms/f3d279cf13499f4c93e2b512ac9499e6/transformed/hermes-android-0.81.5-debug/prefab/modules/libhermes/libs/android.x86_64/libhermes.so"
INTERFACE_INCLUDE_DIRECTORIES "/home/echo/.gradle/caches/8.14.3/transforms/f3d279cf13499f4c93e2b512ac9499e6/transformed/hermes-android-0.81.5-debug/prefab/modules/libhermes/include"
INTERFACE_LINK_LIBRARIES ""
)
endif()
@@ -1,9 +0,0 @@
set(PACKAGE_VERSION 0.81.5)
if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
if("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_EXACT TRUE)
endif()
endif()
@@ -1,851 +0,0 @@
{
"inputs" :
[
{
"path" : "CMakeLists.txt"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeDetermineSystem.cmake"
},
{
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/ndk/27.1.12297006/build/cmake/android.toolchain.cmake"
},
{
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/ndk/27.1.12297006/build/cmake/android-legacy.toolchain.cmake"
},
{
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/ndk/27.1.12297006/build/cmake/abis.cmake"
},
{
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/ndk/27.1.12297006/build/cmake/platforms.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Platform/Android-Determine.cmake"
},
{
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/ndk/27.1.12297006/build/cmake/hooks/pre/Android-Determine.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeSystem.cmake.in"
},
{
"isGenerated" : true,
"path" : "/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/3.22.1-g37088a8/CMakeSystem.cmake"
},
{
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/ndk/27.1.12297006/build/cmake/android.toolchain.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeSystemSpecificInitialize.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Platform/Android-Initialize.cmake"
},
{
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/ndk/27.1.12297006/build/cmake/hooks/pre/Android-Initialize.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeDetermineCCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeDetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Platform/Android-Determine-C.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Platform/Android/Determine-Compiler.cmake"
},
{
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/ndk/27.1.12297006/build/cmake/hooks/pre/Determine-Compiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeDetermineCompilerId.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeCompilerIdDetection.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/ADSP-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/ARMCC-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/ARMClang-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/AppleClang-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Clang-DetermineCompilerInternal.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Borland-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Bruce-C-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Clang-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Clang-DetermineCompilerInternal.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Compaq-C-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Cray-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Embarcadero-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Fujitsu-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/FujitsuClang-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/GHS-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/GNU-C-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/HP-C-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/IAR-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Intel-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/IntelLLVM-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/MSVC-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/NVHPC-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/NVIDIA-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/OpenWatcom-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/PGI-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/PathScale-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/SCO-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/SDCC-C-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/SunPro-C-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/TI-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/TinyCC-C-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/VisualAge-C-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/IBMCPP-C-DetermineVersionInternal.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Watcom-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/XL-C-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/IBMCPP-C-DetermineVersionInternal.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/XLClang-C-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/zOS-C-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/IBMCPP-C-DetermineVersionInternal.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeFindBinUtils.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Clang-FindBinUtils.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeCCompiler.cmake.in"
},
{
"isGenerated" : true,
"path" : "/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/3.22.1-g37088a8/CMakeCCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeDetermineCXXCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeDetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Platform/Android-Determine-CXX.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Platform/Android/Determine-Compiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeDetermineCompilerId.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeCompilerIdDetection.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/ADSP-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/ARMCC-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/ARMClang-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/AppleClang-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Clang-DetermineCompilerInternal.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Borland-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Clang-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Clang-DetermineCompilerInternal.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Comeau-CXX-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Compaq-CXX-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Cray-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Embarcadero-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Fujitsu-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/FujitsuClang-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/GHS-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/GNU-CXX-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/HP-CXX-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/IAR-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Intel-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/IntelLLVM-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/MSVC-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/NVHPC-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/NVIDIA-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/OpenWatcom-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/PGI-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/PathScale-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/SCO-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/SunPro-CXX-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/TI-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/VisualAge-CXX-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/IBMCPP-CXX-DetermineVersionInternal.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Watcom-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/XL-CXX-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/IBMCPP-CXX-DetermineVersionInternal.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/XLClang-CXX-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/zOS-CXX-DetermineCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/IBMCPP-CXX-DetermineVersionInternal.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeFindBinUtils.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Clang-FindBinUtils.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeCXXCompiler.cmake.in"
},
{
"isGenerated" : true,
"path" : "/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/3.22.1-g37088a8/CMakeCXXCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeSystemSpecificInformation.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeGenericSystem.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeInitializeConfigs.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Platform/Android.cmake"
},
{
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/ndk/27.1.12297006/build/cmake/hooks/pre/Android.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Platform/Linux.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Platform/UnixPaths.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeCInformation.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeLanguageInformation.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Clang-C.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Clang.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/CMakeCommonCompilerMacros.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/GNU.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/CMakeCommonCompilerMacros.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Platform/Android-Clang-C.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Platform/Android-Clang.cmake"
},
{
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/ndk/27.1.12297006/build/cmake/hooks/pre/Android-Clang.cmake"
},
{
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/ndk/27.1.12297006/build/cmake/flags.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeCommonLanguageInclude.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeTestCCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeTestCompilerCommon.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeDetermineCompilerABI.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeParseImplicitIncludeInfo.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeParseImplicitLinkInfo.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeParseLibraryArchitecture.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeTestCompilerCommon.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeCCompilerABI.c"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeDetermineCompileFeatures.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Internal/FeatureTesting.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeCCompiler.cmake.in"
},
{
"isGenerated" : true,
"path" : "/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/3.22.1-g37088a8/CMakeCCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeCXXInformation.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeLanguageInformation.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Clang-CXX.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Compiler/Clang.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Platform/Android-Clang-CXX.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Platform/Android-Clang.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeCommonLanguageInclude.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeTestCXXCompiler.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeTestCompilerCommon.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeDetermineCompilerABI.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeParseImplicitIncludeInfo.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeParseImplicitLinkInfo.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeParseLibraryArchitecture.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeTestCompilerCommon.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeCXXCompilerABI.cpp"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeDetermineCompileFeatures.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/Internal/FeatureTesting.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CMakeCXXCompiler.cmake.in"
},
{
"isGenerated" : true,
"path" : "/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/3.22.1-g37088a8/CMakeCXXCompiler.cmake"
},
{
"isExternal" : true,
"path" : "/home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake"
},
{
"isExternal" : true,
"path" : "/home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactAndroid/cmake-utils/folly-flags.cmake"
},
{
"isExternal" : true,
"path" : "/home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactCommon/cmake-utils/react-native-flags.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CheckIPOSupported.cmake"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CheckIPOSupported/CMakeLists-CXX.txt.in"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CheckIPOSupported/foo.cpp"
},
{
"isCMake" : true,
"isExternal" : true,
"path" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22/Modules/CheckIPOSupported/main.cpp"
},
{
"isExternal" : true,
"path" : "/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/prefab/x86/prefab/lib/i686-linux-android/cmake/ReactAndroid/ReactAndroidConfigVersion.cmake"
},
{
"isExternal" : true,
"path" : "/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/prefab/x86/prefab/lib/i686-linux-android/cmake/ReactAndroid/ReactAndroidConfig.cmake"
},
{
"isExternal" : true,
"path" : "/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/prefab/x86/prefab/lib/i686-linux-android/cmake/fbjni/fbjniConfigVersion.cmake"
},
{
"isExternal" : true,
"path" : "/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/prefab/x86/prefab/lib/i686-linux-android/cmake/fbjni/fbjniConfig.cmake"
}
],
"kind" : "cmakeFiles",
"paths" :
{
"build" : "/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86",
"source" : "/home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactAndroid/cmake-utils/default-app-setup"
},
"version" :
{
"major" : 1,
"minor" : 0
}
}
@@ -1,60 +0,0 @@
{
"configurations" :
[
{
"directories" :
[
{
"build" : ".",
"jsonFile" : "directory-.-Debug-f5ebdc15457944623624.json",
"minimumCMakeVersion" :
{
"string" : "3.13"
},
"projectIndex" : 0,
"source" : ".",
"targetIndexes" :
[
0
]
}
],
"name" : "Debug",
"projects" :
[
{
"directoryIndexes" :
[
0
],
"name" : "appmodules",
"targetIndexes" :
[
0
]
}
],
"targets" :
[
{
"directoryIndex" : 0,
"id" : "appmodules::@6890427a1f51a3e7e1df",
"jsonFile" : "target-appmodules-Debug-9ef032710fe355da9383.json",
"name" : "appmodules",
"projectIndex" : 0
}
]
}
],
"kind" : "codemodel",
"paths" :
{
"build" : "/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86",
"source" : "/home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactAndroid/cmake-utils/default-app-setup"
},
"version" :
{
"major" : 2,
"minor" : 3
}
}
@@ -1,14 +0,0 @@
{
"backtraceGraph" :
{
"commands" : [],
"files" : [],
"nodes" : []
},
"installers" : [],
"paths" :
{
"build" : ".",
"source" : "."
}
}
@@ -1,92 +0,0 @@
{
"cmake" :
{
"generator" :
{
"multiConfig" : false,
"name" : "Ninja"
},
"paths" :
{
"cmake" : "/home/echo/Android/Sdk/cmake/3.22.1/bin/cmake",
"cpack" : "/home/echo/Android/Sdk/cmake/3.22.1/bin/cpack",
"ctest" : "/home/echo/Android/Sdk/cmake/3.22.1/bin/ctest",
"root" : "/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22"
},
"version" :
{
"isDirty" : false,
"major" : 3,
"minor" : 22,
"patch" : 1,
"string" : "3.22.1-g37088a8",
"suffix" : "g37088a8"
}
},
"objects" :
[
{
"jsonFile" : "codemodel-v2-d483cb7bfe026ad3a64f.json",
"kind" : "codemodel",
"version" :
{
"major" : 2,
"minor" : 3
}
},
{
"jsonFile" : "cache-v2-099ee692bdea7ed85643.json",
"kind" : "cache",
"version" :
{
"major" : 2,
"minor" : 0
}
},
{
"jsonFile" : "cmakeFiles-v1-b99aeefcc68e41c89ebf.json",
"kind" : "cmakeFiles",
"version" :
{
"major" : 1,
"minor" : 0
}
}
],
"reply" :
{
"client-agp" :
{
"cache-v2" :
{
"jsonFile" : "cache-v2-099ee692bdea7ed85643.json",
"kind" : "cache",
"version" :
{
"major" : 2,
"minor" : 0
}
},
"cmakeFiles-v1" :
{
"jsonFile" : "cmakeFiles-v1-b99aeefcc68e41c89ebf.json",
"kind" : "cmakeFiles",
"version" :
{
"major" : 1,
"minor" : 0
}
},
"codemodel-v2" :
{
"jsonFile" : "codemodel-v2-d483cb7bfe026ad3a64f.json",
"kind" : "codemodel",
"version" :
{
"major" : 2,
"minor" : 3
}
}
}
}
}
@@ -1,226 +0,0 @@
{
"artifacts" :
[
{
"path" : "/home/echo/dev/prototype/apps/mobile/android/app/build/intermediates/cxx/Debug/2z472e3c/obj/x86/libappmodules.so"
}
],
"backtrace" : 3,
"backtraceGraph" :
{
"commands" :
[
"add_library",
"include",
"target_link_libraries",
"target_compile_options",
"target_compile_reactnative_options",
"target_compile_definitions",
"target_include_directories"
],
"files" :
[
"/home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake",
"CMakeLists.txt",
"/home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactCommon/cmake-utils/react-native-flags.cmake"
],
"nodes" :
[
{
"file" : 1
},
{
"command" : 1,
"file" : 1,
"line" : 31,
"parent" : 0
},
{
"file" : 0,
"parent" : 1
},
{
"command" : 0,
"file" : 0,
"line" : 64,
"parent" : 2
},
{
"command" : 2,
"file" : 0,
"line" : 81,
"parent" : 2
},
{
"command" : 4,
"file" : 0,
"line" : 71,
"parent" : 2
},
{
"command" : 3,
"file" : 2,
"line" : 30,
"parent" : 5
},
{
"command" : 5,
"file" : 2,
"line" : 33,
"parent" : 5
},
{
"command" : 6,
"file" : 0,
"line" : 66,
"parent" : 2
}
]
},
"compileGroups" :
[
{
"compileCommandFragments" :
[
{
"fragment" : "-g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D__BIONIC_NO_PAGE_SIZE_MACRO -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -fno-limit-debug-info -fPIC"
},
{
"backtrace" : 6,
"fragment" : "-Wall"
},
{
"backtrace" : 6,
"fragment" : "-Werror"
},
{
"backtrace" : 6,
"fragment" : "-fexceptions"
},
{
"backtrace" : 6,
"fragment" : "-frtti"
},
{
"backtrace" : 6,
"fragment" : "-std=c++20"
},
{
"backtrace" : 6,
"fragment" : "-DFOLLY_NO_CONFIG=1"
},
{
"backtrace" : 6,
"fragment" : "-DLOG_TAG=\\\"ReactNative\\\""
}
],
"defines" :
[
{
"backtrace" : 7,
"define" : "RN_SERIALIZABLE_STATE"
},
{
"define" : "appmodules_EXPORTS"
}
],
"includes" :
[
{
"backtrace" : 8,
"path" : "/home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactAndroid/cmake-utils/default-app-setup"
},
{
"backtrace" : 8,
"path" : "/home/echo/dev/prototype/apps/mobile/android/app/build/generated/autolinking/src/main/jni"
},
{
"backtrace" : 4,
"isSystem" : true,
"path" : "/home/echo/.gradle/caches/8.14.3/transforms/488fe6e480efb97ec75859f6e274282f/transformed/fbjni-0.7.0/prefab/modules/fbjni/include"
},
{
"backtrace" : 4,
"isSystem" : true,
"path" : "/home/echo/.gradle/caches/8.14.3/transforms/e5d515112484fe4dddc05785469cc380/transformed/react-android-0.81.5-debug/prefab/modules/jsi/include"
},
{
"backtrace" : 4,
"isSystem" : true,
"path" : "/home/echo/.gradle/caches/8.14.3/transforms/e5d515112484fe4dddc05785469cc380/transformed/react-android-0.81.5-debug/prefab/modules/reactnative/include"
}
],
"language" : "CXX",
"sourceIndexes" :
[
0
],
"sysroot" :
{
"path" : "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/sysroot"
}
}
],
"id" : "appmodules::@6890427a1f51a3e7e1df",
"link" :
{
"commandFragments" :
[
{
"fragment" : "-Wl,--build-id=sha1 -Wl,--no-rosegment -Wl,--no-undefined-version -Wl,--fatal-warnings -Wl,--no-undefined -Qunused-arguments",
"role" : "flags"
},
{
"backtrace" : 4,
"fragment" : "/home/echo/.gradle/caches/8.14.3/transforms/488fe6e480efb97ec75859f6e274282f/transformed/fbjni-0.7.0/prefab/modules/fbjni/libs/android.x86/libfbjni.so",
"role" : "libraries"
},
{
"backtrace" : 4,
"fragment" : "/home/echo/.gradle/caches/8.14.3/transforms/e5d515112484fe4dddc05785469cc380/transformed/react-android-0.81.5-debug/prefab/modules/jsi/libs/android.x86/libjsi.so",
"role" : "libraries"
},
{
"backtrace" : 4,
"fragment" : "/home/echo/.gradle/caches/8.14.3/transforms/e5d515112484fe4dddc05785469cc380/transformed/react-android-0.81.5-debug/prefab/modules/reactnative/libs/android.x86/libreactnative.so",
"role" : "libraries"
},
{
"fragment" : "-latomic -lm",
"role" : "libraries"
}
],
"language" : "CXX",
"sysroot" :
{
"path" : "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/sysroot"
}
},
"name" : "appmodules",
"nameOnDisk" : "libappmodules.so",
"paths" :
{
"build" : ".",
"source" : "."
},
"sourceGroups" :
[
{
"name" : "Source Files",
"sourceIndexes" :
[
0
]
}
],
"sources" :
[
{
"backtrace" : 3,
"compileGroupIndex" : 0,
"path" : "OnLoad.cpp",
"sourceGroupIndex" : 0
}
],
"type" : "SHARED_LIBRARY"
}
@@ -1,420 +0,0 @@
# This is the CMakeCache file.
# For build in directory: /home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86
# It was generated by CMake: /home/echo/Android/Sdk/cmake/3.22.1/bin/cmake
# You can edit this file to change values found and used by cmake.
# If you do not want to change any of the values, simply exit the editor.
# If you do want to change a value, simply edit, save, and exit the editor.
# The syntax for the file is as follows:
# KEY:TYPE=VALUE
# KEY is the name of a variable in the cache.
# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!.
# VALUE is the current value for the KEY.
########################
# EXTERNAL cache entries
########################
//No help, variable specified on the command line.
ANDROID_ABI:UNINITIALIZED=x86
//No help, variable specified on the command line.
ANDROID_NDK:UNINITIALIZED=/home/echo/Android/Sdk/ndk/27.1.12297006
//No help, variable specified on the command line.
ANDROID_PLATFORM:UNINITIALIZED=android-26
//No help, variable specified on the command line.
ANDROID_STL:UNINITIALIZED=c++_shared
//No help, variable specified on the command line.
ANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES:UNINITIALIZED=ON
//Path to a program.
CCACHE_FOUND:FILEPATH=CCACHE_FOUND-NOTFOUND
//Path to a program.
CMAKE_ADDR2LINE:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-addr2line
//No help, variable specified on the command line.
CMAKE_ANDROID_ARCH_ABI:UNINITIALIZED=x86
//No help, variable specified on the command line.
CMAKE_ANDROID_NDK:UNINITIALIZED=/home/echo/Android/Sdk/ndk/27.1.12297006
//Archiver
CMAKE_AR:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar
//Flags used by the compiler during all build types.
CMAKE_ASM_FLAGS:STRING=
//Flags used by the compiler during debug builds.
CMAKE_ASM_FLAGS_DEBUG:STRING=
//Flags used by the compiler during release builds.
CMAKE_ASM_FLAGS_RELEASE:STRING=
//Choose the type of build, options are: None Debug Release RelWithDebInfo
// MinSizeRel ...
CMAKE_BUILD_TYPE:STRING=Debug
//LLVM archiver
CMAKE_CXX_COMPILER_AR:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar
//Generate index for LLVM archive
CMAKE_CXX_COMPILER_RANLIB:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ranlib
//Flags used by the compiler during all build types.
CMAKE_CXX_FLAGS:STRING=
//Flags used by the compiler during debug builds.
CMAKE_CXX_FLAGS_DEBUG:STRING=
//Flags used by the CXX compiler during MINSIZEREL builds.
CMAKE_CXX_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG
//Flags used by the compiler during release builds.
CMAKE_CXX_FLAGS_RELEASE:STRING=
//Flags used by the CXX compiler during RELWITHDEBINFO builds.
CMAKE_CXX_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG
//Libraries linked by default with all C++ applications.
CMAKE_CXX_STANDARD_LIBRARIES:STRING=-latomic -lm
//LLVM archiver
CMAKE_C_COMPILER_AR:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar
//Generate index for LLVM archive
CMAKE_C_COMPILER_RANLIB:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ranlib
//Flags used by the compiler during all build types.
CMAKE_C_FLAGS:STRING=
//Flags used by the compiler during debug builds.
CMAKE_C_FLAGS_DEBUG:STRING=
//Flags used by the C compiler during MINSIZEREL builds.
CMAKE_C_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG
//Flags used by the compiler during release builds.
CMAKE_C_FLAGS_RELEASE:STRING=
//Flags used by the C compiler during RELWITHDEBINFO builds.
CMAKE_C_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG
//Libraries linked by default with all C applications.
CMAKE_C_STANDARD_LIBRARIES:STRING=-latomic -lm
//Path to a program.
CMAKE_DLLTOOL:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-dlltool
//Flags used by the linker.
CMAKE_EXE_LINKER_FLAGS:STRING=
//Flags used by the linker during DEBUG builds.
CMAKE_EXE_LINKER_FLAGS_DEBUG:STRING=
//Flags used by the linker during MINSIZEREL builds.
CMAKE_EXE_LINKER_FLAGS_MINSIZEREL:STRING=
//Flags used by the linker during RELEASE builds.
CMAKE_EXE_LINKER_FLAGS_RELEASE:STRING=
//Flags used by the linker during RELWITHDEBINFO builds.
CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO:STRING=
//No help, variable specified on the command line.
CMAKE_EXPORT_COMPILE_COMMANDS:UNINITIALIZED=ON
//No help, variable specified on the command line.
CMAKE_FIND_ROOT_PATH:UNINITIALIZED=/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/prefab/x86/prefab
//Install path prefix, prepended onto install directories.
CMAKE_INSTALL_PREFIX:PATH=/usr/local
//No help, variable specified on the command line.
CMAKE_LIBRARY_OUTPUT_DIRECTORY:UNINITIALIZED=/home/echo/dev/prototype/apps/mobile/android/app/build/intermediates/cxx/Debug/2z472e3c/obj/x86
//Path to a program.
CMAKE_LINKER:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/ld.lld
//No help, variable specified on the command line.
CMAKE_MAKE_PROGRAM:UNINITIALIZED=/home/echo/Android/Sdk/cmake/3.22.1/bin/ninja
//Flags used by the linker during the creation of modules.
CMAKE_MODULE_LINKER_FLAGS:STRING=
//Flags used by the linker during the creation of modules during
// DEBUG builds.
CMAKE_MODULE_LINKER_FLAGS_DEBUG:STRING=
//Flags used by the linker during the creation of modules during
// MINSIZEREL builds.
CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL:STRING=
//Flags used by the linker during the creation of modules during
// RELEASE builds.
CMAKE_MODULE_LINKER_FLAGS_RELEASE:STRING=
//Flags used by the linker during the creation of modules during
// RELWITHDEBINFO builds.
CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO:STRING=
//Path to a program.
CMAKE_NM:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-nm
//Path to a program.
CMAKE_OBJCOPY:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-objcopy
//Path to a program.
CMAKE_OBJDUMP:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-objdump
//Value Computed by CMake
CMAKE_PROJECT_DESCRIPTION:STATIC=
//Value Computed by CMake
CMAKE_PROJECT_HOMEPAGE_URL:STATIC=
//Value Computed by CMake
CMAKE_PROJECT_NAME:STATIC=appmodules
//Ranlib
CMAKE_RANLIB:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ranlib
//Path to a program.
CMAKE_READELF:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-readelf
//No help, variable specified on the command line.
CMAKE_RUNTIME_OUTPUT_DIRECTORY:UNINITIALIZED=/home/echo/dev/prototype/apps/mobile/android/app/build/intermediates/cxx/Debug/2z472e3c/obj/x86
//Flags used by the linker during the creation of dll's.
CMAKE_SHARED_LINKER_FLAGS:STRING=
//Flags used by the linker during the creation of shared libraries
// during DEBUG builds.
CMAKE_SHARED_LINKER_FLAGS_DEBUG:STRING=
//Flags used by the linker during the creation of shared libraries
// during MINSIZEREL builds.
CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL:STRING=
//Flags used by the linker during the creation of shared libraries
// during RELEASE builds.
CMAKE_SHARED_LINKER_FLAGS_RELEASE:STRING=
//Flags used by the linker during the creation of shared libraries
// during RELWITHDEBINFO builds.
CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO:STRING=
//If set, runtime paths are not added when installing shared libraries,
// but are added when building.
CMAKE_SKIP_INSTALL_RPATH:BOOL=NO
//If set, runtime paths are not added when using shared libraries.
CMAKE_SKIP_RPATH:BOOL=NO
//Flags used by the linker during the creation of static libraries
// during all build types.
CMAKE_STATIC_LINKER_FLAGS:STRING=
//Flags used by the linker during the creation of static libraries
// during DEBUG builds.
CMAKE_STATIC_LINKER_FLAGS_DEBUG:STRING=
//Flags used by the linker during the creation of static libraries
// during MINSIZEREL builds.
CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL:STRING=
//Flags used by the linker during the creation of static libraries
// during RELEASE builds.
CMAKE_STATIC_LINKER_FLAGS_RELEASE:STRING=
//Flags used by the linker during the creation of static libraries
// during RELWITHDEBINFO builds.
CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO:STRING=
//Strip
CMAKE_STRIP:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip
//No help, variable specified on the command line.
CMAKE_SYSTEM_NAME:UNINITIALIZED=Android
//No help, variable specified on the command line.
CMAKE_SYSTEM_VERSION:UNINITIALIZED=26
//The CMake toolchain file
CMAKE_TOOLCHAIN_FILE:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/build/cmake/android.toolchain.cmake
//If this value is on, makefiles will be generated without the
// .SILENT directive, and all commands will be echoed to the console
// during the make. This is useful for debugging only. With Visual
// Studio IDE projects all commands are done without /nologo.
CMAKE_VERBOSE_MAKEFILE:BOOL=FALSE
//No help, variable specified on the command line.
PROJECT_BUILD_DIR:UNINITIALIZED=/home/echo/dev/prototype/apps/mobile/android/app/build
//No help, variable specified on the command line.
PROJECT_ROOT_DIR:UNINITIALIZED=/home/echo/dev/prototype/apps/mobile/android
//No help, variable specified on the command line.
REACT_ANDROID_DIR:UNINITIALIZED=/home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactAndroid
//The directory containing a CMake configuration file for ReactAndroid.
ReactAndroid_DIR:PATH=/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/prefab/x86/prefab/lib/i686-linux-android/cmake/ReactAndroid
//Value Computed by CMake
appmodules_BINARY_DIR:STATIC=/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86
//Value Computed by CMake
appmodules_IS_TOP_LEVEL:STATIC=ON
//Value Computed by CMake
appmodules_SOURCE_DIR:STATIC=/home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactAndroid/cmake-utils/default-app-setup
//The directory containing a CMake configuration file for fbjni.
fbjni_DIR:PATH=/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/prefab/x86/prefab/lib/i686-linux-android/cmake/fbjni
########################
# INTERNAL cache entries
########################
//ADVANCED property for variable: CMAKE_ADDR2LINE
CMAKE_ADDR2LINE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_AR
CMAKE_AR-ADVANCED:INTERNAL=1
//This is the directory where this CMakeCache.txt was created
CMAKE_CACHEFILE_DIR:INTERNAL=/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86
//Major version of cmake used to create the current loaded cache
CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3
//Minor version of cmake used to create the current loaded cache
CMAKE_CACHE_MINOR_VERSION:INTERNAL=22
//Patch version of cmake used to create the current loaded cache
CMAKE_CACHE_PATCH_VERSION:INTERNAL=1
//Path to CMake executable.
CMAKE_COMMAND:INTERNAL=/home/echo/Android/Sdk/cmake/3.22.1/bin/cmake
//Path to cpack program executable.
CMAKE_CPACK_COMMAND:INTERNAL=/home/echo/Android/Sdk/cmake/3.22.1/bin/cpack
//Path to ctest program executable.
CMAKE_CTEST_COMMAND:INTERNAL=/home/echo/Android/Sdk/cmake/3.22.1/bin/ctest
//ADVANCED property for variable: CMAKE_CXX_COMPILER_AR
CMAKE_CXX_COMPILER_AR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_COMPILER_RANLIB
CMAKE_CXX_COMPILER_RANLIB-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_FLAGS
CMAKE_CXX_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_FLAGS_DEBUG
CMAKE_CXX_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_FLAGS_MINSIZEREL
CMAKE_CXX_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELWITHDEBINFO
CMAKE_CXX_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_STANDARD_LIBRARIES
CMAKE_CXX_STANDARD_LIBRARIES-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_COMPILER_AR
CMAKE_C_COMPILER_AR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_COMPILER_RANLIB
CMAKE_C_COMPILER_RANLIB-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_FLAGS
CMAKE_C_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_FLAGS_DEBUG
CMAKE_C_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_FLAGS_MINSIZEREL
CMAKE_C_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_FLAGS_RELEASE
CMAKE_C_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_FLAGS_RELWITHDEBINFO
CMAKE_C_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_STANDARD_LIBRARIES
CMAKE_C_STANDARD_LIBRARIES-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_DLLTOOL
CMAKE_DLLTOOL-ADVANCED:INTERNAL=1
//Executable file format
CMAKE_EXECUTABLE_FORMAT:INTERNAL=ELF
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS
CMAKE_EXE_LINKER_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_DEBUG
CMAKE_EXE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_MINSIZEREL
CMAKE_EXE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELEASE
CMAKE_EXE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO
CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//Name of external makefile project generator.
CMAKE_EXTRA_GENERATOR:INTERNAL=
//Name of generator.
CMAKE_GENERATOR:INTERNAL=Ninja
//Generator instance identifier.
CMAKE_GENERATOR_INSTANCE:INTERNAL=
//Name of generator platform.
CMAKE_GENERATOR_PLATFORM:INTERNAL=
//Name of generator toolset.
CMAKE_GENERATOR_TOOLSET:INTERNAL=
//Source directory with the top level CMakeLists.txt file for this
// project
CMAKE_HOME_DIRECTORY:INTERNAL=/home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactAndroid/cmake-utils/default-app-setup
//Install .so files without execute permission.
CMAKE_INSTALL_SO_NO_EXE:INTERNAL=0
//ADVANCED property for variable: CMAKE_LINKER
CMAKE_LINKER-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS
CMAKE_MODULE_LINKER_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_DEBUG
CMAKE_MODULE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL
CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELEASE
CMAKE_MODULE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO
CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_NM
CMAKE_NM-ADVANCED:INTERNAL=1
//number of local generators
CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1
//ADVANCED property for variable: CMAKE_OBJCOPY
CMAKE_OBJCOPY-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_OBJDUMP
CMAKE_OBJDUMP-ADVANCED:INTERNAL=1
//Platform information initialized
CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1
//ADVANCED property for variable: CMAKE_RANLIB
CMAKE_RANLIB-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_READELF
CMAKE_READELF-ADVANCED:INTERNAL=1
//Path to CMake installation.
CMAKE_ROOT:INTERNAL=/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS
CMAKE_SHARED_LINKER_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_DEBUG
CMAKE_SHARED_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL
CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELEASE
CMAKE_SHARED_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO
CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SKIP_INSTALL_RPATH
CMAKE_SKIP_INSTALL_RPATH-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SKIP_RPATH
CMAKE_SKIP_RPATH-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS
CMAKE_STATIC_LINKER_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_DEBUG
CMAKE_STATIC_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL
CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELEASE
CMAKE_STATIC_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO
CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STRIP
CMAKE_STRIP-ADVANCED:INTERNAL=1
//uname command
CMAKE_UNAME:INTERNAL=/usr/bin/uname
//ADVANCED property for variable: CMAKE_VERBOSE_MAKEFILE
CMAKE_VERBOSE_MAKEFILE-ADVANCED:INTERNAL=1
@@ -1,72 +0,0 @@
set(CMAKE_C_COMPILER "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/clang")
set(CMAKE_C_COMPILER_ARG1 "")
set(CMAKE_C_COMPILER_ID "Clang")
set(CMAKE_C_COMPILER_VERSION "18.0.2")
set(CMAKE_C_COMPILER_VERSION_INTERNAL "")
set(CMAKE_C_COMPILER_WRAPPER "")
set(CMAKE_C_STANDARD_COMPUTED_DEFAULT "17")
set(CMAKE_C_EXTENSIONS_COMPUTED_DEFAULT "ON")
set(CMAKE_C_COMPILE_FEATURES "c_std_90;c_function_prototypes;c_std_99;c_restrict;c_variadic_macros;c_std_11;c_static_assert;c_std_17;c_std_23")
set(CMAKE_C90_COMPILE_FEATURES "c_std_90;c_function_prototypes")
set(CMAKE_C99_COMPILE_FEATURES "c_std_99;c_restrict;c_variadic_macros")
set(CMAKE_C11_COMPILE_FEATURES "c_std_11;c_static_assert")
set(CMAKE_C17_COMPILE_FEATURES "c_std_17")
set(CMAKE_C23_COMPILE_FEATURES "c_std_23")
set(CMAKE_C_PLATFORM_ID "Linux")
set(CMAKE_C_SIMULATE_ID "")
set(CMAKE_C_COMPILER_FRONTEND_VARIANT "GNU")
set(CMAKE_C_SIMULATE_VERSION "")
set(CMAKE_AR "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar")
set(CMAKE_C_COMPILER_AR "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar")
set(CMAKE_RANLIB "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ranlib")
set(CMAKE_C_COMPILER_RANLIB "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ranlib")
set(CMAKE_LINKER "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/ld.lld")
set(CMAKE_MT "")
set(CMAKE_COMPILER_IS_GNUCC )
set(CMAKE_C_COMPILER_LOADED 1)
set(CMAKE_C_COMPILER_WORKS TRUE)
set(CMAKE_C_ABI_COMPILED TRUE)
set(CMAKE_C_COMPILER_ENV_VAR "CC")
set(CMAKE_C_COMPILER_ID_RUN 1)
set(CMAKE_C_SOURCE_FILE_EXTENSIONS c;m)
set(CMAKE_C_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC)
set(CMAKE_C_LINKER_PREFERENCE 10)
# Save compiler ABI information.
set(CMAKE_C_SIZEOF_DATA_PTR "4")
set(CMAKE_C_COMPILER_ABI "ELF")
set(CMAKE_C_BYTE_ORDER "LITTLE_ENDIAN")
set(CMAKE_C_LIBRARY_ARCHITECTURE "")
if(CMAKE_C_SIZEOF_DATA_PTR)
set(CMAKE_SIZEOF_VOID_P "${CMAKE_C_SIZEOF_DATA_PTR}")
endif()
if(CMAKE_C_COMPILER_ABI)
set(CMAKE_INTERNAL_PLATFORM_ABI "${CMAKE_C_COMPILER_ABI}")
endif()
if(CMAKE_C_LIBRARY_ARCHITECTURE)
set(CMAKE_LIBRARY_ARCHITECTURE "")
endif()
set(CMAKE_C_CL_SHOWINCLUDES_PREFIX "")
if(CMAKE_C_CL_SHOWINCLUDES_PREFIX)
set(CMAKE_CL_SHOWINCLUDES_PREFIX "${CMAKE_C_CL_SHOWINCLUDES_PREFIX}")
endif()
set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/18/include;/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/i686-linux-android;/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include")
set(CMAKE_C_IMPLICIT_LINK_LIBRARIES "-l:libunwind.a;dl;c;-l:libunwind.a;dl")
set(CMAKE_C_IMPLICIT_LINK_DIRECTORIES "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/18/lib/linux/i386;/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/i686-linux-android/26;/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/i686-linux-android;/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib")
set(CMAKE_C_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES "")
@@ -1,83 +0,0 @@
set(CMAKE_CXX_COMPILER "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/clang++")
set(CMAKE_CXX_COMPILER_ARG1 "")
set(CMAKE_CXX_COMPILER_ID "Clang")
set(CMAKE_CXX_COMPILER_VERSION "18.0.2")
set(CMAKE_CXX_COMPILER_VERSION_INTERNAL "")
set(CMAKE_CXX_COMPILER_WRAPPER "")
set(CMAKE_CXX_STANDARD_COMPUTED_DEFAULT "17")
set(CMAKE_CXX_EXTENSIONS_COMPUTED_DEFAULT "ON")
set(CMAKE_CXX_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters;cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates;cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates;cxx_std_17;cxx_std_20;cxx_std_23")
set(CMAKE_CXX98_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters")
set(CMAKE_CXX11_COMPILE_FEATURES "cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates")
set(CMAKE_CXX14_COMPILE_FEATURES "cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates")
set(CMAKE_CXX17_COMPILE_FEATURES "cxx_std_17")
set(CMAKE_CXX20_COMPILE_FEATURES "cxx_std_20")
set(CMAKE_CXX23_COMPILE_FEATURES "cxx_std_23")
set(CMAKE_CXX_PLATFORM_ID "Linux")
set(CMAKE_CXX_SIMULATE_ID "")
set(CMAKE_CXX_COMPILER_FRONTEND_VARIANT "GNU")
set(CMAKE_CXX_SIMULATE_VERSION "")
set(CMAKE_AR "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar")
set(CMAKE_CXX_COMPILER_AR "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar")
set(CMAKE_RANLIB "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ranlib")
set(CMAKE_CXX_COMPILER_RANLIB "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ranlib")
set(CMAKE_LINKER "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/ld.lld")
set(CMAKE_MT "")
set(CMAKE_COMPILER_IS_GNUCXX )
set(CMAKE_CXX_COMPILER_LOADED 1)
set(CMAKE_CXX_COMPILER_WORKS TRUE)
set(CMAKE_CXX_ABI_COMPILED TRUE)
set(CMAKE_CXX_COMPILER_ENV_VAR "CXX")
set(CMAKE_CXX_COMPILER_ID_RUN 1)
set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS C;M;c++;cc;cpp;cxx;m;mm;mpp;CPP;ixx;cppm)
set(CMAKE_CXX_IGNORE_EXTENSIONS inl;h;hpp;HPP;H;o;O;obj;OBJ;def;DEF;rc;RC)
foreach (lang C OBJC OBJCXX)
if (CMAKE_${lang}_COMPILER_ID_RUN)
foreach(extension IN LISTS CMAKE_${lang}_SOURCE_FILE_EXTENSIONS)
list(REMOVE_ITEM CMAKE_CXX_SOURCE_FILE_EXTENSIONS ${extension})
endforeach()
endif()
endforeach()
set(CMAKE_CXX_LINKER_PREFERENCE 30)
set(CMAKE_CXX_LINKER_PREFERENCE_PROPAGATES 1)
# Save compiler ABI information.
set(CMAKE_CXX_SIZEOF_DATA_PTR "4")
set(CMAKE_CXX_COMPILER_ABI "ELF")
set(CMAKE_CXX_BYTE_ORDER "LITTLE_ENDIAN")
set(CMAKE_CXX_LIBRARY_ARCHITECTURE "")
if(CMAKE_CXX_SIZEOF_DATA_PTR)
set(CMAKE_SIZEOF_VOID_P "${CMAKE_CXX_SIZEOF_DATA_PTR}")
endif()
if(CMAKE_CXX_COMPILER_ABI)
set(CMAKE_INTERNAL_PLATFORM_ABI "${CMAKE_CXX_COMPILER_ABI}")
endif()
if(CMAKE_CXX_LIBRARY_ARCHITECTURE)
set(CMAKE_LIBRARY_ARCHITECTURE "")
endif()
set(CMAKE_CXX_CL_SHOWINCLUDES_PREFIX "")
if(CMAKE_CXX_CL_SHOWINCLUDES_PREFIX)
set(CMAKE_CL_SHOWINCLUDES_PREFIX "${CMAKE_CXX_CL_SHOWINCLUDES_PREFIX}")
endif()
set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/c++/v1;/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/18/include;/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/i686-linux-android;/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include")
set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "c++;m;-l:libunwind.a;dl;c;-l:libunwind.a;dl")
set(CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES "/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/18/lib/linux/i386;/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/i686-linux-android/26;/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/i686-linux-android;/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib")
set(CMAKE_CXX_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES "")
@@ -1,15 +0,0 @@
set(CMAKE_HOST_SYSTEM "Linux-6.17.8-arch1-1")
set(CMAKE_HOST_SYSTEM_NAME "Linux")
set(CMAKE_HOST_SYSTEM_VERSION "6.17.8-arch1-1")
set(CMAKE_HOST_SYSTEM_PROCESSOR "x86_64")
include("/home/echo/Android/Sdk/ndk/27.1.12297006/build/cmake/android.toolchain.cmake")
set(CMAKE_SYSTEM "Android-1")
set(CMAKE_SYSTEM_NAME "Android")
set(CMAKE_SYSTEM_VERSION "1")
set(CMAKE_SYSTEM_PROCESSOR "i686")
set(CMAKE_CROSSCOMPILING "TRUE")
set(CMAKE_SYSTEM_LOADED 1)
@@ -1,803 +0,0 @@
#ifdef __cplusplus
# error "A C++ compiler has been selected for C."
#endif
#if defined(__18CXX)
# define ID_VOID_MAIN
#endif
#if defined(__CLASSIC_C__)
/* cv-qualifiers did not exist in K&R C */
# define const
# define volatile
#endif
#if !defined(__has_include)
/* If the compiler does not have __has_include, pretend the answer is
always no. */
# define __has_include(x) 0
#endif
/* Version number components: V=Version, R=Revision, P=Patch
Version date components: YYYY=Year, MM=Month, DD=Day */
#if defined(__INTEL_COMPILER) || defined(__ICC)
# define COMPILER_ID "Intel"
# if defined(_MSC_VER)
# define SIMULATE_ID "MSVC"
# endif
# if defined(__GNUC__)
# define SIMULATE_ID "GNU"
# endif
/* __INTEL_COMPILER = VRP prior to 2021, and then VVVV for 2021 and later,
except that a few beta releases use the old format with V=2021. */
# if __INTEL_COMPILER < 2021 || __INTEL_COMPILER == 202110 || __INTEL_COMPILER == 202111
# define COMPILER_VERSION_MAJOR DEC(__INTEL_COMPILER/100)
# define COMPILER_VERSION_MINOR DEC(__INTEL_COMPILER/10 % 10)
# if defined(__INTEL_COMPILER_UPDATE)
# define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER_UPDATE)
# else
# define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER % 10)
# endif
# else
# define COMPILER_VERSION_MAJOR DEC(__INTEL_COMPILER)
# define COMPILER_VERSION_MINOR DEC(__INTEL_COMPILER_UPDATE)
/* The third version component from --version is an update index,
but no macro is provided for it. */
# define COMPILER_VERSION_PATCH DEC(0)
# endif
# if defined(__INTEL_COMPILER_BUILD_DATE)
/* __INTEL_COMPILER_BUILD_DATE = YYYYMMDD */
# define COMPILER_VERSION_TWEAK DEC(__INTEL_COMPILER_BUILD_DATE)
# endif
# if defined(_MSC_VER)
/* _MSC_VER = VVRR */
# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100)
# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100)
# endif
# if defined(__GNUC__)
# define SIMULATE_VERSION_MAJOR DEC(__GNUC__)
# elif defined(__GNUG__)
# define SIMULATE_VERSION_MAJOR DEC(__GNUG__)
# endif
# if defined(__GNUC_MINOR__)
# define SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__)
# endif
# if defined(__GNUC_PATCHLEVEL__)
# define SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__)
# endif
#elif (defined(__clang__) && defined(__INTEL_CLANG_COMPILER)) || defined(__INTEL_LLVM_COMPILER)
# define COMPILER_ID "IntelLLVM"
#if defined(_MSC_VER)
# define SIMULATE_ID "MSVC"
#endif
#if defined(__GNUC__)
# define SIMULATE_ID "GNU"
#endif
/* __INTEL_LLVM_COMPILER = VVVVRP prior to 2021.2.0, VVVVRRPP for 2021.2.0 and
* later. Look for 6 digit vs. 8 digit version number to decide encoding.
* VVVV is no smaller than the current year when a version is released.
*/
#if __INTEL_LLVM_COMPILER < 1000000L
# define COMPILER_VERSION_MAJOR DEC(__INTEL_LLVM_COMPILER/100)
# define COMPILER_VERSION_MINOR DEC(__INTEL_LLVM_COMPILER/10 % 10)
# define COMPILER_VERSION_PATCH DEC(__INTEL_LLVM_COMPILER % 10)
#else
# define COMPILER_VERSION_MAJOR DEC(__INTEL_LLVM_COMPILER/10000)
# define COMPILER_VERSION_MINOR DEC(__INTEL_LLVM_COMPILER/100 % 100)
# define COMPILER_VERSION_PATCH DEC(__INTEL_LLVM_COMPILER % 100)
#endif
#if defined(_MSC_VER)
/* _MSC_VER = VVRR */
# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100)
# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100)
#endif
#if defined(__GNUC__)
# define SIMULATE_VERSION_MAJOR DEC(__GNUC__)
#elif defined(__GNUG__)
# define SIMULATE_VERSION_MAJOR DEC(__GNUG__)
#endif
#if defined(__GNUC_MINOR__)
# define SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__)
#endif
#if defined(__GNUC_PATCHLEVEL__)
# define SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__)
#endif
#elif defined(__PATHCC__)
# define COMPILER_ID "PathScale"
# define COMPILER_VERSION_MAJOR DEC(__PATHCC__)
# define COMPILER_VERSION_MINOR DEC(__PATHCC_MINOR__)
# if defined(__PATHCC_PATCHLEVEL__)
# define COMPILER_VERSION_PATCH DEC(__PATHCC_PATCHLEVEL__)
# endif
#elif defined(__BORLANDC__) && defined(__CODEGEARC_VERSION__)
# define COMPILER_ID "Embarcadero"
# define COMPILER_VERSION_MAJOR HEX(__CODEGEARC_VERSION__>>24 & 0x00FF)
# define COMPILER_VERSION_MINOR HEX(__CODEGEARC_VERSION__>>16 & 0x00FF)
# define COMPILER_VERSION_PATCH DEC(__CODEGEARC_VERSION__ & 0xFFFF)
#elif defined(__BORLANDC__)
# define COMPILER_ID "Borland"
/* __BORLANDC__ = 0xVRR */
# define COMPILER_VERSION_MAJOR HEX(__BORLANDC__>>8)
# define COMPILER_VERSION_MINOR HEX(__BORLANDC__ & 0xFF)
#elif defined(__WATCOMC__) && __WATCOMC__ < 1200
# define COMPILER_ID "Watcom"
/* __WATCOMC__ = VVRR */
# define COMPILER_VERSION_MAJOR DEC(__WATCOMC__ / 100)
# define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10)
# if (__WATCOMC__ % 10) > 0
# define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10)
# endif
#elif defined(__WATCOMC__)
# define COMPILER_ID "OpenWatcom"
/* __WATCOMC__ = VVRP + 1100 */
# define COMPILER_VERSION_MAJOR DEC((__WATCOMC__ - 1100) / 100)
# define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10)
# if (__WATCOMC__ % 10) > 0
# define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10)
# endif
#elif defined(__SUNPRO_C)
# define COMPILER_ID "SunPro"
# if __SUNPRO_C >= 0x5100
/* __SUNPRO_C = 0xVRRP */
# define COMPILER_VERSION_MAJOR HEX(__SUNPRO_C>>12)
# define COMPILER_VERSION_MINOR HEX(__SUNPRO_C>>4 & 0xFF)
# define COMPILER_VERSION_PATCH HEX(__SUNPRO_C & 0xF)
# else
/* __SUNPRO_CC = 0xVRP */
# define COMPILER_VERSION_MAJOR HEX(__SUNPRO_C>>8)
# define COMPILER_VERSION_MINOR HEX(__SUNPRO_C>>4 & 0xF)
# define COMPILER_VERSION_PATCH HEX(__SUNPRO_C & 0xF)
# endif
#elif defined(__HP_cc)
# define COMPILER_ID "HP"
/* __HP_cc = VVRRPP */
# define COMPILER_VERSION_MAJOR DEC(__HP_cc/10000)
# define COMPILER_VERSION_MINOR DEC(__HP_cc/100 % 100)
# define COMPILER_VERSION_PATCH DEC(__HP_cc % 100)
#elif defined(__DECC)
# define COMPILER_ID "Compaq"
/* __DECC_VER = VVRRTPPPP */
# define COMPILER_VERSION_MAJOR DEC(__DECC_VER/10000000)
# define COMPILER_VERSION_MINOR DEC(__DECC_VER/100000 % 100)
# define COMPILER_VERSION_PATCH DEC(__DECC_VER % 10000)
#elif defined(__IBMC__) && defined(__COMPILER_VER__)
# define COMPILER_ID "zOS"
/* __IBMC__ = VRP */
# define COMPILER_VERSION_MAJOR DEC(__IBMC__/100)
# define COMPILER_VERSION_MINOR DEC(__IBMC__/10 % 10)
# define COMPILER_VERSION_PATCH DEC(__IBMC__ % 10)
#elif defined(__ibmxl__) && defined(__clang__)
# define COMPILER_ID "XLClang"
# define COMPILER_VERSION_MAJOR DEC(__ibmxl_version__)
# define COMPILER_VERSION_MINOR DEC(__ibmxl_release__)
# define COMPILER_VERSION_PATCH DEC(__ibmxl_modification__)
# define COMPILER_VERSION_TWEAK DEC(__ibmxl_ptf_fix_level__)
#elif defined(__IBMC__) && !defined(__COMPILER_VER__) && __IBMC__ >= 800
# define COMPILER_ID "XL"
/* __IBMC__ = VRP */
# define COMPILER_VERSION_MAJOR DEC(__IBMC__/100)
# define COMPILER_VERSION_MINOR DEC(__IBMC__/10 % 10)
# define COMPILER_VERSION_PATCH DEC(__IBMC__ % 10)
#elif defined(__IBMC__) && !defined(__COMPILER_VER__) && __IBMC__ < 800
# define COMPILER_ID "VisualAge"
/* __IBMC__ = VRP */
# define COMPILER_VERSION_MAJOR DEC(__IBMC__/100)
# define COMPILER_VERSION_MINOR DEC(__IBMC__/10 % 10)
# define COMPILER_VERSION_PATCH DEC(__IBMC__ % 10)
#elif defined(__NVCOMPILER)
# define COMPILER_ID "NVHPC"
# define COMPILER_VERSION_MAJOR DEC(__NVCOMPILER_MAJOR__)
# define COMPILER_VERSION_MINOR DEC(__NVCOMPILER_MINOR__)
# if defined(__NVCOMPILER_PATCHLEVEL__)
# define COMPILER_VERSION_PATCH DEC(__NVCOMPILER_PATCHLEVEL__)
# endif
#elif defined(__PGI)
# define COMPILER_ID "PGI"
# define COMPILER_VERSION_MAJOR DEC(__PGIC__)
# define COMPILER_VERSION_MINOR DEC(__PGIC_MINOR__)
# if defined(__PGIC_PATCHLEVEL__)
# define COMPILER_VERSION_PATCH DEC(__PGIC_PATCHLEVEL__)
# endif
#elif defined(_CRAYC)
# define COMPILER_ID "Cray"
# define COMPILER_VERSION_MAJOR DEC(_RELEASE_MAJOR)
# define COMPILER_VERSION_MINOR DEC(_RELEASE_MINOR)
#elif defined(__TI_COMPILER_VERSION__)
# define COMPILER_ID "TI"
/* __TI_COMPILER_VERSION__ = VVVRRRPPP */
# define COMPILER_VERSION_MAJOR DEC(__TI_COMPILER_VERSION__/1000000)
# define COMPILER_VERSION_MINOR DEC(__TI_COMPILER_VERSION__/1000 % 1000)
# define COMPILER_VERSION_PATCH DEC(__TI_COMPILER_VERSION__ % 1000)
#elif defined(__CLANG_FUJITSU)
# define COMPILER_ID "FujitsuClang"
# define COMPILER_VERSION_MAJOR DEC(__FCC_major__)
# define COMPILER_VERSION_MINOR DEC(__FCC_minor__)
# define COMPILER_VERSION_PATCH DEC(__FCC_patchlevel__)
# define COMPILER_VERSION_INTERNAL_STR __clang_version__
#elif defined(__FUJITSU)
# define COMPILER_ID "Fujitsu"
# if defined(__FCC_version__)
# define COMPILER_VERSION __FCC_version__
# elif defined(__FCC_major__)
# define COMPILER_VERSION_MAJOR DEC(__FCC_major__)
# define COMPILER_VERSION_MINOR DEC(__FCC_minor__)
# define COMPILER_VERSION_PATCH DEC(__FCC_patchlevel__)
# endif
# if defined(__fcc_version)
# define COMPILER_VERSION_INTERNAL DEC(__fcc_version)
# elif defined(__FCC_VERSION)
# define COMPILER_VERSION_INTERNAL DEC(__FCC_VERSION)
# endif
#elif defined(__ghs__)
# define COMPILER_ID "GHS"
/* __GHS_VERSION_NUMBER = VVVVRP */
# ifdef __GHS_VERSION_NUMBER
# define COMPILER_VERSION_MAJOR DEC(__GHS_VERSION_NUMBER / 100)
# define COMPILER_VERSION_MINOR DEC(__GHS_VERSION_NUMBER / 10 % 10)
# define COMPILER_VERSION_PATCH DEC(__GHS_VERSION_NUMBER % 10)
# endif
#elif defined(__TINYC__)
# define COMPILER_ID "TinyCC"
#elif defined(__BCC__)
# define COMPILER_ID "Bruce"
#elif defined(__SCO_VERSION__)
# define COMPILER_ID "SCO"
#elif defined(__ARMCC_VERSION) && !defined(__clang__)
# define COMPILER_ID "ARMCC"
#if __ARMCC_VERSION >= 1000000
/* __ARMCC_VERSION = VRRPPPP */
# define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION/1000000)
# define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION/10000 % 100)
# define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000)
#else
/* __ARMCC_VERSION = VRPPPP */
# define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION/100000)
# define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION/10000 % 10)
# define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000)
#endif
#elif defined(__clang__) && defined(__apple_build_version__)
# define COMPILER_ID "AppleClang"
# if defined(_MSC_VER)
# define SIMULATE_ID "MSVC"
# endif
# define COMPILER_VERSION_MAJOR DEC(__clang_major__)
# define COMPILER_VERSION_MINOR DEC(__clang_minor__)
# define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__)
# if defined(_MSC_VER)
/* _MSC_VER = VVRR */
# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100)
# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100)
# endif
# define COMPILER_VERSION_TWEAK DEC(__apple_build_version__)
#elif defined(__clang__) && defined(__ARMCOMPILER_VERSION)
# define COMPILER_ID "ARMClang"
# define COMPILER_VERSION_MAJOR DEC(__ARMCOMPILER_VERSION/1000000)
# define COMPILER_VERSION_MINOR DEC(__ARMCOMPILER_VERSION/10000 % 100)
# define COMPILER_VERSION_PATCH DEC(__ARMCOMPILER_VERSION % 10000)
# define COMPILER_VERSION_INTERNAL DEC(__ARMCOMPILER_VERSION)
#elif defined(__clang__)
# define COMPILER_ID "Clang"
# if defined(_MSC_VER)
# define SIMULATE_ID "MSVC"
# endif
# define COMPILER_VERSION_MAJOR DEC(__clang_major__)
# define COMPILER_VERSION_MINOR DEC(__clang_minor__)
# define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__)
# if defined(_MSC_VER)
/* _MSC_VER = VVRR */
# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100)
# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100)
# endif
#elif defined(__GNUC__)
# define COMPILER_ID "GNU"
# define COMPILER_VERSION_MAJOR DEC(__GNUC__)
# if defined(__GNUC_MINOR__)
# define COMPILER_VERSION_MINOR DEC(__GNUC_MINOR__)
# endif
# if defined(__GNUC_PATCHLEVEL__)
# define COMPILER_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__)
# endif
#elif defined(_MSC_VER)
# define COMPILER_ID "MSVC"
/* _MSC_VER = VVRR */
# define COMPILER_VERSION_MAJOR DEC(_MSC_VER / 100)
# define COMPILER_VERSION_MINOR DEC(_MSC_VER % 100)
# if defined(_MSC_FULL_VER)
# if _MSC_VER >= 1400
/* _MSC_FULL_VER = VVRRPPPPP */
# define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 100000)
# else
/* _MSC_FULL_VER = VVRRPPPP */
# define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 10000)
# endif
# endif
# if defined(_MSC_BUILD)
# define COMPILER_VERSION_TWEAK DEC(_MSC_BUILD)
# endif
#elif defined(__VISUALDSPVERSION__) || defined(__ADSPBLACKFIN__) || defined(__ADSPTS__) || defined(__ADSP21000__)
# define COMPILER_ID "ADSP"
#if defined(__VISUALDSPVERSION__)
/* __VISUALDSPVERSION__ = 0xVVRRPP00 */
# define COMPILER_VERSION_MAJOR HEX(__VISUALDSPVERSION__>>24)
# define COMPILER_VERSION_MINOR HEX(__VISUALDSPVERSION__>>16 & 0xFF)
# define COMPILER_VERSION_PATCH HEX(__VISUALDSPVERSION__>>8 & 0xFF)
#endif
#elif defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ICC)
# define COMPILER_ID "IAR"
# if defined(__VER__) && defined(__ICCARM__)
# define COMPILER_VERSION_MAJOR DEC((__VER__) / 1000000)
# define COMPILER_VERSION_MINOR DEC(((__VER__) / 1000) % 1000)
# define COMPILER_VERSION_PATCH DEC((__VER__) % 1000)
# define COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__)
# elif defined(__VER__) && (defined(__ICCAVR__) || defined(__ICCRX__) || defined(__ICCRH850__) || defined(__ICCRL78__) || defined(__ICC430__) || defined(__ICCRISCV__) || defined(__ICCV850__) || defined(__ICC8051__) || defined(__ICCSTM8__))
# define COMPILER_VERSION_MAJOR DEC((__VER__) / 100)
# define COMPILER_VERSION_MINOR DEC((__VER__) - (((__VER__) / 100)*100))
# define COMPILER_VERSION_PATCH DEC(__SUBVERSION__)
# define COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__)
# endif
#elif defined(__SDCC_VERSION_MAJOR) || defined(SDCC)
# define COMPILER_ID "SDCC"
# if defined(__SDCC_VERSION_MAJOR)
# define COMPILER_VERSION_MAJOR DEC(__SDCC_VERSION_MAJOR)
# define COMPILER_VERSION_MINOR DEC(__SDCC_VERSION_MINOR)
# define COMPILER_VERSION_PATCH DEC(__SDCC_VERSION_PATCH)
# else
/* SDCC = VRP */
# define COMPILER_VERSION_MAJOR DEC(SDCC/100)
# define COMPILER_VERSION_MINOR DEC(SDCC/10 % 10)
# define COMPILER_VERSION_PATCH DEC(SDCC % 10)
# endif
/* These compilers are either not known or too old to define an
identification macro. Try to identify the platform and guess that
it is the native compiler. */
#elif defined(__hpux) || defined(__hpua)
# define COMPILER_ID "HP"
#else /* unknown compiler */
# define COMPILER_ID ""
#endif
/* Construct the string literal in pieces to prevent the source from
getting matched. Store it in a pointer rather than an array
because some compilers will just produce instructions to fill the
array rather than assigning a pointer to a static array. */
char const* info_compiler = "INFO" ":" "compiler[" COMPILER_ID "]";
#ifdef SIMULATE_ID
char const* info_simulate = "INFO" ":" "simulate[" SIMULATE_ID "]";
#endif
#ifdef __QNXNTO__
char const* qnxnto = "INFO" ":" "qnxnto[]";
#endif
#if defined(__CRAYXT_COMPUTE_LINUX_TARGET)
char const *info_cray = "INFO" ":" "compiler_wrapper[CrayPrgEnv]";
#endif
#define STRINGIFY_HELPER(X) #X
#define STRINGIFY(X) STRINGIFY_HELPER(X)
/* Identify known platforms by name. */
#if defined(__linux) || defined(__linux__) || defined(linux)
# define PLATFORM_ID "Linux"
#elif defined(__MSYS__)
# define PLATFORM_ID "MSYS"
#elif defined(__CYGWIN__)
# define PLATFORM_ID "Cygwin"
#elif defined(__MINGW32__)
# define PLATFORM_ID "MinGW"
#elif defined(__APPLE__)
# define PLATFORM_ID "Darwin"
#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
# define PLATFORM_ID "Windows"
#elif defined(__FreeBSD__) || defined(__FreeBSD)
# define PLATFORM_ID "FreeBSD"
#elif defined(__NetBSD__) || defined(__NetBSD)
# define PLATFORM_ID "NetBSD"
#elif defined(__OpenBSD__) || defined(__OPENBSD)
# define PLATFORM_ID "OpenBSD"
#elif defined(__sun) || defined(sun)
# define PLATFORM_ID "SunOS"
#elif defined(_AIX) || defined(__AIX) || defined(__AIX__) || defined(__aix) || defined(__aix__)
# define PLATFORM_ID "AIX"
#elif defined(__hpux) || defined(__hpux__)
# define PLATFORM_ID "HP-UX"
#elif defined(__HAIKU__)
# define PLATFORM_ID "Haiku"
#elif defined(__BeOS) || defined(__BEOS__) || defined(_BEOS)
# define PLATFORM_ID "BeOS"
#elif defined(__QNX__) || defined(__QNXNTO__)
# define PLATFORM_ID "QNX"
#elif defined(__tru64) || defined(_tru64) || defined(__TRU64__)
# define PLATFORM_ID "Tru64"
#elif defined(__riscos) || defined(__riscos__)
# define PLATFORM_ID "RISCos"
#elif defined(__sinix) || defined(__sinix__) || defined(__SINIX__)
# define PLATFORM_ID "SINIX"
#elif defined(__UNIX_SV__)
# define PLATFORM_ID "UNIX_SV"
#elif defined(__bsdos__)
# define PLATFORM_ID "BSDOS"
#elif defined(_MPRAS) || defined(MPRAS)
# define PLATFORM_ID "MP-RAS"
#elif defined(__osf) || defined(__osf__)
# define PLATFORM_ID "OSF1"
#elif defined(_SCO_SV) || defined(SCO_SV) || defined(sco_sv)
# define PLATFORM_ID "SCO_SV"
#elif defined(__ultrix) || defined(__ultrix__) || defined(_ULTRIX)
# define PLATFORM_ID "ULTRIX"
#elif defined(__XENIX__) || defined(_XENIX) || defined(XENIX)
# define PLATFORM_ID "Xenix"
#elif defined(__WATCOMC__)
# if defined(__LINUX__)
# define PLATFORM_ID "Linux"
# elif defined(__DOS__)
# define PLATFORM_ID "DOS"
# elif defined(__OS2__)
# define PLATFORM_ID "OS2"
# elif defined(__WINDOWS__)
# define PLATFORM_ID "Windows3x"
# elif defined(__VXWORKS__)
# define PLATFORM_ID "VxWorks"
# else /* unknown platform */
# define PLATFORM_ID
# endif
#elif defined(__INTEGRITY)
# if defined(INT_178B)
# define PLATFORM_ID "Integrity178"
# else /* regular Integrity */
# define PLATFORM_ID "Integrity"
# endif
#else /* unknown platform */
# define PLATFORM_ID
#endif
/* For windows compilers MSVC and Intel we can determine
the architecture of the compiler being used. This is because
the compilers do not have flags that can change the architecture,
but rather depend on which compiler is being used
*/
#if defined(_WIN32) && defined(_MSC_VER)
# if defined(_M_IA64)
# define ARCHITECTURE_ID "IA64"
# elif defined(_M_ARM64EC)
# define ARCHITECTURE_ID "ARM64EC"
# elif defined(_M_X64) || defined(_M_AMD64)
# define ARCHITECTURE_ID "x64"
# elif defined(_M_IX86)
# define ARCHITECTURE_ID "X86"
# elif defined(_M_ARM64)
# define ARCHITECTURE_ID "ARM64"
# elif defined(_M_ARM)
# if _M_ARM == 4
# define ARCHITECTURE_ID "ARMV4I"
# elif _M_ARM == 5
# define ARCHITECTURE_ID "ARMV5I"
# else
# define ARCHITECTURE_ID "ARMV" STRINGIFY(_M_ARM)
# endif
# elif defined(_M_MIPS)
# define ARCHITECTURE_ID "MIPS"
# elif defined(_M_SH)
# define ARCHITECTURE_ID "SHx"
# else /* unknown architecture */
# define ARCHITECTURE_ID ""
# endif
#elif defined(__WATCOMC__)
# if defined(_M_I86)
# define ARCHITECTURE_ID "I86"
# elif defined(_M_IX86)
# define ARCHITECTURE_ID "X86"
# else /* unknown architecture */
# define ARCHITECTURE_ID ""
# endif
#elif defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ICC)
# if defined(__ICCARM__)
# define ARCHITECTURE_ID "ARM"
# elif defined(__ICCRX__)
# define ARCHITECTURE_ID "RX"
# elif defined(__ICCRH850__)
# define ARCHITECTURE_ID "RH850"
# elif defined(__ICCRL78__)
# define ARCHITECTURE_ID "RL78"
# elif defined(__ICCRISCV__)
# define ARCHITECTURE_ID "RISCV"
# elif defined(__ICCAVR__)
# define ARCHITECTURE_ID "AVR"
# elif defined(__ICC430__)
# define ARCHITECTURE_ID "MSP430"
# elif defined(__ICCV850__)
# define ARCHITECTURE_ID "V850"
# elif defined(__ICC8051__)
# define ARCHITECTURE_ID "8051"
# elif defined(__ICCSTM8__)
# define ARCHITECTURE_ID "STM8"
# else /* unknown architecture */
# define ARCHITECTURE_ID ""
# endif
#elif defined(__ghs__)
# if defined(__PPC64__)
# define ARCHITECTURE_ID "PPC64"
# elif defined(__ppc__)
# define ARCHITECTURE_ID "PPC"
# elif defined(__ARM__)
# define ARCHITECTURE_ID "ARM"
# elif defined(__x86_64__)
# define ARCHITECTURE_ID "x64"
# elif defined(__i386__)
# define ARCHITECTURE_ID "X86"
# else /* unknown architecture */
# define ARCHITECTURE_ID ""
# endif
#elif defined(__TI_COMPILER_VERSION__)
# if defined(__TI_ARM__)
# define ARCHITECTURE_ID "ARM"
# elif defined(__MSP430__)
# define ARCHITECTURE_ID "MSP430"
# elif defined(__TMS320C28XX__)
# define ARCHITECTURE_ID "TMS320C28x"
# elif defined(__TMS320C6X__) || defined(_TMS320C6X)
# define ARCHITECTURE_ID "TMS320C6x"
# else /* unknown architecture */
# define ARCHITECTURE_ID ""
# endif
#else
# define ARCHITECTURE_ID
#endif
/* Convert integer to decimal digit literals. */
#define DEC(n) \
('0' + (((n) / 10000000)%10)), \
('0' + (((n) / 1000000)%10)), \
('0' + (((n) / 100000)%10)), \
('0' + (((n) / 10000)%10)), \
('0' + (((n) / 1000)%10)), \
('0' + (((n) / 100)%10)), \
('0' + (((n) / 10)%10)), \
('0' + ((n) % 10))
/* Convert integer to hex digit literals. */
#define HEX(n) \
('0' + ((n)>>28 & 0xF)), \
('0' + ((n)>>24 & 0xF)), \
('0' + ((n)>>20 & 0xF)), \
('0' + ((n)>>16 & 0xF)), \
('0' + ((n)>>12 & 0xF)), \
('0' + ((n)>>8 & 0xF)), \
('0' + ((n)>>4 & 0xF)), \
('0' + ((n) & 0xF))
/* Construct a string literal encoding the version number. */
#ifdef COMPILER_VERSION
char const* info_version = "INFO" ":" "compiler_version[" COMPILER_VERSION "]";
/* Construct a string literal encoding the version number components. */
#elif defined(COMPILER_VERSION_MAJOR)
char const info_version[] = {
'I', 'N', 'F', 'O', ':',
'c','o','m','p','i','l','e','r','_','v','e','r','s','i','o','n','[',
COMPILER_VERSION_MAJOR,
# ifdef COMPILER_VERSION_MINOR
'.', COMPILER_VERSION_MINOR,
# ifdef COMPILER_VERSION_PATCH
'.', COMPILER_VERSION_PATCH,
# ifdef COMPILER_VERSION_TWEAK
'.', COMPILER_VERSION_TWEAK,
# endif
# endif
# endif
']','\0'};
#endif
/* Construct a string literal encoding the internal version number. */
#ifdef COMPILER_VERSION_INTERNAL
char const info_version_internal[] = {
'I', 'N', 'F', 'O', ':',
'c','o','m','p','i','l','e','r','_','v','e','r','s','i','o','n','_',
'i','n','t','e','r','n','a','l','[',
COMPILER_VERSION_INTERNAL,']','\0'};
#elif defined(COMPILER_VERSION_INTERNAL_STR)
char const* info_version_internal = "INFO" ":" "compiler_version_internal[" COMPILER_VERSION_INTERNAL_STR "]";
#endif
/* Construct a string literal encoding the version number components. */
#ifdef SIMULATE_VERSION_MAJOR
char const info_simulate_version[] = {
'I', 'N', 'F', 'O', ':',
's','i','m','u','l','a','t','e','_','v','e','r','s','i','o','n','[',
SIMULATE_VERSION_MAJOR,
# ifdef SIMULATE_VERSION_MINOR
'.', SIMULATE_VERSION_MINOR,
# ifdef SIMULATE_VERSION_PATCH
'.', SIMULATE_VERSION_PATCH,
# ifdef SIMULATE_VERSION_TWEAK
'.', SIMULATE_VERSION_TWEAK,
# endif
# endif
# endif
']','\0'};
#endif
/* Construct the string literal in pieces to prevent the source from
getting matched. Store it in a pointer rather than an array
because some compilers will just produce instructions to fill the
array rather than assigning a pointer to a static array. */
char const* info_platform = "INFO" ":" "platform[" PLATFORM_ID "]";
char const* info_arch = "INFO" ":" "arch[" ARCHITECTURE_ID "]";
#if !defined(__STDC__) && !defined(__clang__)
# if defined(_MSC_VER) || defined(__ibmxl__) || defined(__IBMC__)
# define C_VERSION "90"
# else
# define C_VERSION
# endif
#elif __STDC_VERSION__ > 201710L
# define C_VERSION "23"
#elif __STDC_VERSION__ >= 201710L
# define C_VERSION "17"
#elif __STDC_VERSION__ >= 201000L
# define C_VERSION "11"
#elif __STDC_VERSION__ >= 199901L
# define C_VERSION "99"
#else
# define C_VERSION "90"
#endif
const char* info_language_standard_default =
"INFO" ":" "standard_default[" C_VERSION "]";
const char* info_language_extensions_default = "INFO" ":" "extensions_default["
/* !defined(_MSC_VER) to exclude Clang's MSVC compatibility mode. */
#if (defined(__clang__) || defined(__GNUC__) || \
defined(__TI_COMPILER_VERSION__)) && \
!defined(__STRICT_ANSI__) && !defined(_MSC_VER)
"ON"
#else
"OFF"
#endif
"]";
/*--------------------------------------------------------------------------*/
#ifdef ID_VOID_MAIN
void main() {}
#else
# if defined(__CLASSIC_C__)
int main(argc, argv) int argc; char *argv[];
# else
int main(int argc, char* argv[])
# endif
{
int require = 0;
require += info_compiler[argc];
require += info_platform[argc];
require += info_arch[argc];
#ifdef COMPILER_VERSION_MAJOR
require += info_version[argc];
#endif
#ifdef COMPILER_VERSION_INTERNAL
require += info_version_internal[argc];
#endif
#ifdef SIMULATE_ID
require += info_simulate[argc];
#endif
#ifdef SIMULATE_VERSION_MAJOR
require += info_simulate_version[argc];
#endif
#if defined(__CRAYXT_COMPUTE_LINUX_TARGET)
require += info_cray[argc];
#endif
require += info_language_standard_default[argc];
require += info_language_extensions_default[argc];
(void)argv;
return require;
}
#endif
@@ -1,791 +0,0 @@
/* This source file must have a .cpp extension so that all C++ compilers
recognize the extension without flags. Borland does not know .cxx for
example. */
#ifndef __cplusplus
# error "A C compiler has been selected for C++."
#endif
#if !defined(__has_include)
/* If the compiler does not have __has_include, pretend the answer is
always no. */
# define __has_include(x) 0
#endif
/* Version number components: V=Version, R=Revision, P=Patch
Version date components: YYYY=Year, MM=Month, DD=Day */
#if defined(__COMO__)
# define COMPILER_ID "Comeau"
/* __COMO_VERSION__ = VRR */
# define COMPILER_VERSION_MAJOR DEC(__COMO_VERSION__ / 100)
# define COMPILER_VERSION_MINOR DEC(__COMO_VERSION__ % 100)
#elif defined(__INTEL_COMPILER) || defined(__ICC)
# define COMPILER_ID "Intel"
# if defined(_MSC_VER)
# define SIMULATE_ID "MSVC"
# endif
# if defined(__GNUC__)
# define SIMULATE_ID "GNU"
# endif
/* __INTEL_COMPILER = VRP prior to 2021, and then VVVV for 2021 and later,
except that a few beta releases use the old format with V=2021. */
# if __INTEL_COMPILER < 2021 || __INTEL_COMPILER == 202110 || __INTEL_COMPILER == 202111
# define COMPILER_VERSION_MAJOR DEC(__INTEL_COMPILER/100)
# define COMPILER_VERSION_MINOR DEC(__INTEL_COMPILER/10 % 10)
# if defined(__INTEL_COMPILER_UPDATE)
# define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER_UPDATE)
# else
# define COMPILER_VERSION_PATCH DEC(__INTEL_COMPILER % 10)
# endif
# else
# define COMPILER_VERSION_MAJOR DEC(__INTEL_COMPILER)
# define COMPILER_VERSION_MINOR DEC(__INTEL_COMPILER_UPDATE)
/* The third version component from --version is an update index,
but no macro is provided for it. */
# define COMPILER_VERSION_PATCH DEC(0)
# endif
# if defined(__INTEL_COMPILER_BUILD_DATE)
/* __INTEL_COMPILER_BUILD_DATE = YYYYMMDD */
# define COMPILER_VERSION_TWEAK DEC(__INTEL_COMPILER_BUILD_DATE)
# endif
# if defined(_MSC_VER)
/* _MSC_VER = VVRR */
# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100)
# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100)
# endif
# if defined(__GNUC__)
# define SIMULATE_VERSION_MAJOR DEC(__GNUC__)
# elif defined(__GNUG__)
# define SIMULATE_VERSION_MAJOR DEC(__GNUG__)
# endif
# if defined(__GNUC_MINOR__)
# define SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__)
# endif
# if defined(__GNUC_PATCHLEVEL__)
# define SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__)
# endif
#elif (defined(__clang__) && defined(__INTEL_CLANG_COMPILER)) || defined(__INTEL_LLVM_COMPILER)
# define COMPILER_ID "IntelLLVM"
#if defined(_MSC_VER)
# define SIMULATE_ID "MSVC"
#endif
#if defined(__GNUC__)
# define SIMULATE_ID "GNU"
#endif
/* __INTEL_LLVM_COMPILER = VVVVRP prior to 2021.2.0, VVVVRRPP for 2021.2.0 and
* later. Look for 6 digit vs. 8 digit version number to decide encoding.
* VVVV is no smaller than the current year when a version is released.
*/
#if __INTEL_LLVM_COMPILER < 1000000L
# define COMPILER_VERSION_MAJOR DEC(__INTEL_LLVM_COMPILER/100)
# define COMPILER_VERSION_MINOR DEC(__INTEL_LLVM_COMPILER/10 % 10)
# define COMPILER_VERSION_PATCH DEC(__INTEL_LLVM_COMPILER % 10)
#else
# define COMPILER_VERSION_MAJOR DEC(__INTEL_LLVM_COMPILER/10000)
# define COMPILER_VERSION_MINOR DEC(__INTEL_LLVM_COMPILER/100 % 100)
# define COMPILER_VERSION_PATCH DEC(__INTEL_LLVM_COMPILER % 100)
#endif
#if defined(_MSC_VER)
/* _MSC_VER = VVRR */
# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100)
# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100)
#endif
#if defined(__GNUC__)
# define SIMULATE_VERSION_MAJOR DEC(__GNUC__)
#elif defined(__GNUG__)
# define SIMULATE_VERSION_MAJOR DEC(__GNUG__)
#endif
#if defined(__GNUC_MINOR__)
# define SIMULATE_VERSION_MINOR DEC(__GNUC_MINOR__)
#endif
#if defined(__GNUC_PATCHLEVEL__)
# define SIMULATE_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__)
#endif
#elif defined(__PATHCC__)
# define COMPILER_ID "PathScale"
# define COMPILER_VERSION_MAJOR DEC(__PATHCC__)
# define COMPILER_VERSION_MINOR DEC(__PATHCC_MINOR__)
# if defined(__PATHCC_PATCHLEVEL__)
# define COMPILER_VERSION_PATCH DEC(__PATHCC_PATCHLEVEL__)
# endif
#elif defined(__BORLANDC__) && defined(__CODEGEARC_VERSION__)
# define COMPILER_ID "Embarcadero"
# define COMPILER_VERSION_MAJOR HEX(__CODEGEARC_VERSION__>>24 & 0x00FF)
# define COMPILER_VERSION_MINOR HEX(__CODEGEARC_VERSION__>>16 & 0x00FF)
# define COMPILER_VERSION_PATCH DEC(__CODEGEARC_VERSION__ & 0xFFFF)
#elif defined(__BORLANDC__)
# define COMPILER_ID "Borland"
/* __BORLANDC__ = 0xVRR */
# define COMPILER_VERSION_MAJOR HEX(__BORLANDC__>>8)
# define COMPILER_VERSION_MINOR HEX(__BORLANDC__ & 0xFF)
#elif defined(__WATCOMC__) && __WATCOMC__ < 1200
# define COMPILER_ID "Watcom"
/* __WATCOMC__ = VVRR */
# define COMPILER_VERSION_MAJOR DEC(__WATCOMC__ / 100)
# define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10)
# if (__WATCOMC__ % 10) > 0
# define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10)
# endif
#elif defined(__WATCOMC__)
# define COMPILER_ID "OpenWatcom"
/* __WATCOMC__ = VVRP + 1100 */
# define COMPILER_VERSION_MAJOR DEC((__WATCOMC__ - 1100) / 100)
# define COMPILER_VERSION_MINOR DEC((__WATCOMC__ / 10) % 10)
# if (__WATCOMC__ % 10) > 0
# define COMPILER_VERSION_PATCH DEC(__WATCOMC__ % 10)
# endif
#elif defined(__SUNPRO_CC)
# define COMPILER_ID "SunPro"
# if __SUNPRO_CC >= 0x5100
/* __SUNPRO_CC = 0xVRRP */
# define COMPILER_VERSION_MAJOR HEX(__SUNPRO_CC>>12)
# define COMPILER_VERSION_MINOR HEX(__SUNPRO_CC>>4 & 0xFF)
# define COMPILER_VERSION_PATCH HEX(__SUNPRO_CC & 0xF)
# else
/* __SUNPRO_CC = 0xVRP */
# define COMPILER_VERSION_MAJOR HEX(__SUNPRO_CC>>8)
# define COMPILER_VERSION_MINOR HEX(__SUNPRO_CC>>4 & 0xF)
# define COMPILER_VERSION_PATCH HEX(__SUNPRO_CC & 0xF)
# endif
#elif defined(__HP_aCC)
# define COMPILER_ID "HP"
/* __HP_aCC = VVRRPP */
# define COMPILER_VERSION_MAJOR DEC(__HP_aCC/10000)
# define COMPILER_VERSION_MINOR DEC(__HP_aCC/100 % 100)
# define COMPILER_VERSION_PATCH DEC(__HP_aCC % 100)
#elif defined(__DECCXX)
# define COMPILER_ID "Compaq"
/* __DECCXX_VER = VVRRTPPPP */
# define COMPILER_VERSION_MAJOR DEC(__DECCXX_VER/10000000)
# define COMPILER_VERSION_MINOR DEC(__DECCXX_VER/100000 % 100)
# define COMPILER_VERSION_PATCH DEC(__DECCXX_VER % 10000)
#elif defined(__IBMCPP__) && defined(__COMPILER_VER__)
# define COMPILER_ID "zOS"
/* __IBMCPP__ = VRP */
# define COMPILER_VERSION_MAJOR DEC(__IBMCPP__/100)
# define COMPILER_VERSION_MINOR DEC(__IBMCPP__/10 % 10)
# define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10)
#elif defined(__ibmxl__) && defined(__clang__)
# define COMPILER_ID "XLClang"
# define COMPILER_VERSION_MAJOR DEC(__ibmxl_version__)
# define COMPILER_VERSION_MINOR DEC(__ibmxl_release__)
# define COMPILER_VERSION_PATCH DEC(__ibmxl_modification__)
# define COMPILER_VERSION_TWEAK DEC(__ibmxl_ptf_fix_level__)
#elif defined(__IBMCPP__) && !defined(__COMPILER_VER__) && __IBMCPP__ >= 800
# define COMPILER_ID "XL"
/* __IBMCPP__ = VRP */
# define COMPILER_VERSION_MAJOR DEC(__IBMCPP__/100)
# define COMPILER_VERSION_MINOR DEC(__IBMCPP__/10 % 10)
# define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10)
#elif defined(__IBMCPP__) && !defined(__COMPILER_VER__) && __IBMCPP__ < 800
# define COMPILER_ID "VisualAge"
/* __IBMCPP__ = VRP */
# define COMPILER_VERSION_MAJOR DEC(__IBMCPP__/100)
# define COMPILER_VERSION_MINOR DEC(__IBMCPP__/10 % 10)
# define COMPILER_VERSION_PATCH DEC(__IBMCPP__ % 10)
#elif defined(__NVCOMPILER)
# define COMPILER_ID "NVHPC"
# define COMPILER_VERSION_MAJOR DEC(__NVCOMPILER_MAJOR__)
# define COMPILER_VERSION_MINOR DEC(__NVCOMPILER_MINOR__)
# if defined(__NVCOMPILER_PATCHLEVEL__)
# define COMPILER_VERSION_PATCH DEC(__NVCOMPILER_PATCHLEVEL__)
# endif
#elif defined(__PGI)
# define COMPILER_ID "PGI"
# define COMPILER_VERSION_MAJOR DEC(__PGIC__)
# define COMPILER_VERSION_MINOR DEC(__PGIC_MINOR__)
# if defined(__PGIC_PATCHLEVEL__)
# define COMPILER_VERSION_PATCH DEC(__PGIC_PATCHLEVEL__)
# endif
#elif defined(_CRAYC)
# define COMPILER_ID "Cray"
# define COMPILER_VERSION_MAJOR DEC(_RELEASE_MAJOR)
# define COMPILER_VERSION_MINOR DEC(_RELEASE_MINOR)
#elif defined(__TI_COMPILER_VERSION__)
# define COMPILER_ID "TI"
/* __TI_COMPILER_VERSION__ = VVVRRRPPP */
# define COMPILER_VERSION_MAJOR DEC(__TI_COMPILER_VERSION__/1000000)
# define COMPILER_VERSION_MINOR DEC(__TI_COMPILER_VERSION__/1000 % 1000)
# define COMPILER_VERSION_PATCH DEC(__TI_COMPILER_VERSION__ % 1000)
#elif defined(__CLANG_FUJITSU)
# define COMPILER_ID "FujitsuClang"
# define COMPILER_VERSION_MAJOR DEC(__FCC_major__)
# define COMPILER_VERSION_MINOR DEC(__FCC_minor__)
# define COMPILER_VERSION_PATCH DEC(__FCC_patchlevel__)
# define COMPILER_VERSION_INTERNAL_STR __clang_version__
#elif defined(__FUJITSU)
# define COMPILER_ID "Fujitsu"
# if defined(__FCC_version__)
# define COMPILER_VERSION __FCC_version__
# elif defined(__FCC_major__)
# define COMPILER_VERSION_MAJOR DEC(__FCC_major__)
# define COMPILER_VERSION_MINOR DEC(__FCC_minor__)
# define COMPILER_VERSION_PATCH DEC(__FCC_patchlevel__)
# endif
# if defined(__fcc_version)
# define COMPILER_VERSION_INTERNAL DEC(__fcc_version)
# elif defined(__FCC_VERSION)
# define COMPILER_VERSION_INTERNAL DEC(__FCC_VERSION)
# endif
#elif defined(__ghs__)
# define COMPILER_ID "GHS"
/* __GHS_VERSION_NUMBER = VVVVRP */
# ifdef __GHS_VERSION_NUMBER
# define COMPILER_VERSION_MAJOR DEC(__GHS_VERSION_NUMBER / 100)
# define COMPILER_VERSION_MINOR DEC(__GHS_VERSION_NUMBER / 10 % 10)
# define COMPILER_VERSION_PATCH DEC(__GHS_VERSION_NUMBER % 10)
# endif
#elif defined(__SCO_VERSION__)
# define COMPILER_ID "SCO"
#elif defined(__ARMCC_VERSION) && !defined(__clang__)
# define COMPILER_ID "ARMCC"
#if __ARMCC_VERSION >= 1000000
/* __ARMCC_VERSION = VRRPPPP */
# define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION/1000000)
# define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION/10000 % 100)
# define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000)
#else
/* __ARMCC_VERSION = VRPPPP */
# define COMPILER_VERSION_MAJOR DEC(__ARMCC_VERSION/100000)
# define COMPILER_VERSION_MINOR DEC(__ARMCC_VERSION/10000 % 10)
# define COMPILER_VERSION_PATCH DEC(__ARMCC_VERSION % 10000)
#endif
#elif defined(__clang__) && defined(__apple_build_version__)
# define COMPILER_ID "AppleClang"
# if defined(_MSC_VER)
# define SIMULATE_ID "MSVC"
# endif
# define COMPILER_VERSION_MAJOR DEC(__clang_major__)
# define COMPILER_VERSION_MINOR DEC(__clang_minor__)
# define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__)
# if defined(_MSC_VER)
/* _MSC_VER = VVRR */
# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100)
# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100)
# endif
# define COMPILER_VERSION_TWEAK DEC(__apple_build_version__)
#elif defined(__clang__) && defined(__ARMCOMPILER_VERSION)
# define COMPILER_ID "ARMClang"
# define COMPILER_VERSION_MAJOR DEC(__ARMCOMPILER_VERSION/1000000)
# define COMPILER_VERSION_MINOR DEC(__ARMCOMPILER_VERSION/10000 % 100)
# define COMPILER_VERSION_PATCH DEC(__ARMCOMPILER_VERSION % 10000)
# define COMPILER_VERSION_INTERNAL DEC(__ARMCOMPILER_VERSION)
#elif defined(__clang__)
# define COMPILER_ID "Clang"
# if defined(_MSC_VER)
# define SIMULATE_ID "MSVC"
# endif
# define COMPILER_VERSION_MAJOR DEC(__clang_major__)
# define COMPILER_VERSION_MINOR DEC(__clang_minor__)
# define COMPILER_VERSION_PATCH DEC(__clang_patchlevel__)
# if defined(_MSC_VER)
/* _MSC_VER = VVRR */
# define SIMULATE_VERSION_MAJOR DEC(_MSC_VER / 100)
# define SIMULATE_VERSION_MINOR DEC(_MSC_VER % 100)
# endif
#elif defined(__GNUC__) || defined(__GNUG__)
# define COMPILER_ID "GNU"
# if defined(__GNUC__)
# define COMPILER_VERSION_MAJOR DEC(__GNUC__)
# else
# define COMPILER_VERSION_MAJOR DEC(__GNUG__)
# endif
# if defined(__GNUC_MINOR__)
# define COMPILER_VERSION_MINOR DEC(__GNUC_MINOR__)
# endif
# if defined(__GNUC_PATCHLEVEL__)
# define COMPILER_VERSION_PATCH DEC(__GNUC_PATCHLEVEL__)
# endif
#elif defined(_MSC_VER)
# define COMPILER_ID "MSVC"
/* _MSC_VER = VVRR */
# define COMPILER_VERSION_MAJOR DEC(_MSC_VER / 100)
# define COMPILER_VERSION_MINOR DEC(_MSC_VER % 100)
# if defined(_MSC_FULL_VER)
# if _MSC_VER >= 1400
/* _MSC_FULL_VER = VVRRPPPPP */
# define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 100000)
# else
/* _MSC_FULL_VER = VVRRPPPP */
# define COMPILER_VERSION_PATCH DEC(_MSC_FULL_VER % 10000)
# endif
# endif
# if defined(_MSC_BUILD)
# define COMPILER_VERSION_TWEAK DEC(_MSC_BUILD)
# endif
#elif defined(__VISUALDSPVERSION__) || defined(__ADSPBLACKFIN__) || defined(__ADSPTS__) || defined(__ADSP21000__)
# define COMPILER_ID "ADSP"
#if defined(__VISUALDSPVERSION__)
/* __VISUALDSPVERSION__ = 0xVVRRPP00 */
# define COMPILER_VERSION_MAJOR HEX(__VISUALDSPVERSION__>>24)
# define COMPILER_VERSION_MINOR HEX(__VISUALDSPVERSION__>>16 & 0xFF)
# define COMPILER_VERSION_PATCH HEX(__VISUALDSPVERSION__>>8 & 0xFF)
#endif
#elif defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ICC)
# define COMPILER_ID "IAR"
# if defined(__VER__) && defined(__ICCARM__)
# define COMPILER_VERSION_MAJOR DEC((__VER__) / 1000000)
# define COMPILER_VERSION_MINOR DEC(((__VER__) / 1000) % 1000)
# define COMPILER_VERSION_PATCH DEC((__VER__) % 1000)
# define COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__)
# elif defined(__VER__) && (defined(__ICCAVR__) || defined(__ICCRX__) || defined(__ICCRH850__) || defined(__ICCRL78__) || defined(__ICC430__) || defined(__ICCRISCV__) || defined(__ICCV850__) || defined(__ICC8051__) || defined(__ICCSTM8__))
# define COMPILER_VERSION_MAJOR DEC((__VER__) / 100)
# define COMPILER_VERSION_MINOR DEC((__VER__) - (((__VER__) / 100)*100))
# define COMPILER_VERSION_PATCH DEC(__SUBVERSION__)
# define COMPILER_VERSION_INTERNAL DEC(__IAR_SYSTEMS_ICC__)
# endif
/* These compilers are either not known or too old to define an
identification macro. Try to identify the platform and guess that
it is the native compiler. */
#elif defined(__hpux) || defined(__hpua)
# define COMPILER_ID "HP"
#else /* unknown compiler */
# define COMPILER_ID ""
#endif
/* Construct the string literal in pieces to prevent the source from
getting matched. Store it in a pointer rather than an array
because some compilers will just produce instructions to fill the
array rather than assigning a pointer to a static array. */
char const* info_compiler = "INFO" ":" "compiler[" COMPILER_ID "]";
#ifdef SIMULATE_ID
char const* info_simulate = "INFO" ":" "simulate[" SIMULATE_ID "]";
#endif
#ifdef __QNXNTO__
char const* qnxnto = "INFO" ":" "qnxnto[]";
#endif
#if defined(__CRAYXT_COMPUTE_LINUX_TARGET)
char const *info_cray = "INFO" ":" "compiler_wrapper[CrayPrgEnv]";
#endif
#define STRINGIFY_HELPER(X) #X
#define STRINGIFY(X) STRINGIFY_HELPER(X)
/* Identify known platforms by name. */
#if defined(__linux) || defined(__linux__) || defined(linux)
# define PLATFORM_ID "Linux"
#elif defined(__MSYS__)
# define PLATFORM_ID "MSYS"
#elif defined(__CYGWIN__)
# define PLATFORM_ID "Cygwin"
#elif defined(__MINGW32__)
# define PLATFORM_ID "MinGW"
#elif defined(__APPLE__)
# define PLATFORM_ID "Darwin"
#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
# define PLATFORM_ID "Windows"
#elif defined(__FreeBSD__) || defined(__FreeBSD)
# define PLATFORM_ID "FreeBSD"
#elif defined(__NetBSD__) || defined(__NetBSD)
# define PLATFORM_ID "NetBSD"
#elif defined(__OpenBSD__) || defined(__OPENBSD)
# define PLATFORM_ID "OpenBSD"
#elif defined(__sun) || defined(sun)
# define PLATFORM_ID "SunOS"
#elif defined(_AIX) || defined(__AIX) || defined(__AIX__) || defined(__aix) || defined(__aix__)
# define PLATFORM_ID "AIX"
#elif defined(__hpux) || defined(__hpux__)
# define PLATFORM_ID "HP-UX"
#elif defined(__HAIKU__)
# define PLATFORM_ID "Haiku"
#elif defined(__BeOS) || defined(__BEOS__) || defined(_BEOS)
# define PLATFORM_ID "BeOS"
#elif defined(__QNX__) || defined(__QNXNTO__)
# define PLATFORM_ID "QNX"
#elif defined(__tru64) || defined(_tru64) || defined(__TRU64__)
# define PLATFORM_ID "Tru64"
#elif defined(__riscos) || defined(__riscos__)
# define PLATFORM_ID "RISCos"
#elif defined(__sinix) || defined(__sinix__) || defined(__SINIX__)
# define PLATFORM_ID "SINIX"
#elif defined(__UNIX_SV__)
# define PLATFORM_ID "UNIX_SV"
#elif defined(__bsdos__)
# define PLATFORM_ID "BSDOS"
#elif defined(_MPRAS) || defined(MPRAS)
# define PLATFORM_ID "MP-RAS"
#elif defined(__osf) || defined(__osf__)
# define PLATFORM_ID "OSF1"
#elif defined(_SCO_SV) || defined(SCO_SV) || defined(sco_sv)
# define PLATFORM_ID "SCO_SV"
#elif defined(__ultrix) || defined(__ultrix__) || defined(_ULTRIX)
# define PLATFORM_ID "ULTRIX"
#elif defined(__XENIX__) || defined(_XENIX) || defined(XENIX)
# define PLATFORM_ID "Xenix"
#elif defined(__WATCOMC__)
# if defined(__LINUX__)
# define PLATFORM_ID "Linux"
# elif defined(__DOS__)
# define PLATFORM_ID "DOS"
# elif defined(__OS2__)
# define PLATFORM_ID "OS2"
# elif defined(__WINDOWS__)
# define PLATFORM_ID "Windows3x"
# elif defined(__VXWORKS__)
# define PLATFORM_ID "VxWorks"
# else /* unknown platform */
# define PLATFORM_ID
# endif
#elif defined(__INTEGRITY)
# if defined(INT_178B)
# define PLATFORM_ID "Integrity178"
# else /* regular Integrity */
# define PLATFORM_ID "Integrity"
# endif
#else /* unknown platform */
# define PLATFORM_ID
#endif
/* For windows compilers MSVC and Intel we can determine
the architecture of the compiler being used. This is because
the compilers do not have flags that can change the architecture,
but rather depend on which compiler is being used
*/
#if defined(_WIN32) && defined(_MSC_VER)
# if defined(_M_IA64)
# define ARCHITECTURE_ID "IA64"
# elif defined(_M_ARM64EC)
# define ARCHITECTURE_ID "ARM64EC"
# elif defined(_M_X64) || defined(_M_AMD64)
# define ARCHITECTURE_ID "x64"
# elif defined(_M_IX86)
# define ARCHITECTURE_ID "X86"
# elif defined(_M_ARM64)
# define ARCHITECTURE_ID "ARM64"
# elif defined(_M_ARM)
# if _M_ARM == 4
# define ARCHITECTURE_ID "ARMV4I"
# elif _M_ARM == 5
# define ARCHITECTURE_ID "ARMV5I"
# else
# define ARCHITECTURE_ID "ARMV" STRINGIFY(_M_ARM)
# endif
# elif defined(_M_MIPS)
# define ARCHITECTURE_ID "MIPS"
# elif defined(_M_SH)
# define ARCHITECTURE_ID "SHx"
# else /* unknown architecture */
# define ARCHITECTURE_ID ""
# endif
#elif defined(__WATCOMC__)
# if defined(_M_I86)
# define ARCHITECTURE_ID "I86"
# elif defined(_M_IX86)
# define ARCHITECTURE_ID "X86"
# else /* unknown architecture */
# define ARCHITECTURE_ID ""
# endif
#elif defined(__IAR_SYSTEMS_ICC__) || defined(__IAR_SYSTEMS_ICC)
# if defined(__ICCARM__)
# define ARCHITECTURE_ID "ARM"
# elif defined(__ICCRX__)
# define ARCHITECTURE_ID "RX"
# elif defined(__ICCRH850__)
# define ARCHITECTURE_ID "RH850"
# elif defined(__ICCRL78__)
# define ARCHITECTURE_ID "RL78"
# elif defined(__ICCRISCV__)
# define ARCHITECTURE_ID "RISCV"
# elif defined(__ICCAVR__)
# define ARCHITECTURE_ID "AVR"
# elif defined(__ICC430__)
# define ARCHITECTURE_ID "MSP430"
# elif defined(__ICCV850__)
# define ARCHITECTURE_ID "V850"
# elif defined(__ICC8051__)
# define ARCHITECTURE_ID "8051"
# elif defined(__ICCSTM8__)
# define ARCHITECTURE_ID "STM8"
# else /* unknown architecture */
# define ARCHITECTURE_ID ""
# endif
#elif defined(__ghs__)
# if defined(__PPC64__)
# define ARCHITECTURE_ID "PPC64"
# elif defined(__ppc__)
# define ARCHITECTURE_ID "PPC"
# elif defined(__ARM__)
# define ARCHITECTURE_ID "ARM"
# elif defined(__x86_64__)
# define ARCHITECTURE_ID "x64"
# elif defined(__i386__)
# define ARCHITECTURE_ID "X86"
# else /* unknown architecture */
# define ARCHITECTURE_ID ""
# endif
#elif defined(__TI_COMPILER_VERSION__)
# if defined(__TI_ARM__)
# define ARCHITECTURE_ID "ARM"
# elif defined(__MSP430__)
# define ARCHITECTURE_ID "MSP430"
# elif defined(__TMS320C28XX__)
# define ARCHITECTURE_ID "TMS320C28x"
# elif defined(__TMS320C6X__) || defined(_TMS320C6X)
# define ARCHITECTURE_ID "TMS320C6x"
# else /* unknown architecture */
# define ARCHITECTURE_ID ""
# endif
#else
# define ARCHITECTURE_ID
#endif
/* Convert integer to decimal digit literals. */
#define DEC(n) \
('0' + (((n) / 10000000)%10)), \
('0' + (((n) / 1000000)%10)), \
('0' + (((n) / 100000)%10)), \
('0' + (((n) / 10000)%10)), \
('0' + (((n) / 1000)%10)), \
('0' + (((n) / 100)%10)), \
('0' + (((n) / 10)%10)), \
('0' + ((n) % 10))
/* Convert integer to hex digit literals. */
#define HEX(n) \
('0' + ((n)>>28 & 0xF)), \
('0' + ((n)>>24 & 0xF)), \
('0' + ((n)>>20 & 0xF)), \
('0' + ((n)>>16 & 0xF)), \
('0' + ((n)>>12 & 0xF)), \
('0' + ((n)>>8 & 0xF)), \
('0' + ((n)>>4 & 0xF)), \
('0' + ((n) & 0xF))
/* Construct a string literal encoding the version number. */
#ifdef COMPILER_VERSION
char const* info_version = "INFO" ":" "compiler_version[" COMPILER_VERSION "]";
/* Construct a string literal encoding the version number components. */
#elif defined(COMPILER_VERSION_MAJOR)
char const info_version[] = {
'I', 'N', 'F', 'O', ':',
'c','o','m','p','i','l','e','r','_','v','e','r','s','i','o','n','[',
COMPILER_VERSION_MAJOR,
# ifdef COMPILER_VERSION_MINOR
'.', COMPILER_VERSION_MINOR,
# ifdef COMPILER_VERSION_PATCH
'.', COMPILER_VERSION_PATCH,
# ifdef COMPILER_VERSION_TWEAK
'.', COMPILER_VERSION_TWEAK,
# endif
# endif
# endif
']','\0'};
#endif
/* Construct a string literal encoding the internal version number. */
#ifdef COMPILER_VERSION_INTERNAL
char const info_version_internal[] = {
'I', 'N', 'F', 'O', ':',
'c','o','m','p','i','l','e','r','_','v','e','r','s','i','o','n','_',
'i','n','t','e','r','n','a','l','[',
COMPILER_VERSION_INTERNAL,']','\0'};
#elif defined(COMPILER_VERSION_INTERNAL_STR)
char const* info_version_internal = "INFO" ":" "compiler_version_internal[" COMPILER_VERSION_INTERNAL_STR "]";
#endif
/* Construct a string literal encoding the version number components. */
#ifdef SIMULATE_VERSION_MAJOR
char const info_simulate_version[] = {
'I', 'N', 'F', 'O', ':',
's','i','m','u','l','a','t','e','_','v','e','r','s','i','o','n','[',
SIMULATE_VERSION_MAJOR,
# ifdef SIMULATE_VERSION_MINOR
'.', SIMULATE_VERSION_MINOR,
# ifdef SIMULATE_VERSION_PATCH
'.', SIMULATE_VERSION_PATCH,
# ifdef SIMULATE_VERSION_TWEAK
'.', SIMULATE_VERSION_TWEAK,
# endif
# endif
# endif
']','\0'};
#endif
/* Construct the string literal in pieces to prevent the source from
getting matched. Store it in a pointer rather than an array
because some compilers will just produce instructions to fill the
array rather than assigning a pointer to a static array. */
char const* info_platform = "INFO" ":" "platform[" PLATFORM_ID "]";
char const* info_arch = "INFO" ":" "arch[" ARCHITECTURE_ID "]";
#if defined(__INTEL_COMPILER) && defined(_MSVC_LANG) && _MSVC_LANG < 201403L
# if defined(__INTEL_CXX11_MODE__)
# if defined(__cpp_aggregate_nsdmi)
# define CXX_STD 201402L
# else
# define CXX_STD 201103L
# endif
# else
# define CXX_STD 199711L
# endif
#elif defined(_MSC_VER) && defined(_MSVC_LANG)
# define CXX_STD _MSVC_LANG
#else
# define CXX_STD __cplusplus
#endif
const char* info_language_standard_default = "INFO" ":" "standard_default["
#if CXX_STD > 202002L
"23"
#elif CXX_STD > 201703L
"20"
#elif CXX_STD >= 201703L
"17"
#elif CXX_STD >= 201402L
"14"
#elif CXX_STD >= 201103L
"11"
#else
"98"
#endif
"]";
const char* info_language_extensions_default = "INFO" ":" "extensions_default["
/* !defined(_MSC_VER) to exclude Clang's MSVC compatibility mode. */
#if (defined(__clang__) || defined(__GNUC__) || \
defined(__TI_COMPILER_VERSION__)) && \
!defined(__STRICT_ANSI__) && !defined(_MSC_VER)
"ON"
#else
"OFF"
#endif
"]";
/*--------------------------------------------------------------------------*/
int main(int argc, char* argv[])
{
int require = 0;
require += info_compiler[argc];
require += info_platform[argc];
#ifdef COMPILER_VERSION_MAJOR
require += info_version[argc];
#endif
#ifdef COMPILER_VERSION_INTERNAL
require += info_version_internal[argc];
#endif
#ifdef SIMULATE_ID
require += info_simulate[argc];
#endif
#ifdef SIMULATE_VERSION_MAJOR
require += info_simulate_version[argc];
#endif
#if defined(__CRAYXT_COMPUTE_LINUX_TARGET)
require += info_cray[argc];
#endif
require += info_language_standard_default[argc];
require += info_language_extensions_default[argc];
(void)argv;
return require;
}
@@ -1,3 +0,0 @@
/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/appmodules.dir
/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/edit_cache.dir
/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/rebuild_cache.dir
@@ -1,23 +0,0 @@
# CMAKE generated file: DO NOT EDIT!
# Generated by CMake Version 3.22
cmake_policy(SET CMP0009 NEW)
# input_SRC at /home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake:55 (file)
file(GLOB NEW_GLOB LIST_DIRECTORIES true "/home/echo/dev/prototype/apps/mobile/android/app/build/generated/autolinking/src/main/jni/*.cpp")
set(OLD_GLOB
)
if(NOT "${NEW_GLOB}" STREQUAL "${OLD_GLOB}")
message("-- GLOB mismatch!")
file(TOUCH_NOCREATE "/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/cmake.verify_globs")
endif()
# override_cpp_SRC at /home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake:50 (file)
# input_SRC at /home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake:55 (file)
file(GLOB NEW_GLOB LIST_DIRECTORIES true "/home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactAndroid/cmake-utils/default-app-setup/*.cpp")
set(OLD_GLOB
"/home/echo/dev/prototype/apps/mobile/node_modules/react-native/ReactAndroid/cmake-utils/default-app-setup/OnLoad.cpp"
)
if(NOT "${NEW_GLOB}" STREQUAL "${OLD_GLOB}")
message("-- GLOB mismatch!")
file(TOUCH_NOCREATE "/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/cmake.verify_globs")
endif()
@@ -1,4 +0,0 @@
# ninja log v5
0 17 1764608951217248929 CMakeFiles/foo.dir/foo.cpp.o cf03112569101c0d
0 17 1764608951217248929 CMakeFiles/boo.dir/main.cpp.o a40c4ac8f9333fbb
17 47 1764608951246597989 libfoo.a 46ab43a90edc58a3
@@ -1,281 +0,0 @@
# This is the CMakeCache file.
# For build in directory: /home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/_CMakeLTOTest-CXX/bin
# It was generated by CMake: /home/echo/Android/Sdk/cmake/3.22.1/bin/cmake
# You can edit this file to change values found and used by cmake.
# If you do not want to change any of the values, simply exit the editor.
# If you do want to change a value, simply edit, save, and exit the editor.
# The syntax for the file is as follows:
# KEY:TYPE=VALUE
# KEY is the name of a variable in the cache.
# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!.
# VALUE is the current value for the KEY.
########################
# EXTERNAL cache entries
########################
//Archiver
CMAKE_AR:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar
//Flags used by the compiler during all build types.
CMAKE_ASM_FLAGS:STRING=
//Flags used by the compiler during debug builds.
CMAKE_ASM_FLAGS_DEBUG:STRING=
//Flags used by the compiler during release builds.
CMAKE_ASM_FLAGS_RELEASE:STRING=
//Choose the type of build, options are: None Debug Release RelWithDebInfo
// MinSizeRel ...
CMAKE_BUILD_TYPE:STRING=
//Flags used by the compiler during all build types.
CMAKE_CXX_FLAGS:STRING=
//Flags used by the compiler during debug builds.
CMAKE_CXX_FLAGS_DEBUG:STRING=
//Flags used by the CXX compiler during MINSIZEREL builds.
CMAKE_CXX_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG
//Flags used by the compiler during release builds.
CMAKE_CXX_FLAGS_RELEASE:STRING=
//Flags used by the CXX compiler during RELWITHDEBINFO builds.
CMAKE_CXX_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG
//Libraries linked by default with all C++ applications.
CMAKE_CXX_STANDARD_LIBRARIES:STRING=-latomic -lm
//Flags used by the compiler during all build types.
CMAKE_C_FLAGS:STRING=
//Flags used by the compiler during debug builds.
CMAKE_C_FLAGS_DEBUG:STRING=
//Flags used by the compiler during release builds.
CMAKE_C_FLAGS_RELEASE:STRING=
//Flags used by the linker.
CMAKE_EXE_LINKER_FLAGS:STRING=
//Flags used by the linker during DEBUG builds.
CMAKE_EXE_LINKER_FLAGS_DEBUG:STRING=
//Flags used by the linker during MINSIZEREL builds.
CMAKE_EXE_LINKER_FLAGS_MINSIZEREL:STRING=
//Flags used by the linker during RELEASE builds.
CMAKE_EXE_LINKER_FLAGS_RELEASE:STRING=
//Flags used by the linker during RELWITHDEBINFO builds.
CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO:STRING=
//Enable/Disable output of compile commands during generation.
CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=
//Install path prefix, prepended onto install directories.
CMAKE_INSTALL_PREFIX:PATH=/usr/local
//No help, variable specified on the command line.
CMAKE_INTERPROCEDURAL_OPTIMIZATION:UNINITIALIZED=ON
//make program
CMAKE_MAKE_PROGRAM:FILEPATH=/home/echo/Android/Sdk/cmake/3.22.1/bin/ninja
//Flags used by the linker during the creation of modules.
CMAKE_MODULE_LINKER_FLAGS:STRING=
//Flags used by the linker during the creation of modules during
// DEBUG builds.
CMAKE_MODULE_LINKER_FLAGS_DEBUG:STRING=
//Flags used by the linker during the creation of modules during
// MINSIZEREL builds.
CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL:STRING=
//Flags used by the linker during the creation of modules during
// RELEASE builds.
CMAKE_MODULE_LINKER_FLAGS_RELEASE:STRING=
//Flags used by the linker during the creation of modules during
// RELWITHDEBINFO builds.
CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO:STRING=
//Value Computed by CMake
CMAKE_PROJECT_DESCRIPTION:STATIC=
//Value Computed by CMake
CMAKE_PROJECT_HOMEPAGE_URL:STATIC=
//Value Computed by CMake
CMAKE_PROJECT_NAME:STATIC=lto-test
//Ranlib
CMAKE_RANLIB:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ranlib
//Flags used by the linker during the creation of dll's.
CMAKE_SHARED_LINKER_FLAGS:STRING=
//Flags used by the linker during the creation of shared libraries
// during DEBUG builds.
CMAKE_SHARED_LINKER_FLAGS_DEBUG:STRING=
//Flags used by the linker during the creation of shared libraries
// during MINSIZEREL builds.
CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL:STRING=
//Flags used by the linker during the creation of shared libraries
// during RELEASE builds.
CMAKE_SHARED_LINKER_FLAGS_RELEASE:STRING=
//Flags used by the linker during the creation of shared libraries
// during RELWITHDEBINFO builds.
CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO:STRING=
//If set, runtime paths are not added when installing shared libraries,
// but are added when building.
CMAKE_SKIP_INSTALL_RPATH:BOOL=NO
//If set, runtime paths are not added when using shared libraries.
CMAKE_SKIP_RPATH:BOOL=NO
//Flags used by the linker during the creation of static libraries
// during all build types.
CMAKE_STATIC_LINKER_FLAGS:STRING=
//Flags used by the linker during the creation of static libraries
// during DEBUG builds.
CMAKE_STATIC_LINKER_FLAGS_DEBUG:STRING=
//Flags used by the linker during the creation of static libraries
// during MINSIZEREL builds.
CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL:STRING=
//Flags used by the linker during the creation of static libraries
// during RELEASE builds.
CMAKE_STATIC_LINKER_FLAGS_RELEASE:STRING=
//Flags used by the linker during the creation of static libraries
// during RELWITHDEBINFO builds.
CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO:STRING=
//Strip
CMAKE_STRIP:FILEPATH=/home/echo/Android/Sdk/ndk/27.1.12297006/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip
//If this value is on, makefiles will be generated without the
// .SILENT directive, and all commands will be echoed to the console
// during the make. This is useful for debugging only. With Visual
// Studio IDE projects all commands are done without /nologo.
CMAKE_VERBOSE_MAKEFILE:BOOL=ON
//Value Computed by CMake
lto-test_BINARY_DIR:STATIC=/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/_CMakeLTOTest-CXX/bin
//Value Computed by CMake
lto-test_IS_TOP_LEVEL:STATIC=ON
//Value Computed by CMake
lto-test_SOURCE_DIR:STATIC=/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/_CMakeLTOTest-CXX/src
########################
# INTERNAL cache entries
########################
//This is the directory where this CMakeCache.txt was created
CMAKE_CACHEFILE_DIR:INTERNAL=/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/_CMakeLTOTest-CXX/bin
//Major version of cmake used to create the current loaded cache
CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3
//Minor version of cmake used to create the current loaded cache
CMAKE_CACHE_MINOR_VERSION:INTERNAL=22
//Patch version of cmake used to create the current loaded cache
CMAKE_CACHE_PATCH_VERSION:INTERNAL=1
//Path to CMake executable.
CMAKE_COMMAND:INTERNAL=/home/echo/Android/Sdk/cmake/3.22.1/bin/cmake
//Path to cpack program executable.
CMAKE_CPACK_COMMAND:INTERNAL=/home/echo/Android/Sdk/cmake/3.22.1/bin/cpack
//Path to ctest program executable.
CMAKE_CTEST_COMMAND:INTERNAL=/home/echo/Android/Sdk/cmake/3.22.1/bin/ctest
//ADVANCED property for variable: CMAKE_CXX_FLAGS
CMAKE_CXX_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_FLAGS_DEBUG
CMAKE_CXX_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_FLAGS_MINSIZEREL
CMAKE_CXX_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_FLAGS_RELWITHDEBINFO
CMAKE_CXX_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_CXX_STANDARD_LIBRARIES
CMAKE_CXX_STANDARD_LIBRARIES-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS
CMAKE_EXE_LINKER_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_DEBUG
CMAKE_EXE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_MINSIZEREL
CMAKE_EXE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELEASE
CMAKE_EXE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO
CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXPORT_COMPILE_COMMANDS
CMAKE_EXPORT_COMPILE_COMMANDS-ADVANCED:INTERNAL=1
//Name of external makefile project generator.
CMAKE_EXTRA_GENERATOR:INTERNAL=
//Name of generator.
CMAKE_GENERATOR:INTERNAL=Ninja
//Generator instance identifier.
CMAKE_GENERATOR_INSTANCE:INTERNAL=
//Name of generator platform.
CMAKE_GENERATOR_PLATFORM:INTERNAL=
//Name of generator toolset.
CMAKE_GENERATOR_TOOLSET:INTERNAL=
//Source directory with the top level CMakeLists.txt file for this
// project
CMAKE_HOME_DIRECTORY:INTERNAL=/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/_CMakeLTOTest-CXX/src
//Install .so files without execute permission.
CMAKE_INSTALL_SO_NO_EXE:INTERNAL=0
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS
CMAKE_MODULE_LINKER_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_DEBUG
CMAKE_MODULE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL
CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELEASE
CMAKE_MODULE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO
CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//number of local generators
CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1
//Path to CMake installation.
CMAKE_ROOT:INTERNAL=/home/echo/Android/Sdk/cmake/3.22.1/share/cmake-3.22
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS
CMAKE_SHARED_LINKER_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_DEBUG
CMAKE_SHARED_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL
CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELEASE
CMAKE_SHARED_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO
CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SKIP_INSTALL_RPATH
CMAKE_SKIP_INSTALL_RPATH-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SKIP_RPATH
CMAKE_SKIP_RPATH-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS
CMAKE_STATIC_LINKER_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_DEBUG
CMAKE_STATIC_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL
CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELEASE
CMAKE_STATIC_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO
CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
CMAKE_SUPPRESS_DEVELOPER_WARNINGS:INTERNAL=FALSE
//ADVANCED property for variable: CMAKE_VERBOSE_MAKEFILE
CMAKE_VERBOSE_MAKEFILE-ADVANCED:INTERNAL=1
@@ -1,4 +0,0 @@
/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/_CMakeLTOTest-CXX/bin/CMakeFiles/foo.dir
/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/_CMakeLTOTest-CXX/bin/CMakeFiles/boo.dir
/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/_CMakeLTOTest-CXX/bin/CMakeFiles/edit_cache.dir
/home/echo/dev/prototype/apps/mobile/android/app/.cxx/Debug/2z472e3c/x86/CMakeFiles/_CMakeLTOTest-CXX/bin/CMakeFiles/rebuild_cache.dir
@@ -1 +0,0 @@
# This file is generated by cmake for dependency checking of the CMakeCache.txt file
Loaded 100 of 290 files, more files were not shown because too many files have changed in this diff. Show more