feat: no login experience and prem tokens
Some checks are pending
Build and Push Docker Images / tag_release (push) Waiting to run
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_backend, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64) (push) Blocked by required conditions
Build and Push Docker Images / build (./surfsense_web, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (backend, surfsense-backend) (push) Blocked by required conditions
Build and Push Docker Images / create_manifest (web, surfsense-web) (push) Blocked by required conditions

This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-04-15 17:02:00 -07:00
parent 87452bb315
commit ff4e0f9b62
68 changed files with 5914 additions and 121 deletions

View file

@ -0,0 +1,97 @@
import {
type AnonChatRequest,
type AnonModel,
type AnonQuotaResponse,
anonChatRequest,
anonQuotaResponse,
getAnonModelResponse,
getAnonModelsResponse,
} from "@/contracts/types/anonymous-chat.types";
import { BACKEND_URL } from "../env-config";
import { ValidationError } from "../error";
const BASE = "/api/v1/public/anon-chat";
class AnonymousChatApiService {
private baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
private fullUrl(path: string): string {
return `${this.baseUrl}${BASE}${path}`;
}
getModels = async (): Promise<AnonModel[]> => {
const res = await fetch(this.fullUrl("/models"), { credentials: "include" });
if (!res.ok) throw new Error(`Failed to fetch models: ${res.status}`);
const data = await res.json();
const parsed = getAnonModelsResponse.safeParse(data);
if (!parsed.success) console.error("Invalid anon models response:", parsed.error);
return data;
};
getModel = async (slug: string): Promise<AnonModel> => {
const res = await fetch(this.fullUrl(`/models/${encodeURIComponent(slug)}`), {
credentials: "include",
});
if (!res.ok) {
if (res.status === 404) throw new Error("Model not found");
throw new Error(`Failed to fetch model: ${res.status}`);
}
const data = await res.json();
const parsed = getAnonModelResponse.safeParse(data);
if (!parsed.success) console.error("Invalid anon model response:", parsed.error);
return data;
};
getQuota = async (): Promise<AnonQuotaResponse> => {
const res = await fetch(this.fullUrl("/quota"), { credentials: "include" });
if (!res.ok) throw new Error(`Failed to fetch quota: ${res.status}`);
const data = await res.json();
const parsed = anonQuotaResponse.safeParse(data);
if (!parsed.success) console.error("Invalid anon quota response:", parsed.error);
return data;
};
streamChat = async (request: AnonChatRequest): Promise<Response> => {
const validated = anonChatRequest.safeParse(request);
if (!validated.success) {
throw new ValidationError(
`Invalid request: ${validated.error.issues.map((i) => i.message).join(", ")}`
);
}
return fetch(this.fullUrl("/stream"), {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify(validated.data),
});
};
uploadDocument = async (file: File): Promise<{ filename: string; size_bytes: number }> => {
const formData = new FormData();
formData.append("file", file);
const res = await fetch(this.fullUrl("/upload"), {
method: "POST",
credentials: "include",
body: formData,
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.detail || `Upload failed: ${res.status}`);
}
return res.json();
};
getDocument = async (): Promise<{ filename: string; size_bytes: number } | null> => {
const res = await fetch(this.fullUrl("/document"), { credentials: "include" });
if (res.status === 404) return null;
if (!res.ok) throw new Error(`Failed to fetch document: ${res.status}`);
return res.json();
};
}
export const anonymousChatApiService = new AnonymousChatApiService(BACKEND_URL);

View file

@ -1,11 +1,18 @@
import {
type CreateCheckoutSessionRequest,
type CreateCheckoutSessionResponse,
type CreateTokenCheckoutSessionRequest,
type CreateTokenCheckoutSessionResponse,
createCheckoutSessionResponse,
createTokenCheckoutSessionResponse,
type GetPagePurchasesResponse,
type GetTokenPurchasesResponse,
getPagePurchasesResponse,
getTokenPurchasesResponse,
type StripeStatusResponse,
stripeStatusResponse,
type TokenStripeStatusResponse,
tokenStripeStatusResponse,
} from "@/contracts/types/stripe.types";
import { baseApiService } from "./base-api.service";
@ -29,6 +36,24 @@ class StripeApiService {
getStatus = async (): Promise<StripeStatusResponse> => {
return baseApiService.get("/api/v1/stripe/status", stripeStatusResponse);
};
createTokenCheckoutSession = async (
request: CreateTokenCheckoutSessionRequest
): Promise<CreateTokenCheckoutSessionResponse> => {
return baseApiService.post(
"/api/v1/stripe/create-token-checkout-session",
createTokenCheckoutSessionResponse,
{ body: request }
);
};
getTokenStatus = async (): Promise<TokenStripeStatusResponse> => {
return baseApiService.get("/api/v1/stripe/token-status", tokenStripeStatusResponse);
};
getTokenPurchases = async (): Promise<GetTokenPurchasesResponse> => {
return baseApiService.get("/api/v1/stripe/token-purchases", getTokenPurchasesResponse);
};
}
export const stripeApiService = new StripeApiService();

View file

@ -18,6 +18,7 @@ const PUBLIC_ROUTE_PREFIXES = [
"/desktop/login",
"/docs",
"/public",
"/free",
"/invite",
"/contact",
"/pricing",