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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/buid_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ on:
jobs:
build-and-deploy:
# Prod
# runs-on: ubuntu-latest
runs-on: ubuntu-latest
# Self hosted
runs-on: self-hosted
#runs-on: self-hosted
environment: production # This tells GitHub to use the production environment secrets

steps:
Expand Down
3 changes: 3 additions & 0 deletions .husky/post-checkout
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { printf >&2 "\n%s\n\n" "This repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'post-checkout' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks')."; exit 2; }
git lfs post-checkout "$@"
3 changes: 3 additions & 0 deletions .husky/post-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { printf >&2 "\n%s\n\n" "This repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'post-commit' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks')."; exit 2; }
git lfs post-commit "$@"
3 changes: 3 additions & 0 deletions .husky/post-merge
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { printf >&2 "\n%s\n\n" "This repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'post-merge' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks')."; exit 2; }
git lfs post-merge "$@"
3 changes: 3 additions & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { printf >&2 "\n%s\n\n" "This repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'pre-push' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks')."; exit 2; }
git lfs pre-push "$@"
1 change: 1 addition & 0 deletions frontend/db/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.db filter=lfs diff=lfs merge=lfs -text
3 changes: 3 additions & 0 deletions frontend/db/svg_icons/svg-icons-db.db
Git LFS file not shown
55 changes: 55 additions & 0 deletions frontend/db/svg_icons/svg-icons-schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
export interface Icon {
id: number;
cluster: string;
name: string;
base64: string;
description: string;
usecases: string;
synonyms: string[]; // JSON array stored as TEXT, SQLite can query with json_each()
tags: string[]; // JSON array stored as TEXT, SQLite can query with json_each()
industry: string;
emotional_cues: string;
enhanced: number; // 0 or 1, convert to boolean
}

export interface Cluster {
name: string;
count: number;
source_folder: string;
path: string;
keywords: string[]; // JSON array stored as TEXT, SQLite can query with json_each()
features: string[]; // JSON array stored as TEXT, SQLite can query with json_each()
title: string;
description: string;
}

export interface Overview {
id: number;
total_count: number;
}

// Raw database row types (before JSON parsing)
export interface RawIconRow {
id: number;
cluster: string;
name: string;
base64: string;
description: string;
usecases: string;
synonyms: string; // JSON string before parsing
tags: string; // JSON string before parsing
industry: string;
emotional_cues: string;
enhanced: number;
}

export interface RawClusterRow {
name: string;
count: number;
source_folder: string;
path: string;
keywords: string; // JSON string before parsing
features: string; // JSON string before parsing
title: string;
description: string;
}
149 changes: 149 additions & 0 deletions frontend/db/svg_icons/svg-icons-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import Database from 'better-sqlite3';
import path from 'path';
import type {
Cluster,
Icon,
Overview,
RawClusterRow,
RawIconRow,
} from './svg-icons-schema';

let dbInstance: Database.Database | null = null;

function getDbPath(): string {
return path.resolve(process.cwd(), 'db/svg_icons/svg-icons-db.db');
}

export function getDb(): Database.Database {
if (dbInstance) return dbInstance;
const dbPath = getDbPath();
dbInstance = new Database(dbPath, { readonly: true });
// Improve read performance for build-time queries
dbInstance.pragma('journal_mode = OFF');
dbInstance.pragma('synchronous = OFF');
return dbInstance;
}

export function getClusterIcons(cluster: string, limit = 10): Icon[] {
const db = getDb();
const stmt = db.prepare(
`SELECT id, cluster, name, base64, description, usecases,
json(synonyms) as synonyms, json(tags) as tags,
industry, emotional_cues, enhanced
FROM icon WHERE cluster = ? ORDER BY name LIMIT ?`
);
const results = stmt.all(cluster, limit) as RawIconRow[];
return results.map((row) => ({
...row,
synonyms: JSON.parse(row.synonyms || '[]') as string[],
tags: JSON.parse(row.tags || '[]') as string[],
})) as Icon[];
}

export function getClusters(): Cluster[] {
const db = getDb();
const stmt = db.prepare(
`SELECT name, count, source_folder, path,
json(keywords) as keywords, json(features) as features,
title, description
FROM cluster ORDER BY name`
);
const results = stmt.all() as RawClusterRow[];
return results.map((row) => ({
...row,
keywords: JSON.parse(row.keywords || '[]') as string[],
features: JSON.parse(row.features || '[]') as string[],
})) as Cluster[];
}

export function getTotalIcons(): number {
const db = getDb();
const row = db
.prepare('SELECT total_count FROM overview WHERE id = 1')
.get() as Overview | undefined;
return row?.total_count ?? 0;
}

export function getIconsByCluster(cluster: string): Icon[] {
const db = getDb();
const stmt = db.prepare(
`SELECT id, cluster, name, base64, description, usecases,
json(synonyms) as synonyms, json(tags) as tags,
industry, emotional_cues, enhanced
FROM icon WHERE cluster = ? ORDER BY name`
);
const results = stmt.all(cluster) as RawIconRow[];
return results.map((row) => ({
...row,
synonyms: JSON.parse(row.synonyms || '[]') as string[],
tags: JSON.parse(row.tags || '[]') as string[],
})) as Icon[];
}

export function getClusterByName(name: string): Cluster | null {
const db = getDb();
const stmt = db.prepare(
`SELECT name, count, source_folder, path,
json(keywords) as keywords, json(features) as features,
title, description
FROM cluster WHERE name = ?`
);
const result = stmt.get(name) as RawClusterRow | undefined;
if (!result) return null;
return {
...result,
keywords: JSON.parse(result.keywords || '[]') as string[],
features: JSON.parse(result.features || '[]') as string[],
} as Cluster;
}

// Get icon by category (cluster display name) and icon name (without .svg extension)
export function getIconByCategoryAndName(
category: string,
iconName: string
): Icon | null {
const db = getDb();
// First, get the cluster to find the source_folder (actual cluster key)
const clusterData = getClusterByName(category);
if (!clusterData) return null;

// Build the filename with .svg extension
const filename = iconName.includes('.svg') ? iconName : `${iconName}.svg`;

// Query icon using source_folder (cluster key) and filename
const stmt = db.prepare(
`SELECT id, cluster, name, base64, description, usecases,
json(synonyms) as synonyms, json(tags) as tags,
industry, emotional_cues, enhanced
FROM icon WHERE cluster = ? AND name = ?`
);
const result = stmt.get(clusterData.source_folder || category, filename) as
| RawIconRow
| undefined;
if (!result) return null;

return {
...result,
synonyms: JSON.parse(result.synonyms || '[]') as string[],
tags: JSON.parse(result.tags || '[]') as string[],
} as Icon;
}

// Example helper function to query icons by tag using json_each
export function getIconsByTag(tag: string): Icon[] {
const db = getDb();
const stmt = db.prepare(
`SELECT DISTINCT i.id, i.cluster, i.name, i.base64, i.description, i.usecases,
json(i.synonyms) as synonyms, json(i.tags) as tags,
i.industry, i.emotional_cues, i.enhanced
FROM icon i, json_each(i.tags)
WHERE json_each.value = ?
ORDER BY i.cluster, i.name`
);
const results = stmt.all(tag) as RawIconRow[];
return results.map((row) => ({
...row,
synonyms: JSON.parse(row.synonyms || '[]') as string[],
tags: JSON.parse(row.tags || '[]') as string[],
})) as Icon[];
}
Loading