Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/api/src/modules/form/form.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export class FormController {
@Get('/hasFilledForm')
async hasFilledForm(@LoginedUser() user: User): Promise<HasFilledFormResponse> {
const result = await this.formService.hasFilledForm(user.uid);
return buildSuccessResponse({ hasFilledForm: result.hasFilledForm, identity: result.identity });
return buildSuccessResponse({
hasFilledForm: result.hasFilledForm,
identity: result.identity,
interests: result.interests,
});
Comment on lines 37 to +43
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for relevant code in form service and schema
echo "=== Searching form.service.ts ==="
rg -n "hasFilledForm|interests" apps/api/src/modules/form/form.service.ts -A 5 -B 2

echo -e "\n=== Searching OpenAPI schema ==="
rg -n "HasFilledFormResponse|interests" packages/openapi-schema/schema.yml -A 3 -B 1

Repository: refly-ai/refly

Length of output: 2116


Prevent interests: null from leaking into the API response.

The FormService.hasFilledForm() method can return interests: null (see lines 115 and 122 where it returns interests: interests ?? null), but the OpenAPI schema defines interests as type: string (non-nullable). The controller passes this null value directly through, violating the API contract.

Proposed fix
  async hasFilledForm(`@LoginedUser`() user: User): Promise<HasFilledFormResponse> {
    const result = await this.formService.hasFilledForm(user.uid);
    return buildSuccessResponse({
      hasFilledForm: result.hasFilledForm,
      identity: result.identity,
-     interests: result.interests,
+     interests: result.interests ?? undefined,
    });
  }
🤖 Prompt for AI Agents
In `@apps/api/src/modules/form/form.controller.ts` around lines 37 - 43, The
controller is passing through result.interests which can be null from
formService.hasFilledForm(), violating the OpenAPI non-nullable string for
interests; update the hasFilledForm controller to coerce null to a string (e.g.,
interests: result.interests ?? '') before calling buildSuccessResponse, and
ensure the returned payload shape in HasFilledFormResponse matches the OpenAPI
schema; adjust or add a minimal inline conversion in the hasFilledForm method
that uses the result from formService.hasFilledForm(user.uid).

}
}
24 changes: 22 additions & 2 deletions apps/api/src/modules/form/form.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,21 @@ export class FormService {
}
}

private extractInterestsFromAnswers(answers: string): string | null {
if (!answers?.trim()) {
return null;
}

try {
const parsed = JSON.parse(answers) as { interests?: unknown } | null;
const interests =
typeof parsed?.interests === 'object' ? JSON.stringify(parsed.interests) : null;
return interests || null;
} catch {
return null;
}
}
Comment on lines +28 to +41
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

extractInterestsFromAnswers currently turns null into the string "null" and ignores string interests.
Handle null explicitly and support string / string[] (likely for multi-select) so downstream telemetry doesn’t get noisy values.

Proposed fix
   private extractInterestsFromAnswers(answers: string): string | null {
     if (!answers?.trim()) {
       return null;
     }

     try {
       const parsed = JSON.parse(answers) as { interests?: unknown } | null;
-      const interests =
-        typeof parsed?.interests === 'object' ? JSON.stringify(parsed.interests) : null;
-      return interests || null;
+      const raw = parsed?.interests;
+      if (raw == null) {
+        return null;
+      }
+      if (typeof raw === 'string') {
+        return raw.trim() || null;
+      }
+      if (Array.isArray(raw) && raw.every((v) => typeof v === 'string')) {
+        return raw.map((v) => v.trim()).filter(Boolean).join(', ') || null;
+      }
+      return JSON.stringify(raw);
     } catch {
       return null;
     }
   }
🤖 Prompt for AI Agents
In `@apps/api/src/modules/form/form.service.ts` around lines 28 - 41, The current
extractInterestsFromAnswers function can convert a null interest into the string
"null" and ignores string or string[] interest values; update
extractInterestsFromAnswers to: parse answers as before, then if
parsed?.interests === null return null; if typeof parsed.interests === 'string'
return that string; if Array.isArray(parsed.interests) return
JSON.stringify(parsed.interests) (or join if you prefer a delimited string); if
typeof parsed.interests === 'object' return JSON.stringify(parsed.interests);
otherwise return null, keeping the existing try/catch behavior for invalid JSON.


async getFormDefinition(_uid: string): Promise<FormDefinition | null> {
const formDefinition = await this.prisma.formDefinition.findFirst();
if (!formDefinition) {
Expand Down Expand Up @@ -79,7 +94,9 @@ export class FormService {
}
}

async hasFilledForm(uid: string): Promise<{ hasFilledForm: boolean; identity: string | null }> {
async hasFilledForm(
uid: string,
): Promise<{ hasFilledForm: boolean; identity: string; interests: string }> {
const user = await this.prisma.user.findUnique({
where: { uid },
select: { preferences: true },
Expand All @@ -92,14 +109,17 @@ export class FormService {

const identity = this.extractRoleFromAnswers(answers?.answers);

const interests = this.extractInterestsFromAnswers(answers?.answers);

if (!user?.preferences) {
return { hasFilledForm: false, identity: identity ?? null };
return { hasFilledForm: false, identity: identity ?? null, interests: interests ?? null };
}

const preferences = JSON.parse(user.preferences);
return {
hasFilledForm: preferences.hasFilledForm ?? true,
identity: identity ?? null,
interests: interests ?? null,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export const useGetUserSettings = () => {

// Check if user has been invited to show invitation code modal
let identity: string | null = null;
let interests: string | null = null;
try {
const invitationResp = await getClient().hasBeenInvited();
const hasBeenInvited = invitationResp.data?.data ?? false;
Expand All @@ -131,6 +132,7 @@ export const useGetUserSettings = () => {
const formResp = await getClient().hasFilledForm();
const hasFilledForm = formResp.data?.data?.hasFilledForm ?? false;
identity = formResp.data?.data?.identity ?? null;
interests = formResp.data?.data?.interests ?? null;
userStore.setShowOnboardingFormModal(!hasFilledForm);
} catch (_formError) {
// If form check fails, don't block user login, default to not showing modal
Expand All @@ -147,7 +149,11 @@ export const useGetUserSettings = () => {
}

if (userTypeForUserProperties) {
updateUserProperties({ user_plan: userTypeForUserProperties, user_identity: identity });
updateUserProperties({
user_plan: userTypeForUserProperties,
user_identity: identity,
user_acquisition_source: interests,
});
}
Comment on lines 151 to 157
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid sending null (and potentially large JSON) into telemetry user properties.
Prefer omitting unset fields (undefined) and consider normalizing interests to a small bounded string before sending as user_acquisition_source.

Proposed fix
     if (userTypeForUserProperties) {
       updateUserProperties({
         user_plan: userTypeForUserProperties,
-        user_identity: identity,
-        user_acquisition_source: interests,
+        user_identity: identity ?? undefined,
+        user_acquisition_source: interests ?? undefined,
       });
     }
🤖 Prompt for AI Agents
In `@packages/ai-workspace-common/src/hooks/use-get-user-settings.ts` around lines
151 - 157, The updateUserProperties call is sending potentially null values and
unbounded JSON (interests); change it to omit unset fields (use undefined
instead of null) and normalize interests to a small bounded string before
sending: only include user_plan when userTypeForUserProperties is non-null,
include user_identity only when identity exists, and transform interests (the
variable passed as user_acquisition_source) into a compact representation (e.g.,
pick/top N, join into a short comma-separated string or map to a single enum/tag
and truncate to a safe length) so you never send large JSON blobs to
updateUserProperties in use-get-user-settings.ts.


// set tour guide
Expand Down
4 changes: 4 additions & 0 deletions packages/ai-workspace-common/src/requests/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5754,6 +5754,10 @@ export type HasFilledFormResponse = BaseResponse & {
* User identity
*/
identity?: string;
/**
* User interests
*/
interests?: string;
};
};

Expand Down
3 changes: 3 additions & 0 deletions packages/openapi-schema/schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10456,6 +10456,9 @@ components:
identity:
type: string
description: User identity
interests:
type: string
description: User interests
GetCreditRechargeResponse:
allOf:
- $ref: '#/components/schemas/BaseResponse'
Expand Down
4 changes: 4 additions & 0 deletions packages/openapi-schema/src/schemas.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7981,6 +7981,10 @@ export const HasFilledFormResponseSchema = {
type: 'string',
description: 'User identity',
},
interests: {
type: 'string',
description: 'User interests',
},
},
},
},
Expand Down
4 changes: 4 additions & 0 deletions packages/openapi-schema/src/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5754,6 +5754,10 @@ export type HasFilledFormResponse = BaseResponse & {
* User identity
*/
identity?: string;
/**
* User interests
*/
interests?: string;
};
};

Expand Down
4 changes: 4 additions & 0 deletions packages/request/src/requests/types.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5412,6 +5412,10 @@ export type HasFilledFormResponse = BaseResponse & {
* User identity
*/
identity?: string;
/**
* User interests
*/
interests?: string;
};
};

Expand Down