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
3 changes: 3 additions & 0 deletions scholarship-finder/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
VITE_SUPABASE_PROJECT_ID="ikudbmsjgzirpyjagdgm"
VITE_SUPABASE_PUBLISHABLE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImlrdWRibXNqZ3ppcnB5amFnZGdtIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njg5NjY5ODAsImV4cCI6MjA4NDU0Mjk4MH0.pjgHkm5fniTp0Dovn4U46VCPztrYBXjWxHEaLsLP5j0"
VITE_SUPABASE_URL="https://ikudbmsjgzirpyjagdgm.supabase.co"
Comment on lines +1 to +3
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Do not commit .env files with real credentials to version control.

This file contains actual Supabase credentials and should not be committed. While the anon key is designed to be public (it's embedded in client-side code), committing .env files sets a dangerous precedent and can lead to accidental exposure of sensitive secrets in the future.

Additionally, the values should not be quoted in .env files per dotenv conventions.

Recommended approach:

  1. Add .env to .gitignore
  2. Create a .env.example with placeholder values for documentation
📁 Proposed .env.example
-VITE_SUPABASE_PROJECT_ID="ikudbmsjgzirpyjagdgm"
-VITE_SUPABASE_PUBLISHABLE_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImlrdWRibXNqZ3ppcnB5amFnZGdtIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njg5NjY5ODAsImV4cCI6MjA4NDU0Mjk4MH0.pjgHkm5fniTp0Dovn4U46VCPztrYBXjWxHEaLsLP5j0"
-VITE_SUPABASE_URL="https://ikudbmsjgzirpyjagdgm.supabase.co"
+VITE_SUPABASE_PROJECT_ID=your_project_id
+VITE_SUPABASE_PUBLISHABLE_KEY=your_anon_key
+VITE_SUPABASE_URL=https://your_project_id.supabase.co

Also add to .gitignore:

.env
.env.local
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 1-1: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 2-2: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 3-3: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)

🪛 Gitleaks (8.30.0)

[high] 2-2: Uncovered a JSON Web Token, which may lead to unauthorized access to web applications and sensitive user data.

(jwt)

🤖 Prompt for AI Agents
In `@scholarship-finder/.env` around lines 1 - 3, Summary: Sensitive Supabase
credentials were committed in .env; remove them and replace with safe
placeholders. Fix: delete the committed .env from the repository (stop tracking
it and remove from the commit/branch), add .env to .gitignore and also
.env.local, create a .env.example containing the keys VITE_SUPABASE_PROJECT_ID,
VITE_SUPABASE_PUBLISHABLE_KEY, and VITE_SUPABASE_URL with placeholder values (no
quoted values) for documentation, and commit only the .env.example; ensure any
real keys are rotated/invalidated outside the repo and confirm no credentials
remain in history or subsequent commits.

24 changes: 24 additions & 0 deletions scholarship-finder/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local
Comment on lines +10 to +13
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

Add .env patterns to prevent credential leakage.

Environment files containing sensitive credentials (Supabase API keys, etc.) are not ignored. This creates a risk that developers could accidentally commit secrets to the repository.

🔒 Proposed fix to add environment file patterns
 node_modules
 dist
 dist-ssr
 *.local
+
+# Environment variables
+.env
+.env.local
+.env*.local
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
node_modules
dist
dist-ssr
*.local
node_modules
dist
dist-ssr
*.local
# Environment variables
.env
.env.local
.env*.local
🤖 Prompt for AI Agents
In `@scholarship-finder/.gitignore` around lines 10 - 13, The repository
.gitignore is missing environment file patterns; update the .gitignore to ignore
environment/credential files by adding patterns like .env, .env.local, .env.*
and .env.*.local (and any other project-specific env variants) so that Supabase
keys and other secrets are not committed; ensure these patterns are appended to
the existing entries (node_modules, dist, dist-ssr, *.local) so all common .env
files are excluded from version control.


# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
139 changes: 139 additions & 0 deletions scholarship-finder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Project Title - Scholarship Match Engine

**Live Link:** https://tinyfishscholarshipfinder.lovable.app/

## What This Project Is -
This project is an AI-powered scholarship discovery and comparison system that automatically finds, scans, and extracts scholarship information directly from official scholarship websites worldwide.

Instead of relying on outdated databases, PDFs, or manual searches, the system pulls live, up-to-date data from source websites and returns it in a clean, structured, and comparable format. Users can search scholarships based on financial need, country/region, academic level, or target university.

## How it works
The system first uses an AI layer to identify and curate relevant scholarship websites based on user input such as:

Country or region

University or institution

Financial need / merit-based criteria

Academic level (undergraduate, postgraduate, PhD)

Field of study

This ensures that only official and relevant sources are used.


## What to Expect
Live, up-to-date data pulled directly from official websites

Parallel web scanning for fast results

Real-time status updates during execution

Structured, comparable output (JSON)

**Demo Video** - https://drive.google.com/file/d/1GXZhJOjiVUP5XcGvTAvRGcYhTWoKXlsE/view?usp=sharing

## Code snippet -
```bash
const response = await fetch("https://mino.ai/v1/automation/run-sse", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "sk-mino-YOUR_API_KEY",
},
body: JSON.stringify({
url: "https://www.gebiz.gov.sg",
goal: "Extract the latest open government tenders. Return JSON with tenderTitle, agency, tenderID, procurementCategory, submissionDeadline, eligibilityCriteria, estimatedValue, tenderStatus, and tenderLink.",
browser_profile: "lite",
}),
});

const reader = response.body!.getReader();
const decoder = new TextDecoder();

while (true) {
const { done, value } = await reader.read();
if (done) break;

const chunk = decoder.decode(value);
for (const line of chunk.split("\n")) {
if (line.startsWith("data: ")) {
const data = JSON.parse(line.slice(6));

// Live browser view
if (data.streamingUrl) {
console.log("Live view:", data.streamingUrl);
}

// Final structured output
if (data.type === "COMPLETE" && data.resultJson) {
console.log("Extracted tenders:", data.resultJson);
}
}
}
}
```
## Tech Stack

Next.js (TypeScript)

Mino API

AI

## Architecture Diagram -
```mermaid
flowchart TB

%% =======================
%% UI LAYER
%% =======================
UI["USER INTERFACE<br/>(React + Tailwind + Dashboard)"]

%% =======================
%% INPUT & ORCHESTRATION
%% =======================
ORCH["Search Orchestration Layer<br/>(Next.js API / Server Actions)"]

%% =======================
%% INTELLIGENCE LAYER
%% =======================
LLM["LLM Intelligence Layer<br/>(ChatGPT API / Gemini API)"]

%% =======================
%% AUTOMATION LAYER
%% =======================
MINO["MINO Web Automation<br/>(Scholarship Discovery & Extraction)"]

%% =======================
%% DATA LAYER
%% =======================
DB["DATA STORE<br/>(Supabase / Postgres)"]

%% =======================
%% DETAIL NODES
%% =======================
LLMD["• Interpret user intent<br/>• Region / University filtering<br/>• Generate authoritative scholarship links"]
MINOD["• Visit scholarship websites<br/>• Extract visible scholarship details<br/>• SSE streaming of results"]
DBD["• Cached scholarships<br/>• Deduplicated entries<br/>• Saved comparisons"]

%% =======================
%% CONNECTIONS
%% =======================
UI --> ORCH

ORCH --> LLM
LLM --> LLMD

ORCH --> MINO
MINO --> MINOD

ORCH --> DB
DB --> DBD

MINO --> ORCH
DB --> ORCH

ORCH --> UI
```
Binary file added scholarship-finder/bun.lockb
Binary file not shown.
20 changes: 20 additions & 0 deletions scholarship-finder/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/index.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}
26 changes: 26 additions & 0 deletions scholarship-finder/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";

export default tseslint.config(
{ ignores: ["dist"] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ["**/*.{ts,tsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
Comment on lines +11 to +15
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
# First, examine the full eslint.config.js file
cd scholarship-finder
wc -l eslint.config.js

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 92


🏁 Script executed:

#!/bin/bash
# Read the full eslint.config.js to understand complete configuration
cd scholarship-finder
cat -n eslint.config.js

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 1019


🏁 Script executed:

#!/bin/bash
# Search for Deno/Node globals in TS/TSX files as suggested in review
cd scholarship-finder
rg -n -g '*.ts' -g '*.tsx' '\bDeno\b|\bprocess\b|\bBuffer\b' -C2

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 1012


🏁 Script executed:

#!/bin/bash
# Check if there are ESLint overrides for supabase/functions in the config
cd scholarship-finder
rg -n 'override|supabase|functions' eslint.config.js

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 55


🏁 Script executed:

#!/bin/bash
# Check project structure to see if supabase/functions should be linted
cd scholarship-finder
ls -la | head -20
fd -t d 'supabase|functions' --max-depth 2

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 1324


🏁 Script executed:

#!/bin/bash
# Search for other Deno/Node globals in TS files (process, Buffer, etc.)
cd scholarship-finder
rg -n -g '*.ts' -g '*.tsx' '\bprocess\b|\bBuffer\b' --max-count 5

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 55


🏁 Script executed:

#!/bin/bash
# Check if there are more TS files in supabase/functions
cd scholarship-finder
find supabase/functions -name '*.ts' -type f

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 120


🏁 Script executed:

#!/bin/bash
# Check what other TS files might be in the project to see full scope
cd scholarship-finder
find . -name '*.ts' -o -name '*.tsx' | grep -v node_modules | head -20

Repository: tinyfish-io/tinyfish-cookbook

Length of output: 732


Add ESLint override with Deno globals for serverless functions.

The config applies to all .ts/.tsx files but only defines browser globals. The repo contains Deno serverless functions (supabase/functions/search-scholarships/index.ts) that use Deno.serve() and Deno.env.get(), which will be flagged as undefined. Add an override for backend files to include globals.deno:

Suggested override
{
  files: ["supabase/functions/**/*.ts"],
  languageOptions: {
    globals: globals.deno,
  },
}
🤖 Prompt for AI Agents
In `@scholarship-finder/eslint.config.js` around lines 11 - 15, The ESLint config
currently sets languageOptions.globals to globals.browser for all TypeScript
files, causing Deno globals (e.g., Deno.serve, Deno.env.get) to be flagged; add
an override entry in eslint.config.js that targets the backend functions (use
files pattern like "supabase/functions/**/*.ts") and set languageOptions.globals
to globals.deno for that override so those files recognize Deno globals; keep
the existing browser globals for the general files setting and ensure the
override object is appended to the top-level overrides array.

plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
"@typescript-eslint/no-unused-vars": "off",
},
},
);
26 changes: 26 additions & 0 deletions scholarship-finder/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- TODO: Set the document title to the name of your application -->
<title>Lovable App</title>
<meta name="description" content="Lovable Generated Project" />
<meta name="author" content="Lovable" />

<!-- TODO: Update og:title to match your application name -->
<meta property="og:title" content="Lovable App" />
<meta property="og:description" content="Lovable Generated Project" />
<meta property="og:type" content="website" />
<meta property="og:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@Lovable" />
<meta name="twitter:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
</head>

<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading