patwie 2 anni fa
commit
36e3ecb5c7
9 ha cambiato i file con 475 aggiunte e 0 eliminazioni
  1. 2 0
      .gitignore
  2. 51 0
      .goreleaser.yaml
  3. 12 0
      README.md
  4. 17 0
      core/entity.go
  5. 31 0
      documents/memory/documents.go
  6. 40 0
      frontend.patch
  7. 42 0
      go.mod
  8. 88 0
      go.sum
  9. 192 0
      main.go

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+dist/
+excalidraw-backend

+ 51 - 0
.goreleaser.yaml

@@ -0,0 +1,51 @@
+# This is an example .goreleaser.yml file with some sensible defaults.
+# Make sure to check the documentation at https://goreleaser.com
+
+# The lines below are called `modelines`. See `:help modeline`
+# Feel free to remove those if you don't want/need to use them.
+# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
+# vim: set ts=2 sw=2 tw=0 fo=cnqoj
+
+version: 1
+
+# before:
+#   hooks:
+#     # You may remove this if you don't use go modules.
+#     - go mod tidy
+#     # you may remove this if you don't need go generate
+#     - go generate ./...
+
+builds:
+  - env:
+      - CGO_ENABLED=0
+    goos:
+      - linux
+      - windows
+      - darwin
+
+archives:
+  - format: tar.gz
+    files: []
+    # this name template makes the OS and Arch compatible with the results of `uname`.
+    name_template: >-
+      {{ .ProjectName }}_
+      {{- title .Os }}_
+      {{- if eq .Arch "amd64" }}x86_64
+      {{- else if eq .Arch "386" }}i386
+      {{- else }}{{ .Arch }}{{ end }}
+      {{- if .Arm }}v{{ .Arm }}{{ end }}
+    # use zip for windows archives
+    format_overrides:
+      - goos: windows
+        format: zip
+
+changelog:
+  sort: asc
+  filters:
+    exclude:
+      - "^docs:"
+      - "^test:"
+
+release:
+  disable: true
+  skip_upload: true

+ 12 - 0
README.md

@@ -0,0 +1,12 @@
+# Exalidraw Backend
+
+Frustrated on how difficult it is to setup excalidraw self-hosted but with data
+storage and collaboration function this represents and attempt to run the
+necessary function with a single binary implemented in go.
+
+Apply the patch to the frontend and build excalidraw. Run Excalidraw frontend and
+on the same host run
+```bash
+go run main.go
+```
+

+ 17 - 0
core/entity.go

@@ -0,0 +1,17 @@
+package core
+
+import (
+	"bytes"
+	"context"
+)
+
+type (
+	Document struct {
+		Data bytes.Buffer
+	}
+
+	DocumentStore interface {
+		FindID(ctx context.Context, id string) (*Document, error)
+		Create(ctx context.Context, document *Document) (string, error)
+	}
+)

+ 31 - 0
documents/memory/documents.go

@@ -0,0 +1,31 @@
+package memory
+
+import (
+	"context"
+	"excalidraw-backend/core"
+	"fmt"
+
+	"github.com/oklog/ulid/v2"
+)
+
+var savedDocuments = make(map[string]core.Document)
+
+type documentStore struct {
+}
+
+func NewDocumentStore() core.DocumentStore {
+	return &documentStore{}
+}
+
+func (s *documentStore) FindID(ctx context.Context, id string) (*core.Document, error) {
+	if val, ok := savedDocuments[id]; ok {
+		return &val, nil
+	}
+	return nil, fmt.Errorf("document with id %s not found", id)
+}
+
+func (s *documentStore) Create(ctx context.Context, document *core.Document) (string, error) {
+	id := ulid.Make().String()
+	savedDocuments[id] = *document
+	return id, nil
+}

+ 40 - 0
frontend.patch

@@ -0,0 +1,40 @@
+diff --git a/.env.production b/.env.production
+index 0c715854..422add5c 100644
+--- a/.env.production
++++ b/.env.production
+@@ -1,17 +1,25 @@
+-VITE_APP_BACKEND_V2_GET_URL=https://json.excalidraw.com/api/v2/
+-VITE_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/
++# VITE_APP_BACKEND_V2_GET_URL=https://json.excalidraw.com/api/v2/
++# VITE_APP_BACKEND_V2_POST_URL=https://json.excalidraw.com/api/v2/post/
++VITE_APP_BACKEND_V2_GET_URL=http://localhost:3002/api/v2/
++VITE_APP_BACKEND_V2_POST_URL=http://localhost:3002/api/v2/post/
+ 
+ VITE_APP_LIBRARY_URL=https://libraries.excalidraw.com
+-VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries
++# VITE_APP_LIBRARY_BACKEND=https://us-central1-excalidraw-room-persistence.cloudfunctions.net/libraries
++VITE_APP_LIBRARY_BACKEND=http://localhost:3002/libraries
++# VITE_APP_PLUS_LP=https://plus.excalidraw.com
++VITE_APP_PLUS_LP=http://localhost:3002/plus/
++# VITE_APP_PLUS_APP=https://app.excalidraw.com
++VITE_APP_PLUS_APP=http://localhost:3002/app/
+ 
+-VITE_APP_PLUS_LP=https://plus.excalidraw.com
+-VITE_APP_PLUS_APP=https://app.excalidraw.com
+-
+-VITE_APP_AI_BACKEND=https://oss-ai.excalidraw.com
++# VITE_APP_AI_BACKEND=https://oss-ai.excalidraw.com
++VITE_APP_AI_BACKEND=http://localhost:3002/ai/
+ 
+ # socket server URL used for collaboration
+-VITE_APP_WS_SERVER_URL=https://oss-collab.excalidraw.com
++VITE_APP_WS_SERVER_URL=http://localhost:3002
++# VITE_APP_WS_SERVER_URL=https://oss-collab.excalidraw.com
+ 
+-VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
++# VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"excalidraw-room-persistence.firebaseapp.com","databaseURL":"https://excalidraw-room-persistence.firebaseio.com","projectId":"excalidraw-room-persistence","storageBucket":"excalidraw-room-persistence.appspot.com","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
++VITE_APP_FIREBASE_CONFIG='{"apiKey":"AIzaSyAd15pYlMci_xIp9ko6wkEsDzAAA0Dn0RU","authDomain":"","databaseURL":"","projectId":"excalidraw-room-persistence","storageBucket":"","messagingSenderId":"654800341332","appId":"1:654800341332:web:4a692de832b55bd57ce0c1"}'
+ 
+-VITE_APP_DISABLE_TRACKING=
++# VITE_APP_DISABLE_TRACKING=
++VITE_APP_DISABLE_TRACKING=yes

+ 42 - 0
go.mod

@@ -0,0 +1,42 @@
+module excalidraw-backend
+
+go 1.21
+
+toolchain go1.22.1
+
+require (
+	github.com/go-chi/chi/v5 v5.0.12
+	github.com/go-chi/cors v1.2.1
+	github.com/go-chi/render v1.0.3
+	github.com/oklog/ulid/v2 v2.1.0
+	github.com/zishang520/engine.io/v2 v2.0.6
+	github.com/zishang520/socket.io/v2 v2.0.5
+)
+
+require (
+	github.com/ajg/form v1.5.1 // indirect
+	github.com/andybalholm/brotli v1.0.6 // indirect
+	github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
+	github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect
+	github.com/gookit/color v1.5.4 // indirect
+	github.com/gorilla/websocket v1.5.0 // indirect
+	github.com/mitchellh/mapstructure v1.5.0 // indirect
+	github.com/onsi/ginkgo/v2 v2.12.0 // indirect
+	github.com/quic-go/qpack v0.4.0 // indirect
+	github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
+	github.com/quic-go/quic-go v0.40.1 // indirect
+	github.com/quic-go/webtransport-go v0.6.0 // indirect
+	github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
+	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
+	github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
+	github.com/zishang520/engine.io-go-parser v1.2.3 // indirect
+	github.com/zishang520/socket.io-go-parser/v2 v2.0.4 // indirect
+	go.uber.org/mock v0.3.0 // indirect
+	golang.org/x/crypto v0.17.0 // indirect
+	golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect
+	golang.org/x/mod v0.12.0 // indirect
+	golang.org/x/net v0.17.0 // indirect
+	golang.org/x/sys v0.15.0 // indirect
+	golang.org/x/text v0.14.0 // indirect
+	golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect
+)

+ 88 - 0
go.sum

@@ -0,0 +1,88 @@
+github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
+github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
+github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
+github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
+github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
+github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
+github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
+github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
+github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
+github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
+github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ=
+github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
+github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
+github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
+github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
+github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
+github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
+github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI=
+github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ=
+github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
+github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
+github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
+github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
+github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
+github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
+github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q=
+github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
+github.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFDD3NxaZLY=
+github.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
+github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
+github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
+github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
+github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
+github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
+github.com/zishang520/engine.io-go-parser v1.2.3 h1:y++zdMKIFgyVvH60TEEHw8gdJkS/qy22wesdALoh+HA=
+github.com/zishang520/engine.io-go-parser v1.2.3/go.mod h1:UrXBVZWQgyHDITYmhnxi2d+NpEWBN8dACboD4dXcx38=
+github.com/zishang520/engine.io/v2 v2.0.6 h1:hXZRwSoZql7xgxbW4xupRjDDLUXcwo/pYSlWc6dNCAk=
+github.com/zishang520/engine.io/v2 v2.0.6/go.mod h1:M908EfW1x3hf+GIONy5x9ZOeeYsPOwUImxyuTabtsYA=
+github.com/zishang520/socket.io-go-parser/v2 v2.0.4 h1:PxzWPrTxueiKXFZJQduJ9sIIzsBbHMi9XO5nTLbIjS4=
+github.com/zishang520/socket.io-go-parser/v2 v2.0.4/go.mod h1:fei4NeEGrSJK+uQPnuI4WVm8h+WLF/kVpDHVjeI+0bA=
+github.com/zishang520/socket.io/v2 v2.0.5 h1:CImu9z6YKFif2mMX2b3y2OUhxxH8nz01PqP6+W6dXy4=
+github.com/zishang520/socket.io/v2 v2.0.5/go.mod h1:r+spG2g+Q0lxhgTHevGl7/h4DzkKrO00i8AEF9vj2PQ=
+go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
+go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
+golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
+golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
+golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
+golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E=
+golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
+google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
+google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 192 - 0
main.go

@@ -0,0 +1,192 @@
+package main
+
+import (
+	"bytes"
+	"excalidraw-backend/core"
+	"excalidraw-backend/documents/memory"
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"os/signal"
+	"syscall"
+
+	"github.com/go-chi/chi/v5"
+	"github.com/go-chi/chi/v5/middleware"
+	"github.com/go-chi/cors"
+	"github.com/go-chi/render"
+	"github.com/oklog/ulid/v2"
+	"github.com/zishang520/engine.io/v2/types"
+	socketio "github.com/zishang520/socket.io/v2/socket"
+)
+
+type (
+	DocumentCreateResponse struct {
+		ID string `json:"id"`
+	}
+
+	UserToFollow struct {
+		SocketId string
+		Username string
+	}
+	OnUserFollowedPayload struct {
+		UserToFollow UserToFollow
+		action       string
+	}
+)
+
+func main() {
+	opts := socketio.DefaultServerOptions()
+	opts.SetMaxHttpBufferSize(5000000)
+	opts.SetPath("/socket.io")
+	opts.SetAllowEIO3(true)
+	opts.SetCors(&types.Cors{
+		Origin:      "*",
+		Credentials: true,
+	})
+	ioo := socketio.NewServer(nil, opts)
+	ioo.On("connection", func(clients ...any) {
+		socket := clients[0].(*socketio.Socket)
+		ioo.To(socketio.Room(socket.Id())).Emit("init-room")
+		me := socket.Id()
+		socket.On("join-room", func(datas ...any) {
+			room := socketio.Room(datas[0].(string))
+			fmt.Printf("Socket %v has joined %v\n", me, room)
+			socket.Join(room)
+			ioo.In(room).FetchSockets()(func(sockets []*socketio.RemoteSocket, _ error) {
+
+				if len(sockets) <= 1 {
+					ioo.To(socketio.Room(socket.Id())).Emit("first-in-room")
+				} else {
+					fmt.Printf("emit new user %v in room %v\n", me, room)
+					socket.Broadcast().To(room).Emit("new-user", me)
+				}
+
+				data := []socketio.SocketId{}
+				for _, osocket := range sockets {
+					data = append(data, osocket.Id())
+				}
+				fmt.Printf(" room %v has users %v\n", room, data)
+
+				ioo.In(room).Emit(
+					"room-user-change",
+					data,
+				)
+
+			})
+		})
+		socket.On("server-broadcast", func(datas ...any) {
+			roomID := datas[0].(string)
+			fmt.Printf(" user %v sends update to room %v\n", me, roomID)
+			socket.Broadcast().To(socketio.Room(roomID)).Emit("client-broadcast", datas[1], datas[2])
+		})
+		socket.On("server-volatile-broadcast", func(datas ...any) {
+			roomID := datas[0].(string)
+			fmt.Printf(" user %v sends volatile update to room %v\n", me, roomID)
+			socket.Volatile().Broadcast().To(socketio.Room(roomID)).Emit("client-broadcast", datas[1], datas[2])
+		})
+
+		socket.On("user-follow", func(datas ...any) {
+			// TODO()
+
+		})
+		socket.On("disconnecting", func(datas ...any) {
+			for _, oroom := range socket.Rooms().Keys() {
+				ioo.In(oroom).FetchSockets()(func(sockets []*socketio.RemoteSocket, _ error) {
+					otherClients := []socketio.SocketId{}
+					fmt.Printf("disconnecting %v from room %v", me, oroom)
+					for _, osocket := range sockets {
+						if osocket.Id() != me {
+							otherClients = append(otherClients, osocket.Id())
+							fmt.Println("other", osocket.Id())
+						}
+					}
+					if len(otherClients) > 0 {
+						ioo.In(oroom).Emit(
+							"room-user-change",
+							otherClients,
+						)
+
+					}
+
+				})
+
+			}
+
+		})
+		socket.On("disconnect", func(datas ...any) {
+			socket.RemoveAllListeners("")
+			socket.Disconnect(true)
+		})
+	})
+
+	r := chi.NewRouter()
+	r.Use(middleware.Logger)
+
+	r.Use(cors.Handler(cors.Options{
+		AllowedOrigins:   []string{"https://*", "http://*"},
+		AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
+		AllowedHeaders:   []string{"Accept", "Authorization", "Content-Type", "Content-Length", "X-CSRF-Token", "Token", "session", "Origin", "Host", "Connection", "Accept-Encoding", "Accept-Language", "X-Requested-With"},
+		AllowCredentials: true,
+		MaxAge:           300, // Maximum value not ignored by any of major browsers
+	}))
+
+	documentStore := memory.NewDocumentStore()
+
+	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
+		w.Write([]byte("you are all set"))
+		fmt.Println(ulid.Make())
+		render.Status(r, http.StatusOK)
+	})
+
+	r.Route("/api/v2", func(r chi.Router) {
+		r.Post("/post/", func(w http.ResponseWriter, r *http.Request) {
+			data := new(bytes.Buffer)
+			_, err := io.Copy(data, r.Body)
+			if err != nil {
+				http.Error(w, "Failed to copy", http.StatusInternalServerError)
+				return
+			}
+			id, err := documentStore.Create(r.Context(), &core.Document{Data: *data})
+			if err != nil {
+				http.Error(w, "Failed to save", http.StatusInternalServerError)
+				return
+			}
+
+			render.JSON(w, r, DocumentCreateResponse{ID: id})
+			render.Status(r, http.StatusOK)
+		})
+		r.Route("/{id}", func(r chi.Router) {
+			r.Get("/", func(w http.ResponseWriter, r *http.Request) {
+				id := chi.URLParam(r, "id")
+				document, err := documentStore.FindID(r.Context(), id)
+				if err != nil {
+					http.Error(w, "not found", http.StatusNotFound)
+					return
+				}
+				w.Write(document.Data.Bytes())
+			})
+		})
+	})
+
+	r.Handle("/socket.io/", ioo.ServeHandler(nil))
+	go http.ListenAndServe(":3002", r)
+
+	exit := make(chan struct{})
+	SignalC := make(chan os.Signal)
+
+	signal.Notify(SignalC, os.Interrupt, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
+	go func() {
+		for s := range SignalC {
+			switch s {
+			case os.Interrupt, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT:
+				close(exit)
+				return
+			}
+		}
+	}()
+
+	<-exit
+	ioo.Close(nil)
+	os.Exit(0)
+}