this repo has no description

WIP: deployed syncing

Changed files
+178 -35
mast-react-vite
server
+36
.dockerignore
··· 1 + # Exclude everything by default 2 + * 3 + 4 + # Then explicitly include only what's needed 5 + 6 + # Include Go files and modules 7 + !go.mod 8 + !go.sum 9 + !server/ 10 + !db/ 11 + 12 + # For the frontend 13 + !mast-react-vite/src/ 14 + !mast-react-vite/public/ 15 + !mast-react-vite/index.html 16 + !mast-react-vite/package.json 17 + !mast-react-vite/package-lock.json 18 + !mast-react-vite/*.config.js 19 + !mast-react-vite/*.config.ts 20 + 21 + # Keep excluding node_modules even with the include patterns above 22 + **/node_modules 23 + 24 + # Exclude common large/unnecessary files 25 + **/.git 26 + **/.gitignore 27 + **/.DS_Store 28 + **/dist 29 + **/build 30 + **/*.log 31 + **/*.test.* 32 + **/*.spec.* 33 + **/*.tmp 34 + **/.env* 35 + **/.vscode 36 + **/.idea
+34 -24
Dockerfile
··· 1 - # Dockerfile 1 + # Base builder 2 + FROM golang:1.21-alpine AS base 3 + WORKDIR /app 4 + RUN apk add --no-cache gcc musl-dev 5 + COPY go.mod go.sum ./ 6 + RUN go mod download 7 + COPY db/crsqlite.so ./db/ 2 8 3 - # Step 1: Use a Node.js image to build the app 4 - FROM node:18 as builder 5 - 6 - # Set working directory inside the container 9 + # Frontend builder 10 + FROM node:18-alpine AS frontend-builder 7 11 WORKDIR /app 8 - 9 - # Copy package.json and package-lock.json 10 - COPY ./mast-react-vite/package*.json ./ 11 - 12 - # Install dependencies 12 + COPY ./mast-react-vite/package.json ./mast-react-vite/package-lock.json ./ 13 13 RUN npm install 14 - 15 - # Copy the rest of the app’s source code 16 - COPY ./mast-react-vite/ . 17 - 18 - # Build the app 14 + COPY ./mast-react-vite/src ./src 15 + COPY ./mast-react-vite/public ./public 16 + COPY ./mast-react-vite/index.html ./ 17 + COPY ./mast-react-vite/*.config.js ./mast-react-vite/*.config.ts ./ 19 18 RUN npm run build 20 19 20 + # Server builder 21 + FROM base AS server-builder 22 + WORKDIR /app 23 + COPY server/main.go server/auth.go server/auth.db server/test.go ./server/ 24 + RUN CGO_ENABLED=1 go build -o /app/bin/server ./server/main.go ./server/auth.go 21 25 22 - # Step 2: Use an Nginx image to serve the static files 23 - FROM nginx:alpine 24 - 25 - # Copy the build files from the builder stage to the Nginx web directory 26 - COPY --from=builder /app/dist /usr/share/nginx/html 27 - 28 - # Expose port 80 26 + # Frontend stage 27 + FROM nginx:alpine AS frontend 28 + COPY --from=frontend-builder /app/dist /usr/share/nginx/html 29 + RUN mkdir -p /usr/share/nginx/html/db 30 + COPY --from=base /app/db/crsqlite.so /usr/share/nginx/html/db/crsqlite.so 29 31 EXPOSE 80 32 + CMD ["nginx", "-g", "daemon off;"] 30 33 31 - # Start Nginx server 32 - CMD ["nginx", "-g", "daemon off;"] 34 + # Server stage 35 + FROM alpine:latest AS server 36 + WORKDIR /app 37 + RUN apk add --no-cache ca-certificates 38 + COPY --from=server-builder /app/bin/server /app/ 39 + COPY --from=base /app/db/ /app/db/ 40 + RUN mkdir -p /app/rooms 41 + EXPOSE 8080 42 + CMD ["/app/server"]
+23
fly.server.toml
··· 1 + # fly.toml file for the Go server 2 + app = "mast-server" 3 + kill_signal = "SIGINT" 4 + kill_timeout = 5 5 + primary_region = "sea" 6 + 7 + [build] 8 + dockerfile = "Dockerfile" 9 + build-target = "server" 10 + 11 + [env] 12 + 13 + [http_service] 14 + internal_port = 8080 15 + force_https = true 16 + auto_stop_machines = true 17 + auto_start_machines = true 18 + min_machines_running = 0 19 + processes = ["app"] 20 + 21 + [mounts] 22 + source = "server_data" 23 + destination = "/app/rooms"
+4
fly.toml
··· 7 7 primary_region = 'sea' 8 8 9 9 [build] 10 + dockerfile = "Dockerfile" 11 + build-target = "frontend" 12 + 13 + [env] 10 14 11 15 [http_service] 12 16 internal_port = 80
+4 -2
mast-react-vite/src/hooks/use-sync.ts
··· 23 23 useEffect(() => { 24 24 if (!worker) return; 25 25 26 - // Start sync 26 + // Start sync with request for unsynced changes 27 + console.log(`Starting sync for ${dbname} with room ${room} and requesting unsynced changes`); 27 28 worker.postMessage({ 28 29 type: 'START_SYNC', 29 30 dbname, 30 31 config: { 31 32 room: room, 32 - url: endpoint 33 + url: endpoint, 34 + requestUnsyncedChanges: true // Request server to send unsynced changes immediately 33 35 } 34 36 }); 35 37
+7 -2
mast-react-vite/src/main.tsx
··· 222 222 223 223 // Custom sync component using your own implementation 224 224 function CustomSyncComponent({ dbname, roomId }: { dbname: string, roomId: string }) { 225 - const endpoint = `ws://localhost:8080/sync`; 225 + const endpoint = process.env.NODE_ENV === 'production' 226 + ? `wss://mast-server.fly.dev/sync` 227 + : `ws://localhost:8080/sync`; 226 228 227 229 const syncStats = useCustomSync({ 228 230 dbname, ··· 342 344 343 345 console.log("Request body:", JSON.stringify(requestBody)); 344 346 345 - const response = await fetch('http://localhost:8080/auth/register-key', { 347 + const authEndpoint = process.env.NODE_ENV === 'production' 348 + ? 'https://mast-server.fly.dev/auth/register-key' 349 + : 'http://localhost:8080/auth/register-key'; 350 + const response = await fetch(authEndpoint, { 346 351 method: 'POST', 347 352 headers: { 348 353 'Content-Type': 'application/json',
+7 -2
mast-react-vite/src/worker/sync-worker.ts
··· 22 22 room: string; 23 23 url: string; 24 24 isConnecting: boolean; 25 + requestUnsyncedChanges?: boolean; 25 26 }> = {}; 26 27 27 28 // Handle messages from the main thread ··· 160 161 const separator = wsUrl.includes('?') ? '&' : '?'; 161 162 wsUrl = `${wsUrl}${separator}room=${config.room}`; 162 163 } 164 + 165 + // Store the requestUnsyncedChanges flag in the connection 166 + connections[dbname].requestUnsyncedChanges = config.requestUnsyncedChanges; 163 167 logDebug(`Connecting to WebSocket at ${wsUrl}`); 164 168 165 169 const ws = new WebSocket(wsUrl); ··· 181 185 }); 182 186 183 187 // Initial sync - request changes from server 184 - logDebug(`Sending initial pull request`); 188 + logDebug(`Sending initial pull request with requestUnsyncedChanges=${connections[dbname].requestUnsyncedChanges}`); 185 189 ws.send(JSON.stringify({ 186 190 type: "pull", 187 - room: config.room 191 + room: config.room, 192 + requestUnsyncedChanges: connections[dbname].requestUnsyncedChanges // Request any unsynced changes from server if specified 188 193 })); 189 194 190 195 // Notify main thread of connection
+48
server/Dockerfile
··· 1 + # Server Dockerfile for Go application 2 + 3 + # Build stage 4 + FROM golang:1.21-alpine AS builder 5 + 6 + WORKDIR /app 7 + 8 + # Install build dependencies 9 + RUN apk add --no-cache gcc musl-dev 10 + 11 + # Copy go mod files 12 + COPY go.mod go.sum ./ 13 + 14 + # Download dependencies 15 + RUN go mod download 16 + 17 + # Copy source code 18 + COPY server/ ./server/ 19 + COPY db/ ./db/ 20 + 21 + # Create output directory 22 + RUN mkdir -p /app/bin 23 + 24 + # Build the application 25 + RUN CGO_ENABLED=1 go build -o /app/bin/server-app ./server/main.go 26 + 27 + # Final stage 28 + FROM alpine:latest 29 + 30 + WORKDIR /app 31 + 32 + # Install runtime dependencies 33 + RUN apk add --no-cache ca-certificates 34 + 35 + # Copy binary from builder stage 36 + COPY --from=builder /app/bin/server-app /app/ 37 + 38 + # Copy SQLite extension 39 + COPY --from=builder /app/db/crsqlite.so /app/db/crsqlite.so 40 + 41 + # Create directory for room databases 42 + RUN mkdir -p /app/rooms 43 + 44 + # Expose port 45 + EXPOSE 8080 46 + 47 + # Run the application 48 + CMD ["/app/server-app"]
+15 -5
server/main.go
··· 163 163 removeClientFromRoom(roomID, conn) 164 164 }() 165 165 166 - // Send initial changes to client 167 - sendInitialChanges(conn, db) 166 + // We don't automatically send initial changes anymore 167 + // The client will request them with a pull message that includes requestUnsyncedChanges 168 168 169 169 // Handle incoming messages 170 170 for { ··· 189 189 switch msgType { 190 190 case "pull": 191 191 // Client is requesting changes 192 + log.Printf("Client in room %s requested pull", roomID) 192 193 changes := getChangesFromDB(db, msg) 193 194 response := map[string]interface{}{ 194 195 "type": "changes", ··· 288 289 } 289 290 290 291 func getChangesFromDB(db *sql.DB, msg map[string]interface{}) []map[string]interface{} { 291 - // Implementation to get changes based on client's request 292 - // This would typically filter based on site_id and version 292 + // Check if client is requesting unsynced changes 293 + requestUnsyncedChanges, _ := msg["requestUnsyncedChanges"].(bool) 294 + log.Printf("Client requesting changes. Unsynced changes requested: %v", requestUnsyncedChanges) 293 295 294 - rows, err := db.Query("SELECT * FROM crsql_changes") 296 + // Default query gets all changes 297 + query := "SELECT * FROM crsql_changes" 298 + 299 + // If we need to send all unsynced changes, no need to filter 300 + // But in a more sophisticated implementation, we could filter based on timestamps 301 + // or some other mechanism to determine what's "unsynced" 302 + 303 + rows, err := db.Query(query) 295 304 if err != nil { 296 305 log.Println("Error querying changes:", err) 297 306 return nil ··· 329 338 changes = append(changes, change) 330 339 } 331 340 341 + log.Printf("Returning %d changes to client for sync", len(changes)) 332 342 return changes 333 343 } 334 344