snapshot of https://github.com/marmelab/atomic-crm.git (main, 2026-09-18; blobs > 131072B omitted)
Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s

This commit is contained in:
upstream-mirror
2026-09-18 01:48:50 +05:00
commit 93ecc1a6f8
787 changed files with 75240 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
// Based on https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/_shared/jwt/default.ts
import * as jose from "jsr:@panva/jose@6";
import { createClient, type User } from "jsr:@supabase/supabase-js@2";
import { createErrorResponse } from "./utils.ts";
const SUPABASE_JWT_ISSUER =
Deno.env.get("SB_JWT_ISSUER") ?? Deno.env.get("SUPABASE_URL") + "/auth/v1";
const SUPABASE_JWT_KEYS = jose.createRemoteJWKSet(
new URL(Deno.env.get("SUPABASE_URL")! + "/auth/v1/.well-known/jwks.json"),
);
function getAuthToken(req: Request) {
const authHeader = req.headers.get("authorization");
if (!authHeader) {
throw new Error("Missing authorization header");
}
const [bearer, token] = authHeader.split(" ");
if (bearer !== "Bearer") {
throw new Error(`Auth header is not 'Bearer {token}'`);
}
return token;
}
function verifySupabaseJWT(jwt: string) {
return jose.jwtVerify(jwt, SUPABASE_JWT_KEYS, {
issuer: SUPABASE_JWT_ISSUER,
});
}
/**
* Validates the Authorization header to ensure that a user is authenticated.
*/
export const AuthMiddleware = async (
req: Request,
next: (req: Request) => Promise<Response>,
) => {
if (req.method === "OPTIONS") return await next(req);
try {
const token = getAuthToken(req);
const isValidJWT = await verifySupabaseJWT(token);
if (isValidJWT) return await next(req);
return createErrorResponse(401, "Invalid authentication");
} catch (e) {
return createErrorResponse(401, e?.toString() || "Unauthorized");
}
};
/**
* Get the authenticated user using the authorization header.
* User will be undefined for OPTIONS requests.
*/
export const UserMiddleware = async (
req: Request,
next: (req: Request, user?: User) => Promise<Response>,
) => {
if (req.method === "OPTIONS") return await next(req);
try {
const authHeader = req.headers.get("Authorization")!;
const localClient = createClient(
Deno.env.get("SUPABASE_URL") ?? "",
Deno.env.get("SB_PUBLISHABLE_KEY") ?? "",
{ global: { headers: { Authorization: authHeader } } },
);
const { data, error: authError } = await localClient.auth.getUser();
if (!data?.user || authError) {
return createErrorResponse(401, "Unauthorized");
}
return next(req, data.user);
} catch (err) {
return createErrorResponse(401, err?.toString() || "Unauthorized");
}
};

View File

@@ -0,0 +1,20 @@
export const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"authorization, x-client-info, apikey, content-type",
"Access-Control-Allow-Methods": "POST, PATCH, DELETE",
};
/**
* Handle OPTIONS requests for CORS preflight.
*/
export function OptionsMiddleware(
req: Request,
next: (req: Request) => Promise<Response>,
) {
if (req.method === "OPTIONS") {
return new Response(null, { status: 204, headers: corsHeaders });
}
return next(req);
}

View File

@@ -0,0 +1,187 @@
import type {
DatabaseConnection,
Driver,
QueryResult,
} from "https://esm.sh/kysely@0.27.2";
import {
CompiledQuery,
Kysely,
PostgresAdapter,
PostgresIntrospector,
PostgresQueryCompiler,
type Generated,
} from "https://esm.sh/kysely@0.27.2";
export { CompiledQuery };
import type { PoolClient } from "https://deno.land/x/postgres@v0.17.0/mod.ts";
import { Pool } from "https://deno.land/x/postgres@v0.17.0/mod.ts";
// Database schema types
export interface ContactsTable {
id: Generated<number>;
first_name: string | null;
last_name: string | null;
gender: string | null;
title: string | null;
email_jsonb: unknown | null; // JSONB array
phone_jsonb: unknown | null; // JSONB array
background: string | null;
avatar: unknown | null; // JSONB
first_seen: Date | null;
last_seen: Date | null;
has_newsletter: boolean | null;
status: string | null;
tags: number[] | null;
company_id: number | null;
sales_id: number | null;
linkedin_url: string | null;
}
interface TasksTable {
id: Generated<number>;
contact_id: number;
type: string | null;
text: string | null;
due_date: Date;
done_date: Date | null;
sales_id: number | null;
}
interface ContactNotesTable {
id: Generated<number>;
contact_id: number;
text: string | null;
date: Date | null;
sales_id: number | null;
status: string | null;
attachments: unknown[] | null; // JSONB array
}
interface DealsTable {
id: Generated<number>;
name: string;
company_id: number | null;
contact_ids: number[];
category: string | null;
stage: string;
description: string | null;
amount: number | null;
created_at: Date;
updated_at: Date;
archived_at: Date | null;
expected_closing_date: Date | null;
sales_id: number | null;
index: number | null;
}
interface Database {
contacts: ContactsTable;
tasks: TasksTable;
contact_notes: ContactNotesTable;
deals: DealsTable;
}
// Deno Postgres Driver for Kysely
class DenoPostgresDriver implements Driver {
private pool: Pool;
private connections = new WeakMap<PoolClient, DatabaseConnection>();
constructor(pool: Pool) {
this.pool = pool;
}
async init(): Promise<void> {
// Connection pool is already initialized
}
async acquireConnection(): Promise<DatabaseConnection> {
const client = await this.pool.connect();
let connection = this.connections.get(client);
if (!connection) {
connection = new DenoPostgresConnection(client);
this.connections.set(client, connection);
}
return connection;
}
async beginTransaction(connection: DatabaseConnection): Promise<void> {
await connection.executeQuery(CompiledQuery.raw("begin"));
}
async commitTransaction(connection: DatabaseConnection): Promise<void> {
await connection.executeQuery(CompiledQuery.raw("commit"));
}
async rollbackTransaction(connection: DatabaseConnection): Promise<void> {
await connection.executeQuery(CompiledQuery.raw("rollback"));
}
async releaseConnection(connection: DatabaseConnection): Promise<void> {
(connection as DenoPostgresConnection).release();
}
async destroy(): Promise<void> {
await this.pool.end();
}
}
class DenoPostgresConnection implements DatabaseConnection {
private client: PoolClient;
constructor(client: PoolClient) {
this.client = client;
}
async executeQuery<O>(compiledQuery: CompiledQuery): Promise<QueryResult<O>> {
const result = await this.client.queryObject<O>({
text: compiledQuery.sql,
args: compiledQuery.parameters as unknown[],
});
if (
result.command === "INSERT" ||
result.command === "UPDATE" ||
result.command === "DELETE"
) {
return {
numAffectedRows: BigInt(result.rowCount ?? 0),
rows: result.rows ?? [],
};
}
return {
rows: result.rows ?? [],
};
}
streamQuery<O>(
_compiledQuery: CompiledQuery,
_chunkSize?: number,
): AsyncIterableIterator<QueryResult<O>> {
throw new Error("Deno Postgres driver does not support streaming");
}
release() {
this.client.release();
}
}
// Create connection pool
// Use SUPABASE_DB_URL if available (production), otherwise fall back to local dev connection string
const connectionString =
Deno.env.get("SUPABASE_DB_URL") ||
"postgresql://postgres:postgres@db:5432/postgres";
const pool = new Pool(connectionString, 1); // Single connection for edge functions
// Create and export Kysely instance
export const db = new Kysely<Database>({
dialect: {
createAdapter: () => new PostgresAdapter(),
createDriver: () => new DenoPostgresDriver(pool),
createIntrospector: (db: Kysely<any>) => new PostgresIntrospector(db),
createQueryCompiler: () => new PostgresQueryCompiler(),
},
});

View File

@@ -0,0 +1,15 @@
import { type User } from "jsr:@supabase/supabase-js@2";
import { supabaseAdmin } from "./supabaseAdmin.ts";
/**
* Get the sale associated to the provided user.
*/
export const getUserSale = async (user: User) => {
return (
await supabaseAdmin
.from("sales")
.select("*")
.eq("user_id", user.id)
.single()
)?.data;
};

View File

@@ -0,0 +1,15 @@
import type { SupabaseClient } from "jsr:@supabase/supabase-js@2";
import { createClient } from "jsr:@supabase/supabase-js@2";
export const supabaseAdmin: SupabaseClient = createClient(
Deno.env.get("SUPABASE_URL") ?? "",
// TODO - When Supabase finally provides the project's secret key to the functions, we'll need to use it here instead of the service role key.
// See https://supabase.com/docs/guides/functions/auth#get-api-details
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "",
{
auth: {
autoRefreshToken: false,
persistSession: false,
},
},
);

View File

@@ -0,0 +1,12 @@
import { corsHeaders } from "./cors.ts";
export function createErrorResponse(
status: number,
message: string,
custom: Record<string, unknown> = {},
) {
return new Response(JSON.stringify({ status, message, ...custom }), {
headers: { "Content-Type": "application/json", ...corsHeaders },
status,
});
}