Skip to content

feat: expand item types and improve API response handling #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
May 18, 2025
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
eb738b2
feat: expand item types and improve API response handling
cubxxw May 15, 2025
945e9d3
refactor: clean up exports and improve type definitions in client files
cubxxw May 15, 2025
24ec5dd
refactor: improve API response handling and type assertions in ItemsT…
cubxxw May 15, 2025
8597763
fix: enhance error handling and localization in dashboard and login c…
cubxxw May 15, 2025
db7a5f4
chore: update pnpm version and enhance error handling in components
cubxxw May 15, 2025
5b6d0ae
fix: update item description handling in DashboardPage component
cubxxw May 16, 2025
d1b77aa
chore: update environment configurations and enhance Google OAuth han…
cubxxw May 16, 2025
b19bebc
fix: enhance authentication flow and improve error handling in middle…
cubxxw May 16, 2025
30581cf
chore: add extension testing capabilities and update Makefile
cubxxw May 16, 2025
fcca09d
chore: update client generation workflow and enhance admin dependencies
cubxxw May 16, 2025
8632aa7
chore: remove deprecated files and update project structure
cubxxw May 16, 2025
c235c6d
chore: remove SECURITY.md and update popup components for better acce…
cubxxw May 16, 2025
292e23a
chore: update Jest configuration and enhance testing utilities
cubxxw May 16, 2025
8ed343e
chore: update Makefile and improve code formatting across components
cubxxw May 16, 2025
3e934dd
feat: enhance sidebar functionality and improve API interactions
cubxxw May 17, 2025
0aa50fa
feat: add diagnose functionality and enhance sidebar resources
cubxxw May 17, 2025
37d3779
feat: add resource links for independent developers in README_zh-CN
cubxxw May 17, 2025
e057865
fix: update favicon file types and replace icon assets
cubxxw May 17, 2025
15803ba
chore: update Makefile and enhance extension functionality
cubxxw May 17, 2025
904b9db
refactor: enhance extension build process and update documentation
cubxxw May 18, 2025
8184607
chore: update test setup and enhance database management
cubxxw May 18, 2025
326d986
feat: add social media integration and improve options page layout
cubxxw May 18, 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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Any operations involving issues and pulls on GitHub
globs:
alwaysApply: false
---
**Repository Resolution Logic:**
**Github Repository Resolution Logic:**

- If owner and repo are explicitly provided, use them.
- Otherwise, default to:
Expand All @@ -17,3 +17,7 @@ alwaysApply: false
- The language chosen for raising an issue: Chinese-
- Be careful not to be too AI in your tone.
- Pay attention to choosing the appropriate labels and types. And select the latest Milestone

**Postgres db operation Logic:**

- db name is `app`
4 changes: 1 addition & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ DOMAIN=localhost.nip.io
# Used by the backend to generate links (e.g., in emails).
# Example for local development: http://localhost:8000
# Example for production: https://dashboard.yourdomain.com
FRONTEND_HOST=http://localhost:8000
FRONTEND_HOST=http://localhost:3000

# -- Backend Settings --

Expand Down Expand Up @@ -220,5 +220,3 @@ OR_API_KEY="sk-or-api03-1234567890"
# Google OAuth client ID and secret for authentication
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_OAUTH_REDIRECT_URI=http://localhost:8000/api/v1/login/google/callback
FRONTEND_URL=http://localhost:3000
2 changes: 1 addition & 1 deletion .github/workflows/generate-client.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 8
version: 9.9.0

- name: Setup Node.js
uses: actions/setup-node@v4
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v3
with:
version: 8
version: 9.9.0
- uses: actions/setup-node@v4
with:
node-version: lts/*
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,5 @@ jobs:
run: |
echo "## 🚀 Release Summary" >> $GITHUB_STEP_SUMMARY
echo "Release draft created for version ${{ github.event.inputs.version }}." >> $GITHUB_STEP_SUMMARY
echo "Visit the [Releases section](https://github.com/vintasoftware/nextjs-fastapi-template/releases) to review and publish the release." >> $GITHUB_STEP_SUMMARY
echo "Visit the [Releases section](https://github.com/https://github.com/telepace/releases) to review and publish the release." >> $GITHUB_STEP_SUMMARY
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix the malformed GitHub URL in the release summary.

The GitHub URL contains a duplication of "https://github.com/" which will result in a broken link.

-          echo "Visit the [Releases section](https://github.com/https://github.com/telepace/releases) to review and publish the release." >> $GITHUB_STEP_SUMMARY
+          echo "Visit the [Releases section](https://github.com/telepace/releases) to review and publish the release." >> $GITHUB_STEP_SUMMARY
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
echo "Visit the [Releases section](https://github.com/https://github.com/telepace/releases) to review and publish the release." >> $GITHUB_STEP_SUMMARY
echo "Visit the [Releases section](https://github.com/telepace/releases) to review and publish the release." >> $GITHUB_STEP_SUMMARY
🤖 Prompt for AI Agents
In .github/workflows/release.yml at line 60, the GitHub URL in the release
summary is malformed due to a duplicated "https://github.com/". Remove the extra
"https://github.com/" so the URL correctly points to
"https://github.com/telepace/releases" to fix the broken link.

echo "Once the draft is published, another action will automatically be triggered to publish the packages." >> $GITHUB_STEP_SUMMARY
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ This project provides optimized scripts and Makefile targets for common developm
### Frontend Development

- `make frontend-install` - Install frontend dependencies
- `make frontend-dev` - Run frontend development server
- `make frontend` - Run frontend development server
- `make frontend-build` - Build frontend for production
- `make frontend-test` - Run frontend tests
- `make frontend-lint` - Run linters on frontend code
Expand Down
2 changes: 0 additions & 2 deletions admin/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,3 @@ export { CancelablePromise, CancelError } from "./core/CancelablePromise"
export { OpenAPI, type OpenAPIConfig } from "./core/OpenAPI"
export * from "./sdk.gen"
export * from "./types.gen"
export { isApiResponse, extractApiResponseError } from "./utils"
export { type ItemPublic } from "./types"
34 changes: 30 additions & 4 deletions admin/src/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,33 @@
* 项目的公共接口定义
*/
export interface ItemPublic {
id: string;
title: string;
description?: string | null;
}
id: string
title: string
description?: string | null
}

/**
* 项目集合的公共接口定义
*/
export interface ItemsPublic {
data: Array<ItemPublic>
count: number
}

/**
* 从API响应中提取Item数据的类型
*/
export type ItemResponse = {
data: ItemPublic
meta?: Record<string, unknown> | null
error?: string | null
}

/**
* 从API响应中提取Items集合数据的类型
*/
export type ItemsResponse = {
data: ItemsPublic
meta?: Record<string, unknown> | null
error?: string | null
}
32 changes: 16 additions & 16 deletions admin/src/client/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
* 检查一个值是否为API响应格式
*/
export const isApiResponse = (value: unknown): boolean => {
if (!value || typeof value !== 'object') {
if (!value || typeof value !== "object") {
return false
}

const obj = value as Record<string, unknown>
// 检查是否包含API响应格式的关键字段
return ('data' in obj || 'meta' in obj || 'error' in obj)
return "data" in obj || "meta" in obj || "error" in obj
}

/**
Expand All @@ -22,41 +22,41 @@ export const extractApiResponseError = (response: unknown): string | null => {
if (!isApiResponse(response)) {
return null
}

const apiResponse = response as Record<string, unknown>

// 直接返回error字段
if (typeof apiResponse.error === 'string') {
if (typeof apiResponse.error === "string") {
return apiResponse.error
}

// 尝试从meta中提取详细错误信息
if (apiResponse.meta && typeof apiResponse.meta === 'object') {
if (apiResponse.meta && typeof apiResponse.meta === "object") {
const meta = apiResponse.meta as Record<string, unknown>

// 检查meta中是否有details字段
if (meta.details) {
if (Array.isArray(meta.details) && meta.details.length > 0) {
const firstDetail = meta.details[0]
if (typeof firstDetail === 'object' && firstDetail !== null) {
if (typeof firstDetail === "object" && firstDetail !== null) {
// 尝试获取msg或message字段
const detailObj = firstDetail as Record<string, unknown>
if (typeof detailObj.msg === 'string') {
if (typeof detailObj.msg === "string") {
return detailObj.msg
}
if (typeof detailObj.message === 'string') {
if (typeof detailObj.message === "string") {
return detailObj.message
}
}
// 如果是字符串,直接返回
if (typeof firstDetail === 'string') {
if (typeof firstDetail === "string") {
return firstDetail
}
} else if (typeof meta.details === 'string') {
} else if (typeof meta.details === "string") {
return meta.details
}
}
}

return null
}
}
16 changes: 7 additions & 9 deletions admin/src/routes/_layout/items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,15 @@ function ItemsTable() {
})

// 安全地处理API响应
const apiData = apiResponse?.data || ({} as any)

const responseData = apiResponse?.data || {} as any
// 使用类型断言处理响应数据
const items: ItemPublic[] = Array.isArray(apiData.data)
? apiData.data
: Array.isArray(apiData)
? apiData
: []

const items: ItemPublic[] = Array.isArray(responseData.data)
? responseData.data
: []

// 使用类型断言处理计数
const count = typeof apiData.count === "number" ? apiData.count : items.length
const count = typeof responseData.count === "number" ? responseData.count : items.length

if (isLoading) {
return <PendingItems />
Expand Down
58 changes: 48 additions & 10 deletions backend/app/api/routes/google_oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from datetime import timedelta
from typing import Any
from urllib.parse import urlencode
import logging

import requests
from fastapi import APIRouter, HTTPException, Request
Expand All @@ -14,6 +15,9 @@
from app.core.security import create_access_token
from app.models import User

# u8bbeu7f6eu65e5u5fd7
logger = logging.getLogger("app.google_oauth")

router = APIRouter(tags=["google_oauth"])

# Google OAuth 2.0 endpoints
Expand All @@ -34,6 +38,7 @@ async def google_callback_api(
):
"""
Handle Google OAuth callback from frontend
This is maintained for backward compatibility but not used in the new flow
"""
try:
# Verify the token with Google
Expand Down Expand Up @@ -86,22 +91,38 @@ async def google_callback_api(
async def google_login(request: Request):
"""
Initiate Google OAuth2 authentication flow
This endpoint redirects to Google's login page
"""
# Check if credentials are configured
if not settings.GOOGLE_CLIENT_ID or not settings.GOOGLE_CLIENT_SECRET:
logger.error("Google OAuth credentials not configured properly")
return RedirectResponse(
f"{settings.FRONTEND_HOST}/login?error=oauth_config_error"
)

# u6253u5370u914du7f6eu4fe1u606fu4fbfu4e8eu8c03u8bd5
logger.info(f"Google OAuth using redirect_uri: {settings.google_oauth_redirect_uri}")
logger.info(f"Make sure this matches the Authorized redirect URIs in Google Console")

# Generate a random state value to prevent CSRF attacks
state = secrets.token_urlsafe(16)
request.session["google_oauth_state"] = state

# u6253u5370u5f53u524du7684 session u72b6u6001
logger.info(f"Generated OAuth state: {state}")

# Construct the authorization URL
auth_params = {
"client_id": settings.GOOGLE_CLIENT_ID,
"redirect_uri": settings.GOOGLE_OAUTH_REDIRECT_URI,
"redirect_uri": settings.google_oauth_redirect_uri,
"response_type": "code",
"scope": "openid email profile",
"state": state,
"prompt": "select_account",
}

auth_url = f"{GOOGLE_AUTH_URL}?{urlencode(auth_params)}"
logger.info(f"Redirecting to Google authorization URL: {auth_url}")
return RedirectResponse(auth_url)


Expand All @@ -115,37 +136,49 @@ async def google_callback(
):
"""
Handle the callback from Google OAuth
This endpoint is called by Google after the user has logged in
"""
logger.info(f"Received Google OAuth callback: code={bool(code)}, state={bool(state)}, error={error or 'None'}")

# Check for errors
if error:
logger.error(f"Google OAuth error: {error}")
return RedirectResponse(
f"{settings.FRONTEND_URL}/login?error=oauth_error&message={error}"
f"{settings.FRONTEND_HOST}/login?error=oauth_error&message={error}"
)

# Verify state to prevent CSRF attacks
stored_state = request.session.get("google_oauth_state")
logger.info(f"Validating state: received={state}, stored={stored_state}")

if not stored_state or stored_state != state:
return RedirectResponse(f"{settings.FRONTEND_URL}/login?error=invalid_state")
logger.error(f"State validation failed: stored={stored_state}, received={state}")
return RedirectResponse(f"{settings.FRONTEND_HOST}/login?error=invalid_state")

# Clear the state from session
request.session.pop("google_oauth_state", None)
logger.info("State validated successfully and cleared from session")

if not code:
return RedirectResponse(f"{settings.FRONTEND_URL}/login?error=no_code")
logger.error("No authorization code provided")
return RedirectResponse(f"{settings.FRONTEND_HOST}/login?error=no_code")

try:
# Exchange the authorization code for tokens
token_data = {
"code": code,
"client_id": settings.GOOGLE_CLIENT_ID,
"client_secret": settings.GOOGLE_CLIENT_SECRET,
"redirect_uri": settings.GOOGLE_OAUTH_REDIRECT_URI,
"redirect_uri": settings.google_oauth_redirect_uri,
"grant_type": "authorization_code",
}

logger.info(f"Exchanging code for token with redirect_uri: {settings.google_oauth_redirect_uri}")

token_response = requests.post(GOOGLE_TOKEN_URL, data=token_data)
token_response.raise_for_status()
tokens = token_response.json()
logger.info("Successfully obtained tokens from Google")

# Get user information using the access token
user_info_response = requests.get(
Expand All @@ -154,6 +187,7 @@ async def google_callback(
)
user_info_response.raise_for_status()
user_info = user_info_response.json()
logger.info(f"Retrieved user info from Google: email={user_info.get('email')}")

# Find or create user based on email
user = crud.get_user_by_email(session=session, email=user_info["email"])
Expand All @@ -167,27 +201,31 @@ async def google_callback(
"google_id": user_info["sub"],
}
user = crud.create_user_oauth(session=session, obj_in=User(**user_data))
logger.info(f"Created new user for Google account: {user_info['email']}")
elif not user.google_id:
# Update existing user with Google ID
user.google_id = user_info["sub"]
session.add(user)
session.commit()
session.refresh(user)
logger.info(f"Updated existing user with Google ID: {user_info['email']}")
else:
logger.info(f"User already exists: {user_info['email']}")

# Create access token for the user
access_token = create_access_token(
subject=user.id,
expires_delta=timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES),
)
logger.info(f"Generated access token for user: {user_info['email']}")

# Redirect to frontend with token
redirect_url = (
f"{settings.FRONTEND_URL}/login/google/callback?token={access_token}"
)
redirect_url = f"{settings.FRONTEND_HOST}/login/google/callback?token={access_token}"
logger.info(f"Redirecting to frontend: {redirect_url}")
return RedirectResponse(redirect_url)

except Exception as e:
print(f"Google OAuth error: {str(e)}")
logger.exception(f"Google OAuth callback error: {str(e)}")
return RedirectResponse(
f"{settings.FRONTEND_URL}/login?error=server_error&message={str(e)}"
f"{settings.FRONTEND_HOST}/login?error=server_error&message={str(e)}"
)
Loading
Loading