Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit3f613b6

Browse files
authored
feat: add app router and build info endpoint (#78)
* feat: add app router and build info endpoint- Introduced a new router for application-related endpoints, including a `/build-info` endpoint to serve current build information from a generated JSON file.- Implemented a Vite plugin to generate `build-info.json` during the build process, containing a unique build hash and timestamp.- Added a `BuildVersionCheck` component in the frontend to monitor build version changes and refresh the application when a new version is detected.- Updated existing routers to include necessary imports for the new functionality.* refactor: remove runtime config generation and implement API for app configuration- Removed the runtime configuration generation from the startup script, streamlining the application startup process.- Added a new endpoint `/config` in the app router to serve application configuration, fetching values from environment variables.- Updated the frontend to retrieve configuration dynamically from the new API instead of relying on a static runtime config file.- Refactored the PostHog initialization to use the fetched configuration, improving analytics setup.
1 parentb3963a0 commit3f613b6

File tree

14 files changed

+306
-47
lines changed

14 files changed

+306
-47
lines changed

‎scripts/startup.sh‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
11
#!/bin/bash
22
set -e
33

4-
# Create runtime config with environment variables
5-
mkdir -p /app/frontend/dist/assets
6-
cat> /app/frontend/dist/assets/runtime-config.js<<EOL
7-
window.RUNTIME_CONFIG = {
8-
CODER_URL: "${CODER_URL}",
9-
VITE_PUBLIC_POSTHOG_KEY: "${VITE_PUBLIC_POSTHOG_KEY}",
10-
VITE_PUBLIC_POSTHOG_HOST: "${VITE_PUBLIC_POSTHOG_HOST}"
11-
};
12-
EOL
13-
144
# Start the application
155
exec uvicorn main:app --host 0.0.0.0 --port 8000 --workers$API_WORKERS

‎src/backend/main.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
fromrouters.workspace_routerimportworkspace_router
1818
fromrouters.pad_routerimportpad_router
1919
fromrouters.template_pad_routerimporttemplate_pad_router
20+
fromrouters.app_routerimportapp_router
2021
fromdatabase.serviceimportTemplatePadService
2122
fromdatabase.databaseimportasync_session,run_migrations_with_lock
2223

@@ -140,6 +141,7 @@ async def read_root(request: Request, auth: Optional[UserSession] = Depends(opti
140141
app.include_router(workspace_router,prefix="/api/workspace")
141142
app.include_router(pad_router,prefix="/api/pad")
142143
app.include_router(template_pad_router,prefix="/api/templates")
144+
app.include_router(app_router,prefix="/api/app")
143145

144146
if__name__=="__main__":
145147
importuvicorn

‎src/backend/routers/app_router.py‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
importos
2+
importjson
3+
importtime
4+
5+
fromfastapiimportAPIRouter
6+
fromconfigimportSTATIC_DIR
7+
8+
app_router=APIRouter()
9+
10+
@app_router.get("/build-info")
11+
asyncdefget_build_info():
12+
"""
13+
Return the current build information from the static assets
14+
"""
15+
try:
16+
# Read the build-info.json file that will be generated during build
17+
build_info_path=os.path.join(STATIC_DIR,"build-info.json")
18+
withopen(build_info_path,'r')asf:
19+
build_info=json.load(f)
20+
returnbuild_info
21+
exceptExceptionase:
22+
# Return a default response if file doesn't exist
23+
print(f"Error reading build-info.json:{str(e)}")
24+
return {"buildHash":"development","timestamp":int(time.time())}
25+
26+
@app_router.get("/config")
27+
asyncdefget_app_config():
28+
"""
29+
Return runtime configuration for the frontend
30+
"""
31+
return {
32+
"coderUrl":os.getenv("CODER_URL",""),
33+
"posthogKey":os.getenv("VITE_PUBLIC_POSTHOG_KEY",""),
34+
"posthogHost":os.getenv("VITE_PUBLIC_POSTHOG_HOST","")
35+
}

‎src/backend/routers/workspace_router.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
importos
2+
importjson
3+
importtime
24

35
frompydanticimportBaseModel
46
fromfastapiimportAPIRouter,Depends,HTTPException
57
fromfastapi.responsesimportJSONResponse
68

79
fromdependenciesimportUserSession,require_auth,get_coder_api
810
fromcoderimportCoderAPI
11+
fromconfigimportSTATIC_DIR
912

1013
workspace_router=APIRouter()
1114

‎src/frontend/index.html‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@
66
name="viewport"
77
content="width=device-width, initial-scale=1, shrink-to-fit=no"
88
/>
9-
<metaname="theme-color"content="#000000"/>
9+
<metaname="theme-color"content="#untime0"/>
1010

1111
<title>Pad.ws</title>
1212
<linkrel="icon"type="image/x-icon"href="/assets/images/favicon.png"/>
13-
<scriptsrc="/assets/runtime-config.js"></script>
1413
</head>
1514

1615
<body>

‎src/frontend/index.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type * as TExcalidraw from "@atyrode/excalidraw";
1515

1616
importAppfrom"./src/App";
1717
importAuthGatefrom"./src/AuthGate";
18+
import{BuildVersionCheck}from"./src/BuildVersionCheck";
1819

1920

2021
declare global{
@@ -31,6 +32,7 @@ async function initApp() {
3132
<StrictMode>
3233
<PostHogProviderclient={posthog}>
3334
<QueryClientProviderclient={queryClient}>
35+
<BuildVersionCheck/>
3436
<AuthGate>
3537
<App
3638
useCustom={(api:any,args?:any[])=>{}}

‎src/frontend/src/AuthGate.tsx‎

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
importReact,{useEffect,useRef,useState}from"react";
22
import{useAuthCheck}from"./api/hooks";
3+
import{getAppConfig}from"./api/configService";
34

45
/**
56
* If unauthenticated, it shows the AuthModal as an overlay, but still renders the app behind it.
@@ -19,26 +20,42 @@ export default function AuthGate({ children }: { children: React.ReactNode }) {
1920
useEffect(()=>{
2021
// Only run the Coder OIDC priming once per session, after auth is confirmed
2122
if(isAuthenticated===true&&!coderAuthDone){
22-
constiframe=document.createElement("iframe");
23-
iframe.style.display="none";
24-
// Use runtime config if available, fall back to import.meta.env
25-
constcoderUrl=window.RUNTIME_CONFIG?.CODER_URL||import.meta.env.CODER_URL;
26-
iframe.src=`${coderUrl}/api/v2/users/oidc/callback`;
27-
console.debug(`[pad.ws] (Silently) Priming Coder OIDC session for${coderUrl}`);
23+
constsetupIframe=async()=>{
24+
try{
25+
// Get config from API
26+
constconfig=awaitgetAppConfig();
27+
28+
if(!config.coderUrl){
29+
console.warn('[pad.ws] Coder URL not found, skipping OIDC priming');
30+
setCoderAuthDone(true);
31+
return;
32+
}
33+
34+
constiframe=document.createElement("iframe");
35+
iframe.style.display="none";
36+
iframe.src=`${config.coderUrl}/api/v2/users/oidc/callback`;
37+
console.debug(`[pad.ws] (Silently) Priming Coder OIDC session for${config.coderUrl}`);
2838

29-
// Remove iframe as soon as it loads, or after 2s fallback
30-
constcleanup=()=>{
31-
if(iframe.parentNode)iframe.parentNode.removeChild(iframe);
32-
setCoderAuthDone(true);
33-
};
34-
35-
iframe.onload=cleanup;
36-
document.body.appendChild(iframe);
37-
iframeRef.current=iframe;
39+
// Remove iframe as soon as it loads, or after fallback timeout
40+
constcleanup=()=>{
41+
if(iframe.parentNode)iframe.parentNode.removeChild(iframe);
42+
setCoderAuthDone(true);
43+
};
3844

39-
// Fallback: remove iframe after 5s if onload doesn't fire
40-
timeoutRef.current=window.setTimeout(cleanup,5000);
45+
iframe.onload=cleanup;
46+
document.body.appendChild(iframe);
47+
iframeRef.current=iframe;
4148

49+
// Fallback: remove iframe after 5s if onload doesn't fire
50+
timeoutRef.current=window.setTimeout(cleanup,5000);
51+
}catch(error){
52+
console.error('[pad.ws] Error setting up Coder OIDC priming:',error);
53+
setCoderAuthDone(true);
54+
}
55+
};
56+
57+
setupIframe();
58+
4259
// Cleanup on unmount or re-run
4360
return()=>{
4461
if(iframeRef.current&&iframeRef.current.parentNode){
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import{useEffect,useState,useCallback}from'react';
2+
import{useBuildInfo,useSaveCanvas}from'./api/hooks';
3+
import{saveCurrentCanvas}from'./utils/canvasUtils';
4+
5+
/**
6+
* Component that checks for application version changes and refreshes the page when needed.
7+
* This component doesn't render anything visible.
8+
*/
9+
exportfunctionBuildVersionCheck(){
10+
// Store the initial build hash when the component first loads
11+
const[initialBuildHash,setInitialBuildHash]=useState<string|null>(null);
12+
13+
// Query for the current build info from the server
14+
const{data:buildInfo}=useBuildInfo();
15+
16+
// Get the saveCanvas mutation
17+
const{mutate:saveCanvas}=useSaveCanvas({
18+
onSuccess:()=>{
19+
console.debug("[pad.ws] Canvas saved before refresh");
20+
// Refresh the page immediately after saving
21+
window.location.reload();
22+
},
23+
onError:(error)=>{
24+
console.error("[pad.ws] Failed to save canvas before refresh:",error);
25+
// Refresh anyway even if save fails
26+
window.location.reload();
27+
}
28+
});
29+
30+
// Function to handle version update
31+
consthandleVersionUpdate=useCallback(()=>{
32+
// Save the canvas and then refresh
33+
saveCurrentCanvas(
34+
saveCanvas,
35+
undefined,// No success callback needed as it's handled in the useSaveCanvas hook
36+
()=>window.location.reload()// On error, just refresh
37+
);
38+
},[saveCanvas]);
39+
40+
useEffect(()=>{
41+
// On first load, store the initial build hash
42+
if(buildInfo?.buildHash&&initialBuildHash===null){
43+
console.log('Initial build hash:',buildInfo.buildHash);
44+
setInitialBuildHash(buildInfo.buildHash);
45+
}
46+
47+
// If we have both values and they don't match, a new version is available
48+
if(initialBuildHash!==null&&
49+
buildInfo?.buildHash&&
50+
initialBuildHash!==buildInfo.buildHash){
51+
52+
console.log('New version detected. Current:',initialBuildHash,'New:',buildInfo.buildHash);
53+
54+
// Save the canvas and then refresh
55+
handleVersionUpdate();
56+
}
57+
},[buildInfo,initialBuildHash,handleVersionUpdate]);
58+
59+
// This component doesn't render anything
60+
returnnull;
61+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import{fetchApi}from'./apiUtils';
2+
3+
/**
4+
* Application configuration interface
5+
*/
6+
exportinterfaceAppConfig{
7+
coderUrl:string;
8+
posthogKey:string;
9+
posthogHost:string;
10+
}
11+
12+
// Cache the config to avoid unnecessary API calls
13+
letcachedConfig:AppConfig|null=null;
14+
15+
/**
16+
* Get the application configuration from the API
17+
*@returns The application configuration
18+
*/
19+
exportasyncfunctiongetAppConfig():Promise<AppConfig>{
20+
// Return cached config if available
21+
if(cachedConfig){
22+
returncachedConfig;
23+
}
24+
25+
try{
26+
// Fetch config from API
27+
constconfig=awaitfetchApi('/api/app/config');
28+
cachedConfig=config;
29+
returnconfig;
30+
}catch(error){
31+
console.error('[pad.ws] Failed to load application configuration:',error);
32+
// Return default values as fallback
33+
return{
34+
coderUrl:'',
35+
posthogKey:'',
36+
posthogHost:''
37+
};
38+
}
39+
}

‎src/frontend/src/api/hooks.ts‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ export interface CanvasBackupsResponse {
3939
backups:CanvasBackup[];
4040
}
4141

42+
exportinterfaceBuildInfo{
43+
buildHash:string;
44+
timestamp:number;
45+
}
46+
4247
// API functions
4348
exportconstapi={
4449
// Authentication
@@ -134,6 +139,16 @@ export const api = {
134139
throwerror;
135140
}
136141
},
142+
143+
// Build Info
144+
getBuildInfo:async():Promise<BuildInfo>=>{
145+
try{
146+
constresult=awaitfetchApi('/api/app/build-info');
147+
returnresult;
148+
}catch(error){
149+
throwerror;
150+
}
151+
},
137152
};
138153

139154
// Query hooks
@@ -192,6 +207,15 @@ export function useCanvasBackups(limit: number = 10, options?: UseQueryOptions<C
192207
});
193208
}
194209

210+
exportfunctionuseBuildInfo(options?:UseQueryOptions<BuildInfo>){
211+
returnuseQuery({
212+
queryKey:['buildInfo'],
213+
queryFn:api.getBuildInfo,
214+
refetchInterval:60000,// Check every minute
215+
...options,
216+
});
217+
}
218+
195219
// Mutation hooks
196220
exportfunctionuseStartWorkspace(options?:UseMutationOptions){
197221
returnuseMutation({

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp