forked from tildetown/bbj2
		
	restructure stuff
This commit is contained in:
		
							parent
							
								
									62bff8ce22
								
							
						
					
					
						commit
						9d8bd4a2df
					
				
							
								
								
									
										45
									
								
								server/cmd/api/api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								server/cmd/api/api.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | ||||
| package api | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"git.tilde.town/tildetown/bbj2/server/cmd/config" | ||||
| 	"git.tilde.town/tildetown/bbj2/server/cmd/db" | ||||
| ) | ||||
| 
 | ||||
| type HTTPError struct { | ||||
| 	Msg  string | ||||
| 	Code int | ||||
| } | ||||
| 
 | ||||
| func (e *HTTPError) Error() string { | ||||
| 	return fmt.Sprintf("%d %s", e.Code, e.Msg) | ||||
| } | ||||
| 
 | ||||
| type BBJResponse struct { | ||||
| 	Error   bool               `json:"error"` | ||||
| 	Data    interface{}        `json:"data"` | ||||
| 	Usermap map[string]db.User `json:"usermap"` | ||||
| } | ||||
| 
 | ||||
| type API struct { | ||||
| 	User *db.User | ||||
| 	Opts config.Options | ||||
| } | ||||
| 
 | ||||
| func (a *API) InstanceInfo() (*BBJResponse, error) { | ||||
| 	type instanceInfo struct { | ||||
| 		InstanceName string `json:"instance_name"` | ||||
| 		AllowAnon    bool   `json:"allow_anon"` | ||||
| 		Admins       []string | ||||
| 	} | ||||
| 	return &BBJResponse{ | ||||
| 		Data: instanceInfo{ | ||||
| 			InstanceName: a.Opts.Config.InstanceName, | ||||
| 			AllowAnon:    a.Opts.Config.AllowAnon, | ||||
| 			Admins:       a.Opts.Config.Admins, | ||||
| 		}, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| type ApiHandler func() (*BBJResponse, error) | ||||
							
								
								
									
										22
									
								
								server/cmd/api/api_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								server/cmd/api/api_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| package api | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"git.tilde.town/tildetown/bbj2/server/cmd/config" | ||||
| ) | ||||
| 
 | ||||
| func TestInstanceInfo(t *testing.T) { | ||||
| 	ts := []struct { | ||||
| 		name     string | ||||
| 		opts     config.Options | ||||
| 		wantResp *BBJResponse | ||||
| 		wantErr  HTTPError | ||||
| 	}{} | ||||
| 
 | ||||
| 	for _, tt := range ts { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			// TODO | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| @ -1,7 +1,9 @@ | ||||
| package main | ||||
| package config | ||||
| 
 | ||||
| import ( | ||||
| 	"database/sql" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 
 | ||||
| 	yaml "gopkg.in/yaml.v3" | ||||
| @ -14,7 +16,32 @@ const ( | ||||
| 	defaultDBPath       = "db.sqlite3" | ||||
| ) | ||||
| 
 | ||||
| func parseConfig(configPath string) (*Config, error) { | ||||
| type IOStreams struct { | ||||
| 	Err io.Writer | ||||
| 	Out io.Writer | ||||
| } | ||||
| 
 | ||||
| type Config struct { | ||||
| 	Admins       []string | ||||
| 	Port         int | ||||
| 	Host         string | ||||
| 	InstanceName string `yaml:"instance_name"` | ||||
| 	AllowAnon    bool   `yaml:"allow_anon"` | ||||
| 	Debug        bool | ||||
| 	DBPath       string `yaml:"db_path"` | ||||
| } | ||||
| 
 | ||||
| type Options struct { | ||||
| 	ConfigPath string | ||||
| 	IO         IOStreams | ||||
| 	Log        func(string) | ||||
| 	Logf       func(string, ...interface{}) | ||||
| 	Config     Config | ||||
| 	DB         *sql.DB | ||||
| 	Reset      bool | ||||
| } | ||||
| 
 | ||||
| func ParseConfig(configPath string) (*Config, error) { | ||||
| 	cfgBytes, err := os.ReadFile(configPath) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to read config file: %w", err) | ||||
							
								
								
									
										7
									
								
								server/cmd/db/db.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								server/cmd/db/db.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| package db | ||||
| 
 | ||||
| type User struct { | ||||
| 	ID       string | ||||
| 	Username string | ||||
| 	Hash     string | ||||
| } | ||||
							
								
								
									
										4
									
								
								server/cmd/example_config.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								server/cmd/example_config.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| allow_anon: true | ||||
| instance_name: "t i l d e . t o w n" | ||||
| admins: ["vilmibm", "natalia", "archangelic"] | ||||
| port: 8099 | ||||
| @ -7,13 +7,14 @@ import ( | ||||
| 	"errors" | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/google/uuid" | ||||
| 	"git.tilde.town/tildetown/bbj2/server/cmd/api" | ||||
| 	"git.tilde.town/tildetown/bbj2/server/cmd/config" | ||||
| 	"git.tilde.town/tildetown/bbj2/server/cmd/db" | ||||
| 	_ "github.com/mattn/go-sqlite3" | ||||
| ) | ||||
| 
 | ||||
| @ -22,40 +23,15 @@ import ( | ||||
| //go:embed schema.sql | ||||
| var schemaSQL string | ||||
| 
 | ||||
| type Config struct { | ||||
| 	Admins       []string | ||||
| 	Port         int | ||||
| 	Host         string | ||||
| 	InstanceName string `yaml:"instance_name"` | ||||
| 	AllowAnon    bool   `yaml:"allow_anon"` | ||||
| 	Debug        bool | ||||
| 	DBPath       string `yaml:"db_path"` | ||||
| } | ||||
| 
 | ||||
| type iostreams struct { | ||||
| 	Err io.Writer | ||||
| 	Out io.Writer | ||||
| } | ||||
| 
 | ||||
| type options struct { | ||||
| 	ConfigPath string | ||||
| 	IO         iostreams | ||||
| 	Log        func(string) | ||||
| 	Logf       func(string, ...interface{}) | ||||
| 	Config     Config | ||||
| 	DB         *sql.DB | ||||
| 	Reset      bool | ||||
| } | ||||
| 
 | ||||
| func main() { | ||||
| 	var configFlag = flag.String("config", "config.yml", "A path to a config file.") | ||||
| 	var resetFlag = flag.Bool("reset", false, "reset the database. WARNING this wipes everything.") | ||||
| 	flag.Parse() | ||||
| 	io := iostreams{ | ||||
| 	io := config.IOStreams{ | ||||
| 		Err: os.Stderr, | ||||
| 		Out: os.Stdout, | ||||
| 	} | ||||
| 	opts := &options{ | ||||
| 	opts := &config.Options{ | ||||
| 		ConfigPath: *configFlag, | ||||
| 		Reset:      *resetFlag, | ||||
| 		IO:         io, | ||||
| @ -77,14 +53,14 @@ func main() { | ||||
| 
 | ||||
| type Teardown func() | ||||
| 
 | ||||
| func setupDB(opts *options) (Teardown, error) { | ||||
| func setupDB(opts *config.Options) (Teardown, error) { | ||||
| 	db, err := sql.Open("sqlite3", opts.Config.DBPath) | ||||
| 	opts.DB = db | ||||
| 	return func() { db.Close() }, err | ||||
| } | ||||
| 
 | ||||
| func _main(opts *options) error { | ||||
| 	cfg, err := parseConfig(opts.ConfigPath) | ||||
| func _main(opts *config.Options) error { | ||||
| 	cfg, err := config.ParseConfig(opts.ConfigPath) | ||||
| 	if err != nil { | ||||
| 		fmt.Fprintf(os.Stderr, "could not read config file '%s'", opts.ConfigPath) | ||||
| 		os.Exit(1) | ||||
| @ -114,7 +90,7 @@ func _main(opts *options) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func ensureSchema(opts options) error { | ||||
| func ensureSchema(opts config.Options) error { | ||||
| 	db := opts.DB | ||||
| 
 | ||||
| 	if opts.Reset { | ||||
| @ -149,7 +125,7 @@ func ensureSchema(opts options) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func handler(opts options, f http.HandlerFunc) http.HandlerFunc { | ||||
| func handler(opts config.Options, f http.HandlerFunc) http.HandlerFunc { | ||||
| 	// TODO make this more real | ||||
| 	return func(w http.ResponseWriter, req *http.Request) { | ||||
| 		opts.Log(req.URL.Path) | ||||
| @ -162,46 +138,27 @@ func handler(opts options, f http.HandlerFunc) http.HandlerFunc { | ||||
| // encryption, it doesn't really help anything. I'd rather have plaintext + | ||||
| // transport encryption and then, on the server side, proper salted hashing. | ||||
| 
 | ||||
| type User struct { | ||||
| 	ID       string | ||||
| 	Username string | ||||
| 	Hash     string | ||||
| } | ||||
| 
 | ||||
| type BBJResponse struct { | ||||
| 	Error   bool            `json:"error"` | ||||
| 	Data    interface{}     `json:"data"` | ||||
| 	Usermap map[string]User `json:"usermap"` | ||||
| } | ||||
| 
 | ||||
| func writeResponse(w http.ResponseWriter, resp BBJResponse) { | ||||
| func writeResponse(w http.ResponseWriter, resp api.BBJResponse) { | ||||
| 	w.WriteHeader(http.StatusOK) | ||||
| 	w.Header().Set("Content-Type", "application/json") | ||||
| 	json.NewEncoder(w).Encode(resp) | ||||
| } | ||||
| 
 | ||||
| // NB breaking: i'm not just returning 200 always but using http status codes | ||||
| func writeErrorResponse(w http.ResponseWriter, code int, resp BBJResponse) { | ||||
| func writeErrorResponse(w http.ResponseWriter, code int, resp api.BBJResponse) { | ||||
| 	w.WriteHeader(code) | ||||
| 	w.Header().Set("Content-Type", "application/json") | ||||
| 	json.NewEncoder(w).Encode(resp) | ||||
| } | ||||
| 
 | ||||
| func getUserFromReq(opts options, req *http.Request) (u User, err error) { | ||||
| func getUserFromReq(opts config.Options, req *http.Request) (u *db.User, err error) { | ||||
| 	u = &db.User{} | ||||
| 	u.Username = req.Header.Get("User") | ||||
| 	u.Hash = req.Header.Get("Auth") | ||||
| 	if u.Username == "" { | ||||
| 		err = errors.New("no User header set") | ||||
| 	if u.Username == "" || u.Username == "anon" { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if u.Username == "anon" { | ||||
| 		if !opts.Config.AllowAnon { | ||||
| 			err = errors.New("anonymous access disabled") | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	db := opts.DB | ||||
| 	stmt, err := db.Prepare("select auth_hash, id from users where user_name = ?") | ||||
| 	if err != nil { | ||||
| @ -228,7 +185,7 @@ func getUserFromReq(opts options, req *http.Request) (u User, err error) { | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func checkAuth(opts options, username, hash string) error { | ||||
| func checkAuth(opts config.Options, username, hash string) error { | ||||
| 	db := opts.DB | ||||
| 	stmt, err := db.Prepare("select auth_hash from users where user_name = ?") | ||||
| 	if err != nil { | ||||
| @ -253,29 +210,51 @@ func checkAuth(opts options, username, hash string) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func setupAPI(opts options) { | ||||
| 	serverErr := func(w http.ResponseWriter, err error) { | ||||
| 		opts.Logf(err.Error()) | ||||
| 		writeErrorResponse(w, 500, BBJResponse{ | ||||
| func setupAPI(opts config.Options) { | ||||
| 	newAPI := func(opts config.Options, w http.ResponseWriter, req *http.Request) *api.API { | ||||
| 		user, err := getUserFromReq(opts, req) | ||||
| 		if err != nil { | ||||
| 			writeErrorResponse(w, 403, api.BBJResponse{ | ||||
| 				Error: true, | ||||
| 			Data:  "server error", | ||||
| 				Data:  err.Error(), | ||||
| 			}) | ||||
| 			return nil | ||||
| 		} | ||||
| 		return &api.API{ | ||||
| 			Opts: opts, | ||||
| 			User: user, | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	badMethod := func(w http.ResponseWriter) { | ||||
| 		writeErrorResponse(w, 400, BBJResponse{ | ||||
| 	invokeAPI := func(w http.ResponseWriter, apiFn api.ApiHandler) { | ||||
| 		resp, err := apiFn() | ||||
| 		if err != nil { | ||||
| 			he := &api.HTTPError{} | ||||
| 			_ = errors.As(err, &he) | ||||
| 			resp := api.BBJResponse{ | ||||
| 				Error: true, | ||||
| 			Data:  "bad method", | ||||
| 		}) | ||||
| 				Data:  he.Msg, | ||||
| 			} | ||||
| 			w.WriteHeader(he.Code) | ||||
| 			w.Header().Set("Content-Type", "application/json") | ||||
| 			json.NewEncoder(w).Encode(resp) | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 	invalidArgs := func(w http.ResponseWriter) { | ||||
| 		writeErrorResponse(w, 400, BBJResponse{ | ||||
| 			Error: true, | ||||
| 			Data:  "invalid args", | ||||
| 		}) | ||||
| 		w.WriteHeader(http.StatusOK) | ||||
| 		w.Header().Set("Content-Type", "application/json") | ||||
| 		json.NewEncoder(w).Encode(resp) | ||||
| 	} | ||||
| 
 | ||||
| 	http.HandleFunc("/instance_info", handler(opts, func(w http.ResponseWriter, req *http.Request) { | ||||
| 		api := newAPI(opts, w, req) | ||||
| 		if api == nil { | ||||
| 			return | ||||
| 		} | ||||
| 		invokeAPI(w, api.InstanceInfo) | ||||
| 	})) | ||||
| 
 | ||||
| 	/* | ||||
| 		http.HandleFunc("/instance_info", handler(opts, func(w http.ResponseWriter, req *http.Request) { | ||||
| 			type instanceInfo struct { | ||||
| 				InstanceName string `json:"instance_name"` | ||||
| @ -291,6 +270,7 @@ func setupAPI(opts options) { | ||||
| 			}) | ||||
| 		})) | ||||
| 
 | ||||
| 
 | ||||
| 			http.HandleFunc("/user_register", handler(opts, func(w http.ResponseWriter, req *http.Request) { | ||||
| 				if req.Method != "POST" { | ||||
| 					badMethod(w) | ||||
| @ -571,7 +551,9 @@ func setupAPI(opts options) { | ||||
| 				} | ||||
| 
 | ||||
| 				writeResponse(w, BBJResponse{Data: t}) | ||||
| 
 | ||||
| 			})) | ||||
| 	*/ | ||||
| } | ||||
| 
 | ||||
| type Thread struct { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user