Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
d7ab41b
Merge pull request #64 from ZatoBox/feature/web-layout-components
esspindola Sep 12, 2025
14aca16
Merge pull request #65 from ZatoBox/develop
esspindola Sep 12, 2025
c0a0cc4
Merge pull request #66 from ZatoBox/feature/web-layout-components
esspindola Sep 12, 2025
1381921
Merge pull request #67 from ZatoBox/develop
esspindola Sep 12, 2025
45273f4
Merge pull request #68 from ZatoBox/feature/web-layout-components
esspindola Sep 12, 2025
3a0c804
Merge pull request #69 from ZatoBox/develop
esspindola Sep 12, 2025
72f9576
Merge pull request #70 from ZatoBox/feature/web-layout-components
esspindola Sep 12, 2025
d8f4719
Merge pull request #71 from ZatoBox/develop
esspindola Sep 12, 2025
b40c18d
Merge pull request #72 from ZatoBox/feature/web-layout-components
esspindola Sep 12, 2025
ca043e7
Merge pull request #73 from ZatoBox/develop
esspindola Sep 12, 2025
1e94c7f
Add .gitkeep files to OCR and back directories
esspindola Sep 16, 2025
2c14962
Merge pull request #75 from ZatoBox/feat/migration-structure
esspindola Sep 16, 2025
c90a77f
Implement OCR API with Gemini integration and add image processing fu…
esspindola Sep 16, 2025
4981a44
Merge pull request #76 from ZatoBox/migration/ocr
esspindola Sep 16, 2025
21d2f76
Add layout management functionality with CRUD operations and API routes
esspindola Sep 16, 2025
bd23fa5
Merge pull request #77 from ZatoBox/migration/layout
esspindola Sep 16, 2025
19314fc
Add category management functionality with repository and service layers
esspindola Sep 16, 2025
891b888
Merge pull request #78 from ZatoBox/migration/categories
esspindola Sep 16, 2025
21726eb
Implement inventory management with models, repository, service, and …
esspindola Sep 16, 2025
d971466
Merge pull request #79 from ZatoBox/migration/inventory
esspindola Sep 16, 2025
b4c4225
Implement product management with models, repository, service, and AP…
esspindola Sep 16, 2025
f9b6fce
Merge pull request #80 from ZatoBox/migration/products
esspindola Sep 16, 2025
eebf701
Direccion de botones de Pircing a /login
Joacoc-10 Sep 16, 2025
be2c2f1
Merge pull request #81 from ZatoBox/fix/pircing-section
esspindola Sep 16, 2025
80d2094
Implement user authentication and management features with JWT, bcryp…
esspindola Sep 16, 2025
0ea2939
Add UpgradePage and PricingSectionCompact components; update routing …
esspindola Sep 16, 2025
92915f9
Merge pull request #82 from ZatoBox/feature/payment-screen-register
esspindola Sep 16, 2025
0ff6e70
Add Upgrade button to SideMenu for user navigation
esspindola Sep 17, 2025
a055a8d
Merge pull request #83 from ZatoBox/feature/upgrade-plan-button
esspindola Sep 17, 2025
1e62c5e
Merge pull request #84 from ZatoBox/migration/auth-users
esspindola Sep 17, 2025
f3d5d77
Add polar API key handling and encryption to user model and repositor…
esspindola Sep 17, 2025
c2fd81c
Refactor authentication logic to support Supabase Auth fallback; ensu…
esspindola Sep 17, 2025
83a850b
Refactor ProfilePage and ProfileForm components for consistency; upda…
esspindola Sep 17, 2025
217f2cc
Implement user deletion functionality and enhance profile image manag…
esspindola Sep 17, 2025
03c61e5
Refactor product API integration and enhance product management; impl…
esspindola Sep 17, 2025
db27ab8
Refactor product management API; change DELETE to PATCH for archiving…
esspindola Sep 17, 2025
923821f
Enhance Polar API key handling; ensure proper error messaging for mis…
esspindola Sep 17, 2025
e98136a
Refactor Polar API key handling; simplify encryption logic and improv…
esspindola Sep 17, 2025
b6d0232
Refactor authentication and cookie handling; streamline token managem…
esspindola Sep 18, 2025
26443a9
Refactor ProductCard and SalesDrawer components; enhance product hand…
esspindola Sep 18, 2025
963db3c
Enhance product data retrieval; filter and map image URLs in HomePage…
esspindola Sep 18, 2025
fe6904d
Add polar_organization_id to UserItem and related components; update …
esspindola Sep 18, 2025
2f8644e
Add polar organization ID handling in product creation and inventory …
esspindola Sep 18, 2025
c790611
Update stock handling in ProductCard and inventory management; retrie…
esspindola Sep 18, 2025
8321b9b
Refactor EditProductPage component; streamline state management, inte…
esspindola Sep 18, 2025
b67167a
Implement Polar checkout functionality; add API route for checkout, e…
esspindola Sep 18, 2025
f718a0a
Remove PaymentScreen component; eliminate cash and polar payment hand…
esspindola Sep 18, 2025
872e8e5
Refactor checkout data structure; replace product prices with product…
esspindola Sep 18, 2025
95a0697
Fix success URL construction in Polar checkout; remove fallback to lo…
esspindola Sep 18, 2025
f0943d9
Enhance Polar checkout functionality; include polarOrganizationId in …
esspindola Sep 18, 2025
4afb4de
Filter out products with names starting with 'Order #' in GET request…
esspindola Sep 18, 2025
3c040e9
Implement webhook setup, retrieval, and deletion for Polar API; add a…
esspindola Sep 19, 2025
c00d4b2
Add bulk product creation endpoint and update OCR result handling
esspindola Sep 19, 2025
2eed48c
Refactor ProfilePage layout, remove sidebar, and adjust save button p…
esspindola Sep 19, 2025
c7efda0
Enhance type handling for products in HomePage and ProductGrid; remov…
esspindola Sep 19, 2025
25fd9d2
Merge pull request #85 from ZatoBox/feat/polar-user-id
esspindola Sep 19, 2025
20ecc6f
Merge pull request #86 from ZatoBox/develop
esspindola Sep 19, 2025
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
5 changes: 5 additions & 0 deletions frontend/backend/OCR/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as genai from '@google/generative-ai'

export function createGeminiClient(apiKey: string) {
return new genai.GoogleGenerativeAI(apiKey)
}
113 changes: 113 additions & 0 deletions frontend/backend/OCR/handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { createGeminiClient } from './client';

const RATE_LIMIT_SECONDS = 300;
const userLastRequest: Map<string, number> = new Map();

export type OCRResponse = {
text: string;
confidence?: number;
language?: string;
products?: Array<Record<string, unknown>>;
};

export function parseAuthHeader(authHeader: string | null | undefined) {
if (!authHeader) return 'anonymous';
return authHeader.startsWith('Bearer ')
? authHeader.replace('Bearer ', '')
: authHeader;
}

export const ALLOWED_TYPES = [
'image/png',
'image/jpg',
'image/jpeg',
'image/webp',
'application/pdf',
];

export async function processImageFile(params: {
file: File;
authHeader: string | null | undefined;
env: any;
}): Promise<OCRResponse> {
const { file, authHeader, env } = params;
const userId = parseAuthHeader(authHeader);
const now = Date.now() / 1000;
const last = userLastRequest.get(userId) ?? 0;
if (now - last < RATE_LIMIT_SECONDS)
throw { status: 429, message: 'rate_limited' };
userLastRequest.set(userId, now);

if (!ALLOWED_TYPES.includes(file.type))
throw { status: 400, message: 'invalid_type' };

const apiKey = env.GEMINI_API_KEY;
if (!apiKey) throw { status: 500, message: 'GEMINI_API_KEY not configured' };

const client = createGeminiClient(apiKey);

const arrayBuffer = await file.arrayBuffer();
const uint8 = new Uint8Array(arrayBuffer);
const base64Data = Buffer.from(uint8).toString('base64');

const prompt = env.OCR_PROMPT || '';

const parts = [
{ text: prompt },
{ inlineData: { mimeType: file.type, data: base64Data } },
];

const model = client.getGenerativeModel({ model: 'gemini-1.5-flash' });
const result = await model.generateContent(parts);
const response = result.response;

let extractedText = response.text();

extractedText = extractedText.trim();
if (extractedText.startsWith('```json'))
extractedText = extractedText.slice(7);
if (extractedText.endsWith('```')) extractedText = extractedText.slice(0, -3);
extractedText = extractedText.trim();

try {
const parsed = JSON.parse(extractedText);
let products = null;
if (Array.isArray(parsed)) products = parsed;
else if (parsed && typeof parsed === 'object') {
if ('productos' in parsed) products = (parsed as any).productos;
else products = [parsed];
}

return { text: extractedText, confidence: 0.95, language: 'es', products };
} catch (e) {
return { text: extractedText, confidence: 0.95, language: 'es' };
}
}

export async function sendBulk(params: {
data: any;
authHeader: string | null | undefined;
env: any;
}) {
const { data, authHeader, env } = params;
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
if (authHeader) {
const token = authHeader.startsWith('Bearer ')
? authHeader.replace('Bearer ', '')
: authHeader;
headers['Authorization'] = `Bearer ${token}`;
}
const res = await fetch(`/api/products/bulk`, {
method: 'POST',
headers,
body: JSON.stringify(data),
});
const text = await res.text();
try {
return { status: res.status, ok: res.ok, body: JSON.parse(text) };
} catch {
return { status: res.status, ok: res.ok, body: text };
}
}
36 changes: 36 additions & 0 deletions frontend/backend/auth/models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export type RoleUser = 'admin' | 'manager' | 'user';

export interface UserItem {
id: string;
full_name: string;
email: string;
password?: string | null;
role?: RoleUser;
phone?: string | null;
profile_image?: string | null;
polar_api_key?: string | null;
polar_organization_id?: string | null;
}

export interface LoginRequest {
email: string;
password: string;
}

export interface RegisterRequest {
full_name: string;
email: string;
password: string;
phone?: string | null;
}

export interface UserInfo {
id: string;
email: string;
full_name: string;
phone?: string | null;
address?: string | null;
role: RoleUser;
created_at: string;
last_updated: string;
}
109 changes: 109 additions & 0 deletions frontend/backend/auth/repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { createClient } from '@/utils/supabase/server';
import type { UserItem } from './models';
import { encryptString, decryptString } from '@/utils/crypto';
import { getCurrentTimeWithTimezone } from '@/utils/timezone';

export class UserRepository {
table = 'users';

async findAllUsers(): Promise<UserItem[]> {
const supabase = await createClient();
const { data } = await supabase.from(this.table).select('*');
return (data || []) as UserItem[];
}

async findByEmail(email: string): Promise<UserItem | null> {
if (!email) return null;
const supabase = await createClient();
const { data } = await supabase
.from(this.table)
.select('*')
.eq('email', email)
.limit(1);
if (!data || data.length === 0) return null;
return data[0] as UserItem;
}

async findByUserId(user_id: string): Promise<UserItem | null> {
if (!user_id) return null;
const supabase = await createClient();
const { data } = await supabase
.from(this.table)
.select('*')
.eq('id', user_id)
.limit(1);
if (!data || data.length === 0) return null;
return data[0] as UserItem;
}

async createUser(payload: Record<string, any>): Promise<string> {
const supabase = await createClient();
const now = getCurrentTimeWithTimezone('UTC');
const insertPayload = {
full_name: payload.full_name,
email: payload.email,
password: payload.password,
phone: payload.phone || null,
role: payload.role || 'user',
profile_image: payload.profile_image || null,
polar_api_key: payload.polar_api_key || null,
polar_organization_id: payload.polar_organization_id || null,
created_at: now,
last_updated: now,
};
const { data, error } = await supabase
.from(this.table)
.insert(insertPayload)
.select()
.single();
if (error) throw error;
return (data && data.id) || '';
}

async updateProfile(
user_id: string,
updates: Record<string, any>
): Promise<UserItem> {
const supabase = await createClient();
const now = getCurrentTimeWithTimezone('UTC');
updates.last_updated = now;
if (typeof updates.polar_api_key !== 'undefined') {
updates.polar_api_key = updates.polar_api_key || null;
}
if (typeof updates.polar_organization_id !== 'undefined') {
updates.polar_organization_id = updates.polar_organization_id || null;
}
const { data, error } = await supabase
.from(this.table)
.update(updates)
.eq('id', user_id)
.select()
.single();
if (error) throw error;
return data as UserItem;
}

async getDecryptedPolarKey(user_id: string): Promise<string | null> {
const supabase = await createClient();
const { data } = await supabase
.from(this.table)
.select('polar_api_key')
.eq('id', user_id)
.limit(1)
.single();
if (!data || !data.polar_api_key) return null;
return data.polar_api_key as string;
}

async deleteUser(user_id: string): Promise<UserItem | null> {
const supabase = await createClient();
const { data, error } = await supabase
.from(this.table)
.delete()
.eq('id', user_id)
.select()
.single();
if (error) throw error;
return (data as UserItem) || null;
}
}
Loading