Use filesystem based kv store instead of sqlite

This commit is contained in:
r 2019-12-17 20:17:25 +00:00
parent 3b50f40c08
commit 59aad78f66
11 changed files with 217 additions and 88 deletions

2
go.mod
View File

@ -4,7 +4,7 @@ go 1.13
require ( require (
github.com/gorilla/mux v1.7.3 github.com/gorilla/mux v1.7.3
github.com/mattn/go-sqlite3 v2.0.1+incompatible github.com/mattn/go-sqlite3 v2.0.2+incompatible // indirect
mastodon v0.0.0-00010101000000-000000000000 mastodon v0.0.0-00010101000000-000000000000
) )

10
go.mod.old Normal file
View File

@ -0,0 +1,10 @@
module web
go 1.13
require (
github.com/gorilla/mux v1.7.3
mastodon v0.0.0-00010101000000-000000000000
)
replace mastodon => ./mastodon

4
go.sum
View File

@ -2,7 +2,7 @@ github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw= github.com/mattn/go-sqlite3 v2.0.2+incompatible h1:qzw9c2GNT8UFrgWNDhCTqRqYUSmu/Dav/9Z58LGpk7U=
github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v2.0.2+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y= github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE= github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=

92
kv/kv.go Normal file
View File

@ -0,0 +1,92 @@
package kv
import (
"errors"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
)
var (
errInvalidKey = errors.New("invalid key")
errNoSuchKey = errors.New("no such key")
)
type Database struct {
data map[string][]byte
basedir string
m sync.RWMutex
}
func NewDatabse(basedir string) (db *Database, err error) {
err = os.Mkdir(basedir, 0755)
if err != nil && !os.IsExist(err) {
return
}
return &Database{
data: make(map[string][]byte),
basedir: basedir,
}, nil
}
func (db *Database) Set(key string, val []byte) (err error) {
if len(key) < 1 {
return errInvalidKey
}
db.m.Lock()
defer func() {
if err != nil {
delete(db.data, key)
}
db.m.Unlock()
}()
db.data[key] = val
err = ioutil.WriteFile(filepath.Join(db.basedir, key), val, 0644)
return
}
func (db *Database) Get(key string) (val []byte, err error) {
if len(key) < 1 {
return nil, errInvalidKey
}
db.m.RLock()
defer db.m.RUnlock()
data, ok := db.data[key]
if !ok {
data, err = ioutil.ReadFile(filepath.Join(db.basedir, key))
if err != nil {
err = errNoSuchKey
return nil, err
}
db.data[key] = data
}
val = make([]byte, len(data))
copy(val, data)
return
}
func (db *Database) Remove(key string) {
if len(key) < 1 || strings.ContainsRune(key, os.PathSeparator) {
return
}
db.m.Lock()
defer db.m.Unlock()
delete(db.data, key)
os.Remove(filepath.Join(db.basedir, key))
return
}

17
main.go
View File

@ -1,19 +1,18 @@
package main package main
import ( import (
"database/sql"
"log" "log"
"math/rand" "math/rand"
"net/http" "net/http"
"os" "os"
"path/filepath"
"time" "time"
"web/config" "web/config"
"web/kv"
"web/renderer" "web/renderer"
"web/repository" "web/repository"
"web/service" "web/service"
_ "github.com/mattn/go-sqlite3"
) )
func init() { func init() {
@ -35,22 +34,24 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
db, err := sql.Open("sqlite3", config.DatabasePath) err = os.Mkdir(config.DatabasePath, 0755)
if err != nil { if err != nil && !os.IsExist(err) {
log.Fatal(err) log.Fatal(err)
} }
defer db.Close()
sessionRepo, err := repository.NewSessionRepository(db) sessionDB, err := kv.NewDatabse(filepath.Join(config.DatabasePath, "session"))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
appRepo, err := repository.NewAppRepository(db) appDB, err := kv.NewDatabse(filepath.Join(config.DatabasePath, "app"))
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
sessionRepo := repository.NewSessionRepository(sessionDB)
appRepo := repository.NewAppRepository(appDB)
var logger *log.Logger var logger *log.Logger
if len(config.Logfile) < 1 { if len(config.Logfile) < 1 {
logger = log.New(os.Stdout, "", log.LstdFlags) logger = log.New(os.Stdout, "", log.LstdFlags)

View File

@ -1,19 +1,40 @@
package model package model
import "errors" import (
"errors"
"strings"
)
var ( var (
ErrAppNotFound = errors.New("app not found") ErrAppNotFound = errors.New("app not found")
) )
type App struct { type App struct {
InstanceURL string InstanceDomain string
ClientID string InstanceURL string
ClientSecret string ClientID string
ClientSecret string
} }
type AppRepository interface { type AppRepository interface {
Add(app App) (err error) Add(app App) (err error)
Update(instanceURL string, clientID string, clientSecret string) (err error) Get(instanceDomain string) (app App, err error)
Get(instanceURL string) (app App, err error) }
func (a *App) Marshal() []byte {
str := a.InstanceURL + "\n" + a.ClientID + "\n" + a.ClientSecret
return []byte(str)
}
func (a *App) Unmarshal(instanceDomain string, data []byte) error {
str := string(data)
lines := strings.Split(str, "\n")
if len(lines) != 3 {
return errors.New("invalid data")
}
a.InstanceDomain = instanceDomain
a.InstanceURL = lines[0]
a.ClientID = lines[1]
a.ClientSecret = lines[2]
return nil
} }

View File

@ -1,15 +1,18 @@
package model package model
import "errors" import (
"errors"
"strings"
)
var ( var (
ErrSessionNotFound = errors.New("session not found") ErrSessionNotFound = errors.New("session not found")
) )
type Session struct { type Session struct {
ID string ID string
InstanceURL string InstanceDomain string
AccessToken string AccessToken string
} }
type SessionRepository interface { type SessionRepository interface {
@ -21,3 +24,26 @@ type SessionRepository interface {
func (s Session) IsLoggedIn() bool { func (s Session) IsLoggedIn() bool {
return len(s.AccessToken) > 0 return len(s.AccessToken) > 0
} }
func (s *Session) Marshal() []byte {
str := s.InstanceDomain + "\n" + s.AccessToken
return []byte(str)
}
func (s *Session) Unmarshal(id string, data []byte) error {
str := string(data)
lines := strings.Split(str, "\n")
size := len(lines)
if size == 1 {
s.InstanceDomain = lines[0]
} else if size == 2 {
s.InstanceDomain = lines[0]
s.AccessToken = lines[1]
} else {
return errors.New("invalid data")
}
s.ID = id
return nil
}

View File

@ -1,54 +1,33 @@
package repository package repository
import ( import (
"database/sql" "web/kv"
"web/model" "web/model"
) )
type appRepository struct { type appRepository struct {
db *sql.DB db *kv.Database
} }
func NewAppRepository(db *sql.DB) (*appRepository, error) { func NewAppRepository(db *kv.Database) *appRepository {
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS app
(instance_url varchar, client_id varchar, client_secret varchar)`,
)
if err != nil {
return nil, err
}
return &appRepository{ return &appRepository{
db: db, db: db,
}, nil }
} }
func (repo *appRepository) Add(a model.App) (err error) { func (repo *appRepository) Add(a model.App) (err error) {
_, err = repo.db.Exec("INSERT INTO app VALUES (?, ?, ?)", a.InstanceURL, a.ClientID, a.ClientSecret) err = repo.db.Set(a.InstanceDomain, a.Marshal())
return return
} }
func (repo *appRepository) Update(instanceURL string, clientID string, clientSecret string) (err error) { func (repo *appRepository) Get(instanceDomain string) (a model.App, err error) {
_, err = repo.db.Exec("UPDATE app SET client_id = ?, client_secret = ? where instance_url = ?", clientID, clientSecret, instanceURL) data, err := repo.db.Get(instanceDomain)
return
}
func (repo *appRepository) Get(instanceURL string) (a model.App, err error) {
rows, err := repo.db.Query("SELECT * FROM app WHERE instance_url = ?", instanceURL)
if err != nil { if err != nil {
return
}
defer rows.Close()
if !rows.Next() {
err = model.ErrAppNotFound err = model.ErrAppNotFound
return return
} }
err = rows.Scan(&a.InstanceURL, &a.ClientID, &a.ClientSecret) err = a.Unmarshal(instanceDomain, data)
if err != nil {
return
}
return return
} }

View File

@ -1,54 +1,50 @@
package repository package repository
import ( import (
"database/sql" "web/kv"
"web/model" "web/model"
) )
type sessionRepository struct { type sessionRepository struct {
db *sql.DB db *kv.Database
} }
func NewSessionRepository(db *sql.DB) (*sessionRepository, error) { func NewSessionRepository(db *kv.Database) *sessionRepository {
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS session
(id varchar, instance_url varchar, access_token varchar)`,
)
if err != nil {
return nil, err
}
return &sessionRepository{ return &sessionRepository{
db: db, db: db,
}, nil }
} }
func (repo *sessionRepository) Add(s model.Session) (err error) { func (repo *sessionRepository) Add(s model.Session) (err error) {
_, err = repo.db.Exec("INSERT INTO session VALUES (?, ?, ?)", s.ID, s.InstanceURL, s.AccessToken) err = repo.db.Set(s.ID, s.Marshal())
return return
} }
func (repo *sessionRepository) Update(sessionID string, accessToken string) (err error) { func (repo *sessionRepository) Update(id string, accessToken string) (err error) {
_, err = repo.db.Exec("UPDATE session SET access_token = ? where id = ?", accessToken, sessionID) data, err := repo.db.Get(id)
return
}
func (repo *sessionRepository) Get(id string) (s model.Session, err error) {
rows, err := repo.db.Query("SELECT * FROM session WHERE id = ?", id)
if err != nil { if err != nil {
return return
} }
defer rows.Close()
if !rows.Next() { var s model.Session
err = s.Unmarshal(id, data)
if err != nil {
return
}
s.AccessToken = accessToken
return repo.db.Set(id, s.Marshal())
}
func (repo *sessionRepository) Get(id string) (s model.Session, err error) {
data, err := repo.db.Get(id)
if err != nil {
err = model.ErrSessionNotFound err = model.ErrSessionNotFound
return return
} }
err = rows.Scan(&s.ID, &s.InstanceURL, &s.AccessToken) err = s.Unmarshal(id, data)
if err != nil {
return
}
return return
} }

View File

@ -40,12 +40,12 @@ func (s *authService) getClient(ctx context.Context) (c *mastodon.Client, err er
if err != nil { if err != nil {
return nil, ErrInvalidSession return nil, ErrInvalidSession
} }
client, err := s.appRepo.Get(session.InstanceURL) client, err := s.appRepo.Get(session.InstanceDomain)
if err != nil { if err != nil {
return return
} }
c = mastodon.NewClient(&mastodon.Config{ c = mastodon.NewClient(&mastodon.Config{
Server: session.InstanceURL, Server: client.InstanceURL,
ClientID: client.ClientID, ClientID: client.ClientID,
ClientSecret: client.ClientSecret, ClientSecret: client.ClientSecret,
AccessToken: session.AccessToken, AccessToken: session.AccessToken,

View File

@ -9,7 +9,6 @@ import (
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"net/url" "net/url"
"path"
"strings" "strings"
"mastodon" "mastodon"
@ -64,14 +63,18 @@ func NewService(clientName string, clientScope string, clientWebsite string,
func (svc *service) GetAuthUrl(ctx context.Context, instance string) ( func (svc *service) GetAuthUrl(ctx context.Context, instance string) (
redirectUrl string, sessionID string, err error) { redirectUrl string, sessionID string, err error) {
if !strings.HasPrefix(instance, "https://") { var instanceURL string
instance = "https://" + instance if strings.HasPrefix(instance, "https://") {
instanceURL = instance
instance = strings.TrimPrefix(instance, "https://")
} else {
instanceURL = "https://" + instance
} }
sessionID = util.NewSessionId() sessionID = util.NewSessionId()
err = svc.sessionRepo.Add(model.Session{ err = svc.sessionRepo.Add(model.Session{
ID: sessionID, ID: sessionID,
InstanceURL: instance, InstanceDomain: instance,
}) })
if err != nil { if err != nil {
return return
@ -85,7 +88,7 @@ func (svc *service) GetAuthUrl(ctx context.Context, instance string) (
var mastoApp *mastodon.Application var mastoApp *mastodon.Application
mastoApp, err = mastodon.RegisterApp(ctx, &mastodon.AppConfig{ mastoApp, err = mastodon.RegisterApp(ctx, &mastodon.AppConfig{
Server: instance, Server: instanceURL,
ClientName: svc.clientName, ClientName: svc.clientName,
Scopes: svc.clientScope, Scopes: svc.clientScope,
Website: svc.clientWebsite, Website: svc.clientWebsite,
@ -96,9 +99,10 @@ func (svc *service) GetAuthUrl(ctx context.Context, instance string) (
} }
app = model.App{ app = model.App{
InstanceURL: instance, InstanceDomain: instance,
ClientID: mastoApp.ClientID, InstanceURL: instanceURL,
ClientSecret: mastoApp.ClientSecret, ClientID: mastoApp.ClientID,
ClientSecret: mastoApp.ClientSecret,
} }
err = svc.appRepo.Add(app) err = svc.appRepo.Add(app)
@ -136,7 +140,7 @@ func (svc *service) GetUserToken(ctx context.Context, sessionID string, c *masto
return return
} }
app, err := svc.appRepo.Get(session.InstanceURL) app, err := svc.appRepo.Get(session.InstanceDomain)
if err != nil { if err != nil {
return return
} }