mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-13 07:02:36 +00: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:
|
||||
contents: write
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
|
@ -58,6 +59,22 @@ jobs:
|
|||
fi
|
||||
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
|
||||
uses: pnpm/action-setup@v5
|
||||
|
||||
|
|
@ -98,7 +115,31 @@ jobs:
|
|||
|
||||
- name: Package & Publish
|
||||
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
|
||||
env:
|
||||
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:
|
||||
"""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)
|
||||
should_sanitize = exc.status_code == 500
|
||||
|
||||
# Structured dict details (e.g. {"code": "CAPTCHA_REQUIRED", "message": "..."})
|
||||
# are preserved so the frontend can parse them.
|
||||
|
|
@ -130,9 +141,9 @@ def _http_exception_handler(request: Request, exc: HTTPException) -> JSONRespons
|
|||
exc.status_code,
|
||||
message,
|
||||
)
|
||||
if exc.status_code == 500:
|
||||
message = GENERIC_5XX_MESSAGE
|
||||
err_code = "INTERNAL_ERROR"
|
||||
if should_sanitize:
|
||||
message = GENERIC_5XX_MESSAGE
|
||||
err_code = "INTERNAL_ERROR"
|
||||
body = {
|
||||
"error": {
|
||||
"code": err_code,
|
||||
|
|
@ -159,8 +170,8 @@ def _http_exception_handler(request: Request, exc: HTTPException) -> JSONRespons
|
|||
exc.status_code,
|
||||
detail,
|
||||
)
|
||||
if exc.status_code == 500:
|
||||
detail = GENERIC_5XX_MESSAGE
|
||||
if should_sanitize:
|
||||
detail = GENERIC_5XX_MESSAGE
|
||||
code = _status_to_code(exc.status_code, detail)
|
||||
return _build_error_response(exc.status_code, detail, code=code, request_id=rid)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[project]
|
||||
name = "surf-new-backend"
|
||||
version = "0.0.16"
|
||||
version = "0.0.19"
|
||||
description = "SurfSense Backend"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
|
|
|
|||
|
|
@ -70,6 +70,20 @@ def _make_test_app():
|
|||
async def raise_http_500():
|
||||
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")
|
||||
async def raise_connector():
|
||||
raise ConnectorError("GitHub API returned 401")
|
||||
|
|
@ -184,6 +198,20 @@ class TestHTTPExceptionHandler:
|
|||
assert body["error"]["message"] == GENERIC_5XX_MESSAGE
|
||||
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
|
||||
|
|
|
|||
2
surfsense_backend/uv.lock
generated
2
surfsense_backend/uv.lock
generated
|
|
@ -7947,7 +7947,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "surf-new-backend"
|
||||
version = "0.0.16"
|
||||
version = "0.0.19"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "alembic" },
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "surfsense_browser_extension",
|
||||
"displayName": "Surfsense Browser Extension",
|
||||
"version": "0.0.16",
|
||||
"version": "0.0.19",
|
||||
"description": "Extension to collect Browsing History for SurfSense.",
|
||||
"author": "https://github.com/MODSetter",
|
||||
"engines": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "surfsense-desktop",
|
||||
"version": "0.0.16",
|
||||
"version": "0.0.19",
|
||||
"description": "SurfSense Desktop App",
|
||||
"main": "dist/main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ async function getAllModels(): Promise<AnonModel[]> {
|
|||
|
||||
function buildSeoTitle(model: AnonModel): string {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import type { AnonModel } from "@/contracts/types/anonymous-chat.types";
|
|||
import { BACKEND_URL } from "@/lib/env-config";
|
||||
|
||||
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:
|
||||
"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: [
|
||||
|
|
@ -67,7 +67,7 @@ export const metadata: Metadata = {
|
|||
canonical: "https://surfsense.com/free",
|
||||
},
|
||||
openGraph: {
|
||||
title: "ChatGPT Free Online Without Login | Claude AI Free No Login | SurfSense",
|
||||
title: "Free AI Chat, No Login Required | SurfSense",
|
||||
description:
|
||||
"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",
|
||||
|
|
@ -84,7 +84,7 @@ export const metadata: Metadata = {
|
|||
},
|
||||
twitter: {
|
||||
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:
|
||||
"Use ChatGPT free online without login. Chat with GPT-4, Claude AI, Gemini and more. No sign-up needed.",
|
||||
images: ["/og-image.png"],
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export const metadata: Metadata = {
|
|||
alternates: {
|
||||
canonical: "https://surfsense.com",
|
||||
},
|
||||
title: "SurfSense - NotebookLM Alternative | Free ChatGPT & Claude AI",
|
||||
title: "SurfSense – Open Source, Privacy-Focused NotebookLM Alternative for Teams",
|
||||
description:
|
||||
"Open source NotebookLM alternative for teams with no data limits. Use ChatGPT, Claude AI, and any AI model for free.",
|
||||
keywords: [
|
||||
|
|
@ -87,7 +87,7 @@ export const metadata: Metadata = {
|
|||
"SurfSense",
|
||||
],
|
||||
openGraph: {
|
||||
title: "SurfSense - NotebookLM Alternative | Free ChatGPT & Claude AI",
|
||||
title: "SurfSense – Open Source, Privacy-Focused NotebookLM Alternative for Teams",
|
||||
description:
|
||||
"Open source NotebookLM alternative for teams with no data limits. Use ChatGPT, Claude, and any AI model for free.",
|
||||
url: "https://surfsense.com",
|
||||
|
|
@ -105,7 +105,7 @@ export const metadata: Metadata = {
|
|||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "SurfSense - NotebookLM Alternative | Free ChatGPT & Claude AI",
|
||||
title: "SurfSense – Open Source, Privacy-Focused NotebookLM Alternative for Teams",
|
||||
description:
|
||||
"Open source NotebookLM alternative for teams with no data limits. Use ChatGPT, Claude AI, and any AI model for free.",
|
||||
creator: "@SurfSenseAI",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "surfsense_web",
|
||||
"version": "0.0.16",
|
||||
"version": "0.0.19",
|
||||
"private": true,
|
||||
"description": "SurfSense Frontend",
|
||||
"scripts": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue