Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Settings come from ``settings.json``, see ``settings.json.template`` for an exam
``clientSecret`` is the clientSecret used to authenticate with the backend Etherpad instances. This should be a random string that is unique to this service.
``tokenURL`` is the URL of the OAuth2 token endpoint. This is normally `http://<your-host>:<your-port>/oidc/token`

## Basic Auth backed Etherpads
## Basic Auth-backed Etherpads

``username`` is the username used to authenticate with the backend Etherpad instances. This should be a random string that is unique to this service.

Expand All @@ -52,5 +52,11 @@ Settings come from ``settings.json``, see ``settings.json.template`` for an exam
Pads will be fetched every checkInterval * seconds whereas the normal checkInterval runs every checkInterval * milliseconds.
If pads are deleted they are also deleted from the reverse proxy so it can be reassigned to another backend.


## Database support

- SQLite (default, file-based, no setup required) - specified by dbSettings.filename = "db/etherpad-proxy.db"
- Postgres - specified by dbSettings.postgresConnstr - e.g. postgres://user:password@localhost:5432/etherpad_proxy_db?sslmode=disable

# License
Apache 2
8 changes: 5 additions & 3 deletions admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ package main

import (
"context"
"net/http"

"github.com/ether/etherpad-proxy/databases/interfaces"
"github.com/ether/etherpad-proxy/ui"
"go.uber.org/zap"
"net/http"
)

type AdminPanel struct {
DB *DB
DB interfaces.IDB
logger *zap.SugaredLogger
}

func (a *AdminPanel) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
padIDMap, err := a.DB.getAllPads()
padIDMap, err := a.DB.GetAllPads()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
Expand Down
36 changes: 36 additions & 0 deletions databases/database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package databases

import (
"fmt"

"github.com/ether/etherpad-proxy/databases/interfaces"
"github.com/ether/etherpad-proxy/databases/postgres"
"github.com/ether/etherpad-proxy/databases/sqlite"
"github.com/ether/etherpad-proxy/models"
)

type DBType string

const (
DBTypeSQLite DBType = "sqlite"
DBTypePostgres DBType = "postgres"
)

func CreateNewDatabase(settings models.Settings) (interfaces.IDB, error) {
var dbType = DBTypePostgres
if settings.DBSettings.Filename != "" {
dbType = DBTypeSQLite
}

if settings.DBSettings.Connstr != "" {
dbType = DBTypePostgres
}

switch dbType {
case DBTypePostgres:
return postgres.NewPostgresDB(settings.DBSettings.Connstr)
case DBTypeSQLite:
return sqlite.NewSQLiteDB(settings.DBSettings.Filename)
}
return nil, fmt.Errorf("unknown database type: %s", dbType)
}
13 changes: 13 additions & 0 deletions databases/interfaces/iDB.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package interfaces

import "github.com/ether/etherpad-proxy/models"

type IDB interface {
Close() error
Get(id string) (*models.DBBackend, error)
CleanUpPads(padIds []string, padPrefix string) error
RecordClash(id string, data string) error
Set(id string, backend models.DBBackend) error
GetAllPads() (map[string]string, error)
GetClashByPadID(id string) ([]string, error)
}
135 changes: 135 additions & 0 deletions databases/postgres/postgres_db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package postgres

import (
"database/sql"

sq "github.com/Masterminds/squirrel"
"github.com/ether/etherpad-proxy/databases/interfaces"
"github.com/ether/etherpad-proxy/models"
)

type Postgres struct {
Conn *sql.DB
}

var _ interfaces.IDB = (*Postgres)(nil)

func NewPostgresDB(connstr string) (*Postgres, error) {
conn, err := sql.Open("postgres", connstr)
if err != nil {
return nil, err
}

db := &Postgres{
Conn: conn,
}

if _, err = db.Conn.Exec("CREATE TABLE IF NOT EXISTS pad (id TEXT, backend TEXT, PRIMARY KEY (id))"); err != nil {
return nil, err
}

if _, err = db.Conn.Exec("CREATE TABLE IF NOT EXISTS clashes (id TEXT, data TEXT, PRIMARY KEY (id, data))"); err != nil {
return nil, err
}

return db, nil
}

func (db *Postgres) Close() error {
return db.Conn.Close()
}

func (db *Postgres) Get(id string) (*models.DBBackend, error) {
var data string
var sqlGet, args, err = sq.Select("backend").From("pad").Where(sq.Eq{"id": id}).ToSql()
if err != nil {
return nil, err
}
err = db.Conn.QueryRow(sqlGet, args...).Scan(&data)
if err != nil {
return nil, err
}

var actualData = models.DBBackend{
Backend: data,
}

return &actualData, nil
}

func (db *Postgres) CleanUpPads(padIds []string, padPrefix string) error {
sqlDelete, args, err := sq.Delete("pad").Where(sq.And{sq.NotEq{"id": padIds},
sq.Like{"backend": padPrefix}}).ToSql()
if err != nil {
return err
}

_, err = db.Conn.Exec(sqlDelete, args...)
return err
}

func (db *Postgres) RecordClash(id string, data string) error {
_, err := db.Conn.Exec("INSERT OR REPLACE INTO clashes (id, data) VALUES (?, ?)", id, data)
if err != nil {
return err
}
return nil
}

func (db *Postgres) Set(id string, dbModel models.DBBackend) error {

_, err := db.Conn.Exec("INSERT OR REPLACE INTO pad (id, backend) VALUES (?, ?)", id, dbModel.Backend)
if err != nil {
return err
}
return nil
}

func (db *Postgres) GetAllPads() (map[string]string, error) {
var padIDMap = make(map[string]string)
var sqlGet, args, err = sq.Select("id, backend").From("pad").ToSql()
if err != nil {
return nil, err
}
rows, err := db.Conn.Query(sqlGet, args...)
if err != nil {
return nil, err
}
defer rows.Close()

for rows.Next() {
var padID string
var backend string
if err := rows.Scan(&padID, &backend); err != nil {
return nil, err
}
padIDMap[padID] = backend
}

return padIDMap, nil
}

func (db *Postgres) GetClashByPadID(padId string) ([]string, error) {
var sqlGet, args, err = sq.Select("data").From("clashes").Where(sq.Eq{"id": padId}).ToSql()

if err != nil {
return nil, err
}

rows, err := db.Conn.Query(sqlGet, args...)
if err != nil {
return nil, err
}

defer rows.Close()
var data = make([]string, 0)
for rows.Next() {
var clash string
if err := rows.Scan(&clash); err != nil {
return nil, err
}
data = append(data, clash)
}

return data, nil
}
12 changes: 8 additions & 4 deletions db.go → databases/sqlite/sqlite_db.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package main
package sqlite

import (
"database/sql"

"github.com/ether/etherpad-proxy/databases/interfaces"
"github.com/ether/etherpad-proxy/models"
_ "modernc.org/sqlite"
)
Expand All @@ -12,7 +14,7 @@ type DB struct {
Conn *sql.DB
}

func NewDB(filename string) (*DB, error) {
func NewSQLiteDB(filename string) (*DB, error) {
conn, err := sql.Open("sqlite", filename)
if err != nil {
return nil, err
Expand Down Expand Up @@ -86,7 +88,7 @@ func (db *DB) Set(id string, dbModel models.DBBackend) error {
return nil
}

func (db *DB) getAllPads() (map[string]string, error) {
func (db *DB) GetAllPads() (map[string]string, error) {
var padIDMap = make(map[string]string)
var sqlGet, args, err = sq.Select("id, backend").From("pad").ToSql()
if err != nil {
Expand All @@ -110,7 +112,7 @@ func (db *DB) getAllPads() (map[string]string, error) {
return padIDMap, nil
}

func (db *DB) getClashByPadID(padId string) ([]string, error) {
func (db *DB) GetClashByPadID(padId string) ([]string, error) {
var sqlGet, args, err = sq.Select("data").From("clashes").Where(sq.Eq{"id": padId}).ToSql()

if err != nil {
Expand All @@ -134,3 +136,5 @@ func (db *DB) getClashByPadID(padId string) ([]string, error) {

return data, nil
}

var _ interfaces.IDB = (*DB)(nil)
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
Expand Down
1 change: 1 addition & 0 deletions models/Settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type Settings struct {

type DBSettings struct {
Filename string `json:"filename"`
Connstr string `json:"postgresConnstr"`
}

type Backend struct {
Expand Down
14 changes: 8 additions & 6 deletions proxyHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,27 @@ import (
"context"
"database/sql"
"errors"
"github.com/PuerkitoBio/goquery"
"github.com/ether/etherpad-proxy/models"
"github.com/ether/etherpad-proxy/ui"
"go.uber.org/zap"
"log"
"net/http"
"net/http/httputil"
"slices"
"strconv"
"strings"
"time"

"github.com/PuerkitoBio/goquery"
"github.com/ether/etherpad-proxy/databases/interfaces"
"github.com/ether/etherpad-proxy/models"
"github.com/ether/etherpad-proxy/ui"
"go.uber.org/zap"
)
import "math/rand/v2"
import _ "github.com/ether/etherpad-proxy/ui"

type ProxyHandler struct {
p map[string]httputil.ReverseProxy
logger *zap.SugaredLogger
db DB
db interfaces.IDB
}

type StaticResource struct {
Expand Down Expand Up @@ -126,7 +128,7 @@ func (ph *ProxyHandler) createRoute(padId *string, r *http.Request) (*httputil.R
}
if errors.Is(err, sql.ErrNoRows) {
// if no backend is stored for this pad, create a new connection
result, err := ph.db.getClashByPadID(*padId)
result, err := ph.db.GetClashByPadID(*padId)

if err != nil && errors.Is(err, sql.ErrNoRows) || len(result) == 0 {
AvailableBackends.Mutex.Lock()
Expand Down
Loading