Skip to content

Commit

Permalink
Update all non-major dependencies (#23)
Browse files Browse the repository at this point in the history
* Create simple CLI

* Create goreleaser config

* Set max editor width

* Update all non-major dependencies

---------

Co-authored-by: Aaron Kirkbride <aaron@aaronkirkbride.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
  • Loading branch information
renovate[bot] and aaron7 authored Jun 19, 2024
1 parent 2af430d commit eef3dc5
Show file tree
Hide file tree
Showing 14 changed files with 590 additions and 77 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ tsconfig.tsbuildinfo
.DS_Store
.env
generated/
*.gen.go
39 changes: 39 additions & 0 deletions cli/.goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
# vim: set ts=2 sw=2 tw=0 fo=cnqoj

version: 2

before:
hooks:
- go mod tidy
- go generate ./...

builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin

archives:
- format: tar.gz
# 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:'
9 changes: 9 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Collabify CLI

## Usage

`collabify <filepath>`

# Future ideas

- Create from a template: e.g. `collabify -t templates/kickoff.md 2024-01-01-project-kickoff.md`
6 changes: 6 additions & 0 deletions cli/api/cfg.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package: api
output: collabify.gen.go
generate:
models: true
chi-server: true
embedded-spec: true
97 changes: 97 additions & 0 deletions cli/api/collabify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package api

import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
)

type Collabify struct {
fileId string
filepath string
}

// Ensure that we've conformed to the `ServerInterface` with a compile-time check
var _ ServerInterface = (*Collabify)(nil)

func NewCollabify(fileId string, filepath string) *Collabify {
return &Collabify{
fileId: fileId,
filepath: filepath,
}
}

func (c Collabify) GetFile(w http.ResponseWriter, r *http.Request, fileId string) {
if fileId != c.fileId {
http.Error(w, "File ID not found", http.StatusNotFound)
return
}

content, err := os.ReadFile(c.filepath)
if err != nil {
http.Error(w, "File not found", http.StatusNotFound)
return
}

w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "text/markdown")
w.Write(content)
}

func (c Collabify) UpdateFile(w http.ResponseWriter, r *http.Request, fileId string) {
if fileId != c.fileId {
http.Error(w, "Route not found", http.StatusNotFound)
return
}

content, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading request", http.StatusBadRequest)
return
}
defer r.Body.Close()

err = os.WriteFile(c.filepath, content, 0644)
if err != nil {
http.Error(w, "Failed to write to file", http.StatusInternalServerError)
return
}

log.Println("File updated")

w.WriteHeader(http.StatusOK)
}

type SessionData struct {
URL string `json:"url"`
JoinURL string `json:"joinUrl"`
}

func (c Collabify) PostSession(w http.ResponseWriter, r *http.Request) {
var session SessionData

if err := json.NewDecoder(r.Body).Decode(&session); err != nil {
http.Error(w, "Invalid input", http.StatusBadRequest)
return
}

fmt.Printf("\n------------------------\n")
fmt.Printf("Your session URL: %s\n", session.URL)
fmt.Printf("Join URL to share: %s\n", session.JoinURL)
fmt.Printf("------------------------\n\n")

w.WriteHeader(http.StatusOK)
}

func (c Collabify) Stop(w http.ResponseWriter, r *http.Request) {
log.Println("Session ended via web")

w.WriteHeader(http.StatusAccepted)

go func() {
os.Exit(0)
}()
}
3 changes: 3 additions & 0 deletions cli/api/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package api

//go:generate go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config cfg.yaml ../../schema/api.yaml
53 changes: 53 additions & 0 deletions cli/api/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package api

import (
"context"
"crypto/sha256"
"crypto/subtle"
"errors"
"fmt"
"net/http"

"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
middleware "github.com/oapi-codegen/nethttp-middleware"
"github.com/rs/cors"
)

func CorsMiddleware(allowedOrigins []string) func(next http.Handler) http.Handler {
return cors.New(cors.Options{
AllowedOrigins: allowedOrigins,
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
AllowCredentials: true,
}).Handler
}

func OpenApiRequestValidatorMiddleware(swagger *openapi3.T, authToken string) func(next http.Handler) http.Handler {
return middleware.OapiRequestValidatorWithOptions(swagger, &middleware.Options{
Options: openapi3filter.Options{
AuthenticationFunc: authenticate(authToken),
},
SilenceServersWarning: true,
})
}

func authenticate(authToken string) openapi3filter.AuthenticationFunc {
expectedAuthHeader := "Bearer " + authToken
expectedHash := sha256.Sum256([]byte(expectedAuthHeader))

return func(ctx context.Context, input *openapi3filter.AuthenticationInput) error {
if input.SecuritySchemeName != "bearerAuth" {
return fmt.Errorf("security scheme %s != 'bearerAuth'", input.SecuritySchemeName)
}

providedAuthHeader := input.RequestValidationInput.Request.Header.Get("Authorization")
providedHash := sha256.Sum256([]byte(providedAuthHeader))

if subtle.ConstantTimeCompare(expectedHash[:], providedHash[:]) != 1 {
return errors.New("invalid token")
}

return nil
}
}
28 changes: 28 additions & 0 deletions cli/api/router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package api

import (
"fmt"
"os"

"github.com/go-chi/chi/v5"
)

func CreateRouter(filename string, fileId string, authToken string, allowedOrigins []string) *chi.Mux {
swagger, err := GetSwagger()
if err != nil {
fmt.Println("Error loading swagger spec:", err)
os.Exit(1)
}

r := chi.NewRouter()

r.Use(CorsMiddleware(allowedOrigins))
r.Use(OpenApiRequestValidatorMiddleware(swagger, authToken))

collabify := NewCollabify(fileId, filename)
r.Route("/v1", func(r chi.Router) {
r.Mount("/", HandlerFromMux(collabify, chi.NewMux()))
})

return r
}
100 changes: 100 additions & 0 deletions cli/collabify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package main

import (
"fmt"
"log"
"net"
"net/http"
"os"
"time"

"collabify/api"

"github.com/getkin/kin-openapi/openapi3filter"
"github.com/pkg/browser"
"github.com/urfave/cli/v2"
)

const defaultAppUrl = "https://collabify.it" // Base URL for the app
const defaultListenHost = "localhost" // `localhost` is recommended for most use cases
const defaultListenPort = "0" // 0 uses the next available port

func init() {
openapi3filter.RegisterBodyDecoder("text/markdown", openapi3filter.FileBodyDecoder)
}

func main() {
appUrl := getEnv("COLLABIFY_APP_URL", defaultAppUrl)
listenHost := getEnv("COLLABIFY_LISTEN_HOST", defaultListenHost)
listenPort := getEnv("COLLABIFY_LISTEN_PORT", defaultListenPort)
allowedOrigins := []string{appUrl}

app := &cli.App{
Name: "collabify",
Usage: "serve a markdown file over HTTP",
Action: func(c *cli.Context) error {
filename := c.Args().Get(0)
if filename == "" {
return cli.Exit("You must provide a filename", 1)
}
if _, err := os.Stat(filename); err != nil {
return err
}

// Generate a random fileId and authToken
fileId, err := generateRandomUrlSafeString(8)
if err != nil {
return err
}
authToken, err := generateRandomUrlSafeString(16)
if err != nil {
return err
}

listener, err := net.Listen("tcp", ":"+listenPort)
if err != nil {
fmt.Println("Failed to listen:", err)
}
defer listener.Close()

// This may be different from listenPort if listenPort
// was set to 0 (i.e. use the next available port)
port := listener.Addr().(*net.TCPAddr).Port

r := api.CreateRouter(filename, fileId, authToken, allowedOrigins)

// Start the HTTP server
serverStarted := make(chan error)
go func() {
err := http.Serve(listener, r)
if err != nil {
serverStarted <- err
return
}
}()

select {
case err := <-serverStarted:
fmt.Println("Server failed to start:", err)

case <-time.After(100 * time.Millisecond):
localUrl := fmt.Sprintf("http://%s:%d", listenHost, port)
newSessionUrl, err := buildNewSessionUrl(appUrl, localUrl, fileId, authToken)
if err != nil {
log.Fatal("Failed to build new URL:", err)
}

fmt.Println("Opening", newSessionUrl)
browser.OpenURL(newSessionUrl)
}

log.Fatal(http.Serve(listener, r))
return nil
},
}

err := app.Run(os.Args)
if err != nil {
fmt.Println(err)
}
}
36 changes: 36 additions & 0 deletions cli/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module collabify

go 1.22.4

require (
github.com/getkin/kin-openapi v0.124.0
github.com/go-chi/chi/v5 v5.0.12
github.com/oapi-codegen/nethttp-middleware v1.0.2
github.com/oapi-codegen/oapi-codegen/v2 v2.3.0
github.com/oapi-codegen/runtime v1.1.1
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
github.com/rs/cors v1.11.0
github.com/urfave/cli/v2 v2.27.2
)

require (
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/go-openapi/jsonpointer v0.20.2 // indirect
github.com/go-openapi/swag v0.22.8 // indirect
github.com/google/uuid v1.5.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/invopop/yaml v0.2.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.21.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading

0 comments on commit eef3dc5

Please sign in to comment.