mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-26 21:39:43 +02:00
Merge remote-tracking branch 'upstream/dev' into feat/obsidian-plugin
This commit is contained in:
commit
16ea8e2401
12 changed files with 100 additions and 20 deletions
43
.github/workflows/desktop-release.yml
vendored
43
.github/workflows/desktop-release.yml
vendored
|
|
@ -22,6 +22,7 @@ on:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|
@ -58,6 +59,22 @@ jobs:
|
||||||
fi
|
fi
|
||||||
echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT"
|
echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Detect Windows signing eligibility
|
||||||
|
id: sign
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# Sign Windows builds only on production v* tags (not beta-v*, not workflow_dispatch).
|
||||||
|
# This matches the single OIDC federated credential configured in Entra ID.
|
||||||
|
if [ "${{ matrix.os }}" = "windows-latest" ] \
|
||||||
|
&& [ "${{ github.event_name }}" = "push" ] \
|
||||||
|
&& [[ "$GITHUB_REF" == refs/tags/v* ]]; then
|
||||||
|
echo "enabled=true" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Windows signing: ENABLED (v* tag on windows-latest)"
|
||||||
|
else
|
||||||
|
echo "enabled=false" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Windows signing: skipped"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v5
|
uses: pnpm/action-setup@v5
|
||||||
|
|
||||||
|
|
@ -98,7 +115,31 @@ jobs:
|
||||||
|
|
||||||
- name: Package & Publish
|
- name: Package & Publish
|
||||||
shell: bash
|
shell: bash
|
||||||
run: pnpm exec electron-builder ${{ matrix.platform }} --config electron-builder.yml --publish ${{ inputs.publish || 'always' }} -c.extraMetadata.version=${{ steps.version.outputs.VERSION }}
|
run: |
|
||||||
|
CMD=(pnpm exec electron-builder ${{ matrix.platform }} \
|
||||||
|
--config electron-builder.yml \
|
||||||
|
--publish "${{ inputs.publish || 'always' }}" \
|
||||||
|
-c.extraMetadata.version="${{ steps.version.outputs.VERSION }}")
|
||||||
|
|
||||||
|
if [ "${{ steps.sign.outputs.enabled }}" = "true" ]; then
|
||||||
|
CMD+=(-c.win.azureSignOptions.publisherName="$WINDOWS_PUBLISHER_NAME")
|
||||||
|
CMD+=(-c.win.azureSignOptions.endpoint="$AZURE_CODESIGN_ENDPOINT")
|
||||||
|
CMD+=(-c.win.azureSignOptions.codeSigningAccountName="$AZURE_CODESIGN_ACCOUNT")
|
||||||
|
CMD+=(-c.win.azureSignOptions.certificateProfileName="$AZURE_CODESIGN_PROFILE")
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${CMD[@]}"
|
||||||
working-directory: surfsense_desktop
|
working-directory: surfsense_desktop
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
WINDOWS_PUBLISHER_NAME: ${{ vars.WINDOWS_PUBLISHER_NAME }}
|
||||||
|
AZURE_CODESIGN_ENDPOINT: ${{ vars.AZURE_CODESIGN_ENDPOINT }}
|
||||||
|
AZURE_CODESIGN_ACCOUNT: ${{ vars.AZURE_CODESIGN_ACCOUNT }}
|
||||||
|
AZURE_CODESIGN_PROFILE: ${{ vars.AZURE_CODESIGN_PROFILE }}
|
||||||
|
# Service principal credentials for Azure.Identity EnvironmentCredential used by the
|
||||||
|
# TrustedSigning PowerShell module. Only populated when signing is enabled.
|
||||||
|
# electron-builder 26 does not yet support OIDC federated tokens for Azure signing,
|
||||||
|
# so we fall back to client-secret auth. Rotate AZURE_CLIENT_SECRET before expiry.
|
||||||
|
AZURE_TENANT_ID: ${{ steps.sign.outputs.enabled == 'true' && secrets.AZURE_TENANT_ID || '' }}
|
||||||
|
AZURE_CLIENT_ID: ${{ steps.sign.outputs.enabled == 'true' && secrets.AZURE_CLIENT_ID || '' }}
|
||||||
|
AZURE_CLIENT_SECRET: ${{ steps.sign.outputs.enabled == 'true' && secrets.AZURE_CLIENT_SECRET || '' }}
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
0.0.16
|
0.0.19
|
||||||
|
|
|
||||||
|
|
@ -114,8 +114,19 @@ def _surfsense_error_handler(request: Request, exc: SurfSenseError) -> JSONRespo
|
||||||
|
|
||||||
|
|
||||||
def _http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse:
|
def _http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse:
|
||||||
"""Wrap FastAPI/Starlette HTTPExceptions into the standard envelope."""
|
"""Wrap FastAPI/Starlette HTTPExceptions into the standard envelope.
|
||||||
|
|
||||||
|
5xx sanitization policy:
|
||||||
|
- 500 responses are sanitized (replaced with ``GENERIC_5XX_MESSAGE``) because
|
||||||
|
they usually wrap raw internal errors and may leak sensitive info.
|
||||||
|
- Other 5xx statuses (501, 502, 503, 504, ...) are raised explicitly by
|
||||||
|
route code to communicate a specific, user-safe operational state
|
||||||
|
(e.g. 503 "Page purchases are temporarily unavailable."). Those details
|
||||||
|
are preserved so the frontend can render them, but the error is still
|
||||||
|
logged server-side.
|
||||||
|
"""
|
||||||
rid = _get_request_id(request)
|
rid = _get_request_id(request)
|
||||||
|
should_sanitize = exc.status_code == 500
|
||||||
|
|
||||||
# Structured dict details (e.g. {"code": "CAPTCHA_REQUIRED", "message": "..."})
|
# Structured dict details (e.g. {"code": "CAPTCHA_REQUIRED", "message": "..."})
|
||||||
# are preserved so the frontend can parse them.
|
# are preserved so the frontend can parse them.
|
||||||
|
|
@ -130,9 +141,9 @@ def _http_exception_handler(request: Request, exc: HTTPException) -> JSONRespons
|
||||||
exc.status_code,
|
exc.status_code,
|
||||||
message,
|
message,
|
||||||
)
|
)
|
||||||
if exc.status_code == 500:
|
if should_sanitize:
|
||||||
message = GENERIC_5XX_MESSAGE
|
message = GENERIC_5XX_MESSAGE
|
||||||
err_code = "INTERNAL_ERROR"
|
err_code = "INTERNAL_ERROR"
|
||||||
body = {
|
body = {
|
||||||
"error": {
|
"error": {
|
||||||
"code": err_code,
|
"code": err_code,
|
||||||
|
|
@ -159,8 +170,8 @@ def _http_exception_handler(request: Request, exc: HTTPException) -> JSONRespons
|
||||||
exc.status_code,
|
exc.status_code,
|
||||||
detail,
|
detail,
|
||||||
)
|
)
|
||||||
if exc.status_code == 500:
|
if should_sanitize:
|
||||||
detail = GENERIC_5XX_MESSAGE
|
detail = GENERIC_5XX_MESSAGE
|
||||||
code = _status_to_code(exc.status_code, detail)
|
code = _status_to_code(exc.status_code, detail)
|
||||||
return _build_error_response(exc.status_code, detail, code=code, request_id=rid)
|
return _build_error_response(exc.status_code, detail, code=code, request_id=rid)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[project]
|
[project]
|
||||||
name = "surf-new-backend"
|
name = "surf-new-backend"
|
||||||
version = "0.0.16"
|
version = "0.0.19"
|
||||||
description = "SurfSense Backend"
|
description = "SurfSense Backend"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,20 @@ def _make_test_app():
|
||||||
async def raise_http_500():
|
async def raise_http_500():
|
||||||
raise HTTPException(status_code=500, detail="secret db password leaked")
|
raise HTTPException(status_code=500, detail="secret db password leaked")
|
||||||
|
|
||||||
|
@app.get("/http-503")
|
||||||
|
async def raise_http_503():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=503,
|
||||||
|
detail="Page purchases are temporarily unavailable.",
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.get("/http-502")
|
||||||
|
async def raise_http_502():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=502,
|
||||||
|
detail="Unable to create Stripe checkout session.",
|
||||||
|
)
|
||||||
|
|
||||||
@app.get("/surfsense-connector")
|
@app.get("/surfsense-connector")
|
||||||
async def raise_connector():
|
async def raise_connector():
|
||||||
raise ConnectorError("GitHub API returned 401")
|
raise ConnectorError("GitHub API returned 401")
|
||||||
|
|
@ -184,6 +198,20 @@ class TestHTTPExceptionHandler:
|
||||||
assert body["error"]["message"] == GENERIC_5XX_MESSAGE
|
assert body["error"]["message"] == GENERIC_5XX_MESSAGE
|
||||||
assert body["error"]["code"] == "INTERNAL_ERROR"
|
assert body["error"]["code"] == "INTERNAL_ERROR"
|
||||||
|
|
||||||
|
def test_503_preserves_detail(self, client):
|
||||||
|
# Intentional 503s (e.g. feature flag off) must surface the developer
|
||||||
|
# message so the frontend can render actionable copy.
|
||||||
|
body = _assert_envelope(client.get("/http-503"), 503)
|
||||||
|
assert (
|
||||||
|
body["error"]["message"] == "Page purchases are temporarily unavailable."
|
||||||
|
)
|
||||||
|
assert body["error"]["message"] != GENERIC_5XX_MESSAGE
|
||||||
|
|
||||||
|
def test_502_preserves_detail(self, client):
|
||||||
|
body = _assert_envelope(client.get("/http-502"), 502)
|
||||||
|
assert body["error"]["message"] == "Unable to create Stripe checkout session."
|
||||||
|
assert body["error"]["message"] != GENERIC_5XX_MESSAGE
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# SurfSenseError hierarchy
|
# SurfSenseError hierarchy
|
||||||
|
|
|
||||||
2
surfsense_backend/uv.lock
generated
2
surfsense_backend/uv.lock
generated
|
|
@ -7947,7 +7947,7 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "surf-new-backend"
|
name = "surf-new-backend"
|
||||||
version = "0.0.16"
|
version = "0.0.19"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "alembic" },
|
{ name = "alembic" },
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "surfsense_browser_extension",
|
"name": "surfsense_browser_extension",
|
||||||
"displayName": "Surfsense Browser Extension",
|
"displayName": "Surfsense Browser Extension",
|
||||||
"version": "0.0.16",
|
"version": "0.0.19",
|
||||||
"description": "Extension to collect Browsing History for SurfSense.",
|
"description": "Extension to collect Browsing History for SurfSense.",
|
||||||
"author": "https://github.com/MODSetter",
|
"author": "https://github.com/MODSetter",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "surfsense-desktop",
|
"name": "surfsense-desktop",
|
||||||
"version": "0.0.16",
|
"version": "0.0.19",
|
||||||
"description": "SurfSense Desktop App",
|
"description": "SurfSense Desktop App",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ async function getAllModels(): Promise<AnonModel[]> {
|
||||||
|
|
||||||
function buildSeoTitle(model: AnonModel): string {
|
function buildSeoTitle(model: AnonModel): string {
|
||||||
if (model.seo_title) return model.seo_title;
|
if (model.seo_title) return model.seo_title;
|
||||||
return `${model.name} Free Online Without Login | No Sign-Up AI Chat | SurfSense`;
|
return `Chat with ${model.name} Free, No Login | SurfSense`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSeoDescription(model: AnonModel): string {
|
function buildSeoDescription(model: AnonModel): string {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import type { AnonModel } from "@/contracts/types/anonymous-chat.types";
|
||||||
import { BACKEND_URL } from "@/lib/env-config";
|
import { BACKEND_URL } from "@/lib/env-config";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "ChatGPT Free Online Without Login | Chat GPT No Login, Claude AI Free | SurfSense",
|
title: "Free AI Chat, No Login Required | SurfSense",
|
||||||
description:
|
description:
|
||||||
"Use ChatGPT free online without login. Chat with GPT-4, Claude AI, Gemini and more for free. No sign-up required. Open source NotebookLM alternative with free AI chat and document Q&A.",
|
"Use ChatGPT free online without login. Chat with GPT-4, Claude AI, Gemini and more for free. No sign-up required. Open source NotebookLM alternative with free AI chat and document Q&A.",
|
||||||
keywords: [
|
keywords: [
|
||||||
|
|
@ -67,7 +67,7 @@ export const metadata: Metadata = {
|
||||||
canonical: "https://surfsense.com/free",
|
canonical: "https://surfsense.com/free",
|
||||||
},
|
},
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: "ChatGPT Free Online Without Login | Claude AI Free No Login | SurfSense",
|
title: "Free AI Chat, No Login Required | SurfSense",
|
||||||
description:
|
description:
|
||||||
"Use ChatGPT free online without login. Chat with GPT-4, Claude AI, Gemini and 100+ AI models. Open source NotebookLM alternative.",
|
"Use ChatGPT free online without login. Chat with GPT-4, Claude AI, Gemini and 100+ AI models. Open source NotebookLM alternative.",
|
||||||
url: "https://surfsense.com/free",
|
url: "https://surfsense.com/free",
|
||||||
|
|
@ -84,7 +84,7 @@ export const metadata: Metadata = {
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: "summary_large_image",
|
card: "summary_large_image",
|
||||||
title: "ChatGPT Free Online Without Login | Claude AI Free No Login | SurfSense",
|
title: "Free AI Chat, No Login Required | SurfSense",
|
||||||
description:
|
description:
|
||||||
"Use ChatGPT free online without login. Chat with GPT-4, Claude AI, Gemini and more. No sign-up needed.",
|
"Use ChatGPT free online without login. Chat with GPT-4, Claude AI, Gemini and more. No sign-up needed.",
|
||||||
images: ["/og-image.png"],
|
images: ["/og-image.png"],
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ export const metadata: Metadata = {
|
||||||
alternates: {
|
alternates: {
|
||||||
canonical: "https://surfsense.com",
|
canonical: "https://surfsense.com",
|
||||||
},
|
},
|
||||||
title: "SurfSense - NotebookLM Alternative | Free ChatGPT & Claude AI",
|
title: "SurfSense – Open Source, Privacy-Focused NotebookLM Alternative for Teams",
|
||||||
description:
|
description:
|
||||||
"Open source NotebookLM alternative for teams with no data limits. Use ChatGPT, Claude AI, and any AI model for free.",
|
"Open source NotebookLM alternative for teams with no data limits. Use ChatGPT, Claude AI, and any AI model for free.",
|
||||||
keywords: [
|
keywords: [
|
||||||
|
|
@ -87,7 +87,7 @@ export const metadata: Metadata = {
|
||||||
"SurfSense",
|
"SurfSense",
|
||||||
],
|
],
|
||||||
openGraph: {
|
openGraph: {
|
||||||
title: "SurfSense - NotebookLM Alternative | Free ChatGPT & Claude AI",
|
title: "SurfSense – Open Source, Privacy-Focused NotebookLM Alternative for Teams",
|
||||||
description:
|
description:
|
||||||
"Open source NotebookLM alternative for teams with no data limits. Use ChatGPT, Claude, and any AI model for free.",
|
"Open source NotebookLM alternative for teams with no data limits. Use ChatGPT, Claude, and any AI model for free.",
|
||||||
url: "https://surfsense.com",
|
url: "https://surfsense.com",
|
||||||
|
|
@ -105,7 +105,7 @@ export const metadata: Metadata = {
|
||||||
},
|
},
|
||||||
twitter: {
|
twitter: {
|
||||||
card: "summary_large_image",
|
card: "summary_large_image",
|
||||||
title: "SurfSense - NotebookLM Alternative | Free ChatGPT & Claude AI",
|
title: "SurfSense – Open Source, Privacy-Focused NotebookLM Alternative for Teams",
|
||||||
description:
|
description:
|
||||||
"Open source NotebookLM alternative for teams with no data limits. Use ChatGPT, Claude AI, and any AI model for free.",
|
"Open source NotebookLM alternative for teams with no data limits. Use ChatGPT, Claude AI, and any AI model for free.",
|
||||||
creator: "@SurfSenseAI",
|
creator: "@SurfSenseAI",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "surfsense_web",
|
"name": "surfsense_web",
|
||||||
"version": "0.0.16",
|
"version": "0.0.19",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "SurfSense Frontend",
|
"description": "SurfSense Frontend",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue