integrate backend Google OAuth and set up avatar media proxy

- Implement the full login and onboarding workflow to handle name, handle, and avatar.

- Rewrite avatar asset URLs to route through the self-hosted media service.

- Add Next.js rewrites configuration to proxy media requests directly to the FastAPI backend.
This commit is contained in:
2026-05-20 17:27:16 +08:00
parent 6fe1504e7e
commit c187cd22ed
13 changed files with 673 additions and 271 deletions
+179
View File
@@ -8,9 +8,18 @@ importers:
.:
dependencies:
'@apollo/client':
specifier: ^4.1.9
version: 4.1.9(graphql@16.14.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)
'@apollo/experimental-nextjs-app-support':
specifier: ^0.14.5
version: 0.14.5(@apollo/client@4.1.9(graphql@16.14.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2))(@types/react@19.2.14)(graphql@16.14.0)(next@16.2.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)
'@tailwindcss/cli':
specifier: ^4.3.0
version: 4.3.0
graphql:
specifier: ^16.14.0
version: 16.14.0
lucide-react:
specifier: ^1.16.0
version: 1.16.0(react@19.2.4)
@@ -61,6 +70,50 @@ packages:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
'@apollo/client-integration-nextjs@0.14.5':
resolution: {integrity: sha512-aNh16zlx5rrGtP946jW+O/J7OUyoSC0dxjicFIztiw/S5tQjNqZgqmlgtOXMn7zAR8qV9VOAiKjK5UROaN8iJg==}
peerDependencies:
'@apollo/client': ^4.0.0
next: ^15.2.3 || ^16.0.0
react: ^19
rxjs: ^7.3.0
'@apollo/client-react-streaming@0.14.5':
resolution: {integrity: sha512-ru9FP4g5tULITsWBO6oDSHOflnZaD6lm0AtjT9mkZbbrUkWwoYcSrTKdjNrjfrnppqXS1igv+DpcO+SlXdUGXg==}
peerDependencies:
'@apollo/client': ^4.0.0
graphql: ^16 || >=17.0.0-alpha.2
react: ^19
react-dom: ^19
rxjs: ^7.3.0
'@apollo/client@4.1.9':
resolution: {integrity: sha512-qfpkQD51tdU/7iAR6aLb4w9o/L7I475DluWHRb61U/3Q0AH29nNOxOBHjBbWDdf16ncPOoQuxne1sEs2NjqBFw==}
peerDependencies:
graphql: ^16.0.0
graphql-ws: ^5.5.5 || ^6.0.3
react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc
react-dom: ^17.0.0 || ^18.0.0 || >=19.0.0-rc
rxjs: ^7.3.0
subscriptions-transport-ws: ^0.9.0 || ^0.11.0
peerDependenciesMeta:
graphql-ws:
optional: true
react:
optional: true
react-dom:
optional: true
subscriptions-transport-ws:
optional: true
'@apollo/experimental-nextjs-app-support@0.14.5':
resolution: {integrity: sha512-66E646B/CXMMzFpT3abHLCx+wVg5y8Uu2mUr0YbeXUKDMQmQLxEl4W27YQ3ukkYunMg5CSAq99MP1Tje1KSthA==}
peerDependencies:
'@apollo/client': ^4.0.0
next: ^15.2.3
react: ^19
rxjs: ^7.3.0
'@babel/code-frame@7.29.0':
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
engines: {node: '>=6.9.0'}
@@ -175,6 +228,11 @@ packages:
resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@graphql-typed-document-node/core@3.2.0':
resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==}
peerDependencies:
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
'@humanfs/core@0.19.2':
resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==}
engines: {node: '>=18.18.0'}
@@ -796,6 +854,22 @@ packages:
cpu: [x64]
os: [win32]
'@wry/caches@1.0.1':
resolution: {integrity: sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA==}
engines: {node: '>=8'}
'@wry/context@0.7.4':
resolution: {integrity: sha512-jmT7Sb4ZQWI5iyu3lobQxICu2nC/vbUhP0vIdd6tHC9PTfenmRmuIFqktc6GH9cgi+ZHnsLWPvfSvc4DrYmKiQ==}
engines: {node: '>=8'}
'@wry/equality@0.5.7':
resolution: {integrity: sha512-BRFORjsTuQv5gxcXsuDXx6oGRhuVsEGwZy6LOzRRfgu+eSfxbhUQ9L9YtSEIuIjY/o7g3iWFjrc5eSY1GXP2Dw==}
engines: {node: '>=8'}
'@wry/trie@0.5.0':
resolution: {integrity: sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA==}
engines: {node: '>=8'}
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -1278,6 +1352,16 @@ packages:
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
graphql-tag@2.12.6:
resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==}
engines: {node: '>=10'}
peerDependencies:
graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
graphql@16.14.0:
resolution: {integrity: sha512-BBvQ/406p+4CZbTpCbVPSxfzrZrbnuWSP1ELYgyS6B+hNeKzgrdB4JczCa5VZUBQrDa9hUngm0KnexY6pJRN5Q==}
engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
has-bigints@1.1.0:
resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
engines: {node: '>= 0.4'}
@@ -1693,6 +1777,9 @@ packages:
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
engines: {node: '>= 0.4'}
optimism@0.18.1:
resolution: {integrity: sha512-mLXNwWPa9dgFyDqkNi54sjDyNJ9/fTI6WGBLgnXku1vdKY/jovHfZT5r+aiVeFFLOz+foPNOm5YJ4mqgld2GBQ==}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -1800,6 +1887,9 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
rxjs@7.8.2:
resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
safe-array-concat@1.1.4:
resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==}
engines: {node: '>=0.4'}
@@ -2058,6 +2148,57 @@ snapshots:
'@alloc/quick-lru@5.2.0': {}
'@apollo/client-integration-nextjs@0.14.5(@apollo/client@4.1.9(graphql@16.14.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2))(@types/react@19.2.14)(graphql@16.14.0)(next@16.2.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)':
dependencies:
'@apollo/client': 4.1.9(graphql@16.14.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)
'@apollo/client-react-streaming': 0.14.5(@apollo/client@4.1.9(graphql@16.14.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2))(@types/react@19.2.14)(graphql@16.14.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)
next: 16.2.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
react: 19.2.4
rxjs: 7.8.2
transitivePeerDependencies:
- '@types/react'
- graphql
- react-dom
'@apollo/client-react-streaming@0.14.5(@apollo/client@4.1.9(graphql@16.14.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2))(@types/react@19.2.14)(graphql@16.14.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)':
dependencies:
'@apollo/client': 4.1.9(graphql@16.14.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)
'@types/react-dom': 19.2.3(@types/react@19.2.14)
'@wry/equality': 0.5.7
graphql: 16.14.0
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
rxjs: 7.8.2
transitivePeerDependencies:
- '@types/react'
'@apollo/client@4.1.9(graphql@16.14.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)':
dependencies:
'@graphql-typed-document-node/core': 3.2.0(graphql@16.14.0)
'@wry/caches': 1.0.1
'@wry/equality': 0.5.7
'@wry/trie': 0.5.0
graphql: 16.14.0
graphql-tag: 2.12.6(graphql@16.14.0)
optimism: 0.18.1
rxjs: 7.8.2
tslib: 2.8.1
optionalDependencies:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
'@apollo/experimental-nextjs-app-support@0.14.5(@apollo/client@4.1.9(graphql@16.14.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2))(@types/react@19.2.14)(graphql@16.14.0)(next@16.2.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)':
dependencies:
'@apollo/client': 4.1.9(graphql@16.14.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)
'@apollo/client-integration-nextjs': 0.14.5(@apollo/client@4.1.9(graphql@16.14.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2))(@types/react@19.2.14)(graphql@16.14.0)(next@16.2.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rxjs@7.8.2)
next: 16.2.6(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
react: 19.2.4
rxjs: 7.8.2
transitivePeerDependencies:
- '@types/react'
- graphql
- react-dom
'@babel/code-frame@7.29.0':
dependencies:
'@babel/helper-validator-identifier': 7.28.5
@@ -2220,6 +2361,10 @@ snapshots:
'@eslint/core': 0.17.0
levn: 0.4.1
'@graphql-typed-document-node/core@3.2.0(graphql@16.14.0)':
dependencies:
graphql: 16.14.0
'@humanfs/core@0.19.2':
dependencies:
'@humanfs/types': 0.15.0
@@ -2732,6 +2877,22 @@ snapshots:
'@unrs/resolver-binding-win32-x64-msvc@1.12.2':
optional: true
'@wry/caches@1.0.1':
dependencies:
tslib: 2.8.1
'@wry/context@0.7.4':
dependencies:
tslib: 2.8.1
'@wry/equality@0.5.7':
dependencies:
tslib: 2.8.1
'@wry/trie@0.5.0':
dependencies:
tslib: 2.8.1
acorn-jsx@5.3.2(acorn@8.16.0):
dependencies:
acorn: 8.16.0
@@ -3390,6 +3551,13 @@ snapshots:
graceful-fs@4.2.11: {}
graphql-tag@2.12.6(graphql@16.14.0):
dependencies:
graphql: 16.14.0
tslib: 2.8.1
graphql@16.14.0: {}
has-bigints@1.1.0: {}
has-flag@4.0.0: {}
@@ -3781,6 +3949,13 @@ snapshots:
define-properties: 1.2.1
es-object-atoms: 1.1.1
optimism@0.18.1:
dependencies:
'@wry/caches': 1.0.1
'@wry/context': 0.7.4
'@wry/trie': 0.5.0
tslib: 2.8.1
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@@ -3894,6 +4069,10 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
rxjs@7.8.2:
dependencies:
tslib: 2.8.1
safe-array-concat@1.1.4:
dependencies:
call-bind: 1.0.9