···11+# Exclude everything by default
22+*
33+44+# Then explicitly include only what's needed
55+66+# Include Go files and modules
77+!go.mod
88+!go.sum
99+!server/
1010+!db/
1111+1212+# For the frontend
1313+!mast-react-vite/src/
1414+!mast-react-vite/public/
1515+!mast-react-vite/index.html
1616+!mast-react-vite/package.json
1717+!mast-react-vite/package-lock.json
1818+!mast-react-vite/*.config.js
1919+!mast-react-vite/*.config.ts
2020+2121+# Keep excluding node_modules even with the include patterns above
2222+**/node_modules
2323+2424+# Exclude common large/unnecessary files
2525+**/.git
2626+**/.gitignore
2727+**/.DS_Store
2828+**/dist
2929+**/build
3030+**/*.log
3131+**/*.test.*
3232+**/*.spec.*
3333+**/*.tmp
3434+**/.env*
3535+**/.vscode
3636+**/.idea
+34-24
Dockerfile
···11-# Dockerfile
11+# Base builder
22+FROM golang:1.21-alpine AS base
33+WORKDIR /app
44+RUN apk add --no-cache gcc musl-dev
55+COPY go.mod go.sum ./
66+RUN go mod download
77+COPY db/crsqlite.so ./db/
2833-# Step 1: Use a Node.js image to build the app
44-FROM node:18 as builder
55-66-# Set working directory inside the container
99+# Frontend builder
1010+FROM node:18-alpine AS frontend-builder
711WORKDIR /app
88-99-# Copy package.json and package-lock.json
1010-COPY ./mast-react-vite/package*.json ./
1111-1212-# Install dependencies
1212+COPY ./mast-react-vite/package.json ./mast-react-vite/package-lock.json ./
1313RUN npm install
1414-1515-# Copy the rest of the app’s source code
1616-COPY ./mast-react-vite/ .
1717-1818-# Build the app
1414+COPY ./mast-react-vite/src ./src
1515+COPY ./mast-react-vite/public ./public
1616+COPY ./mast-react-vite/index.html ./
1717+COPY ./mast-react-vite/*.config.js ./mast-react-vite/*.config.ts ./
1918RUN npm run build
20192020+# Server builder
2121+FROM base AS server-builder
2222+WORKDIR /app
2323+COPY server/main.go server/auth.go server/auth.db server/test.go ./server/
2424+RUN CGO_ENABLED=1 go build -o /app/bin/server ./server/main.go ./server/auth.go
21252222-# Step 2: Use an Nginx image to serve the static files
2323-FROM nginx:alpine
2424-2525-# Copy the build files from the builder stage to the Nginx web directory
2626-COPY --from=builder /app/dist /usr/share/nginx/html
2727-2828-# Expose port 80
2626+# Frontend stage
2727+FROM nginx:alpine AS frontend
2828+COPY --from=frontend-builder /app/dist /usr/share/nginx/html
2929+RUN mkdir -p /usr/share/nginx/html/db
3030+COPY --from=base /app/db/crsqlite.so /usr/share/nginx/html/db/crsqlite.so
2931EXPOSE 80
3232+CMD ["nginx", "-g", "daemon off;"]
30333131-# Start Nginx server
3232-CMD ["nginx", "-g", "daemon off;"]
3434+# Server stage
3535+FROM alpine:latest AS server
3636+WORKDIR /app
3737+RUN apk add --no-cache ca-certificates
3838+COPY --from=server-builder /app/bin/server /app/
3939+COPY --from=base /app/db/ /app/db/
4040+RUN mkdir -p /app/rooms
4141+EXPOSE 8080
4242+CMD ["/app/server"]
···2222 room: string;
2323 url: string;
2424 isConnecting: boolean;
2525+ requestUnsyncedChanges?: boolean;
2526}> = {};
26272728// Handle messages from the main thread
···160161 const separator = wsUrl.includes('?') ? '&' : '?';
161162 wsUrl = `${wsUrl}${separator}room=${config.room}`;
162163 }
164164+165165+ // Store the requestUnsyncedChanges flag in the connection
166166+ connections[dbname].requestUnsyncedChanges = config.requestUnsyncedChanges;
163167 logDebug(`Connecting to WebSocket at ${wsUrl}`);
164168165169 const ws = new WebSocket(wsUrl);
···181185 });
182186183187 // Initial sync - request changes from server
184184- logDebug(`Sending initial pull request`);
188188+ logDebug(`Sending initial pull request with requestUnsyncedChanges=${connections[dbname].requestUnsyncedChanges}`);
185189 ws.send(JSON.stringify({
186190 type: "pull",
187187- room: config.room
191191+ room: config.room,
192192+ requestUnsyncedChanges: connections[dbname].requestUnsyncedChanges // Request any unsynced changes from server if specified
188193 }));
189194190195 // Notify main thread of connection
+48
server/Dockerfile
···11+# Server Dockerfile for Go application
22+33+# Build stage
44+FROM golang:1.21-alpine AS builder
55+66+WORKDIR /app
77+88+# Install build dependencies
99+RUN apk add --no-cache gcc musl-dev
1010+1111+# Copy go mod files
1212+COPY go.mod go.sum ./
1313+1414+# Download dependencies
1515+RUN go mod download
1616+1717+# Copy source code
1818+COPY server/ ./server/
1919+COPY db/ ./db/
2020+2121+# Create output directory
2222+RUN mkdir -p /app/bin
2323+2424+# Build the application
2525+RUN CGO_ENABLED=1 go build -o /app/bin/server-app ./server/main.go
2626+2727+# Final stage
2828+FROM alpine:latest
2929+3030+WORKDIR /app
3131+3232+# Install runtime dependencies
3333+RUN apk add --no-cache ca-certificates
3434+3535+# Copy binary from builder stage
3636+COPY --from=builder /app/bin/server-app /app/
3737+3838+# Copy SQLite extension
3939+COPY --from=builder /app/db/crsqlite.so /app/db/crsqlite.so
4040+4141+# Create directory for room databases
4242+RUN mkdir -p /app/rooms
4343+4444+# Expose port
4545+EXPOSE 8080
4646+4747+# Run the application
4848+CMD ["/app/server-app"]
+15-5
server/main.go
···163163 removeClientFromRoom(roomID, conn)
164164 }()
165165166166- // Send initial changes to client
167167- sendInitialChanges(conn, db)
166166+ // We don't automatically send initial changes anymore
167167+ // The client will request them with a pull message that includes requestUnsyncedChanges
168168169169 // Handle incoming messages
170170 for {
···189189 switch msgType {
190190 case "pull":
191191 // Client is requesting changes
192192+ log.Printf("Client in room %s requested pull", roomID)
192193 changes := getChangesFromDB(db, msg)
193194 response := map[string]interface{}{
194195 "type": "changes",
···288289}
289290290291func getChangesFromDB(db *sql.DB, msg map[string]interface{}) []map[string]interface{} {
291291- // Implementation to get changes based on client's request
292292- // This would typically filter based on site_id and version
292292+ // Check if client is requesting unsynced changes
293293+ requestUnsyncedChanges, _ := msg["requestUnsyncedChanges"].(bool)
294294+ log.Printf("Client requesting changes. Unsynced changes requested: %v", requestUnsyncedChanges)
293295294294- rows, err := db.Query("SELECT * FROM crsql_changes")
296296+ // Default query gets all changes
297297+ query := "SELECT * FROM crsql_changes"
298298+299299+ // If we need to send all unsynced changes, no need to filter
300300+ // But in a more sophisticated implementation, we could filter based on timestamps
301301+ // or some other mechanism to determine what's "unsynced"
302302+303303+ rows, err := db.Query(query)
295304 if err != nil {
296305 log.Println("Error querying changes:", err)
297306 return nil
···329338 changes = append(changes, change)
330339 }
331340341341+ log.Printf("Returning %d changes to client for sync", len(changes))
332342 return changes
333343}
334344