package jsondb import ( "encoding/json" "errors" "fmt" "os" "path" "path/filepath" "strings" "git.0x0001f346.de/andreas/utils" ) type Cluster map[string]Database type Database map[string]DatabaseValue type DatabaseValue interface{} var cluster Cluster = Cluster{} var locationOfCluster string = utils.GetPathToParrentFolderOfThisExecutable() var nameOfCluster string = "jsondb" const databaseExtension string = ".json" // CreateDatabase creates a database or returns nil if it already exists func CreateDatabase(name string) error { _, databaseAlreadyExists := cluster[name] if databaseAlreadyExists { return nil } err := safeDatabase(name, Database{}) if err != nil { return err } return nil } // DeleteDatabase deletes a database func DeleteDatabase(nameOfDatabase string) error { err := os.Remove(getFullPathToDatabase(nameOfDatabase)) if err != nil { return err } delete(cluster, nameOfDatabase) return nil } // DeleteKeyFromDatabase deletes a key in a database and write it to file func DeleteKeyFromDatabase(nameOfDatabase string, key string) error { err := ReloadDatabase(nameOfDatabase) if err != nil { return err } database, err := getDeepCopyOfDatabase(nameOfDatabase) if err != nil { return err } delete(database, key) err = safeDatabase(nameOfDatabase, database) if err != nil { return err } cluster[nameOfDatabase] = database return nil } // GetDatabase returns a database if it exists func GetDatabase(name string) (Database, error) { database, err := getDeepCopyOfDatabase(name) if err != nil { return nil, err } return database, nil } // GetKeyFromDatabase returns a key from a database if both exist func GetKeyFromDatabase(nameOfDatabase string, key string) (DatabaseValue, error) { database, databaseExists := cluster[nameOfDatabase] if !databaseExists { return nil, errors.New("database does not exist") } value, keyExists := database[key] if !keyExists { return nil, errors.New("key does not exist") } return value, nil } // GetLocationOfCluster returns locationOfCluster func GetLocationOfCluster() string { return locationOfCluster } // GetNameOfCluster returns nameOfCluster func GetNameOfCluster() string { return nameOfCluster } // LoadCluster loads all databases from the current cluster func LoadCluster() error { if !utils.DoesFolderExist(getFullPathToCluster()) { err := createCluster() if err != nil { return err } } for _, p := range utils.GetAllFilesInFolderByExtension(getFullPathToCluster(), databaseExtension) { databaseName := strings.TrimSuffix(path.Base(p), filepath.Ext(path.Base(p))) if !isValidName(databaseName) { continue } database, err := loadDatabase(databaseName) if err != nil { return err } cluster[databaseName] = database } return nil } // ReloadDatabase reloads a database from file func ReloadDatabase(name string) error { _, databaseExists := cluster[name] if !databaseExists { return errors.New("database does not exist") } database, err := loadDatabase(name) if err != nil { return err } cluster[name] = database return nil } // RenameDatabase renames a database func RenameDatabase(nameOfDatabase string, newNameOfDatabase string) error { err := ReloadDatabase(nameOfDatabase) if err != nil { return err } database, err := getDeepCopyOfDatabase(nameOfDatabase) if err != nil { return err } err = safeDatabase(newNameOfDatabase, database) if err != nil { return err } cluster[newNameOfDatabase] = database err = DeleteDatabase(nameOfDatabase) if err != nil { return err } return nil } // RenameKeyInDatabase renames a key in a database and write it to file func RenameKeyInDatabase(nameOfDatabase string, keyOld string, keyNew string) error { err := ReloadDatabase(nameOfDatabase) if err != nil { return err } database, err := getDeepCopyOfDatabase(nameOfDatabase) if err != nil { return err } database[keyNew] = database[keyOld] delete(database, keyOld) err = safeDatabase(nameOfDatabase, database) if err != nil { return err } cluster[nameOfDatabase] = database return nil } // SetDatabase sets a database and writes it to file func SetDatabase(name string, database Database) error { _, databaseExists := cluster[name] if !databaseExists { return errors.New("database does not exist") } err := safeDatabase(name, database) if err != nil { return err } cluster[name] = database return nil } // SetKeyInDatabase sets a key from in a database and write it to file func SetKeyInDatabase(nameOfDatabase string, key string, value DatabaseValue) error { err := ReloadDatabase(nameOfDatabase) if err != nil { return err } database, err := getDeepCopyOfDatabase(nameOfDatabase) if err != nil { return err } database[key] = value err = safeDatabase(nameOfDatabase, database) if err != nil { return err } cluster[nameOfDatabase] = database return nil } // SetLocationOfCluster sets locationOfCluster if p is a valid folder func SetLocationOfCluster(location string) error { if !utils.DoesFolderExist(location) { return errors.New("not a valid cluster location") } locationOfCluster = location return nil } // SetNameOfCluster sets nameOfCluster if s is a valid cluster name func SetNameOfCluster(name string) error { if !isValidName(name) { return errors.New("not a valid cluster name") } nameOfCluster = name return nil } func getFullPathToCluster() string { return filepath.Join( locationOfCluster, nameOfCluster, ) } func getFullPathToDatabase(name string) string { return filepath.Join( getFullPathToCluster(), fmt.Sprintf("%s%s", name, databaseExtension), ) } func isValidName(s string) bool { if len(s) == 0 { return false } whitelist := utils.BuildWhitelistForStringSanitisation( []string{ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", }, ) return !utils.DoesStringContainsNonWhitelistedSubstrings(s, whitelist) } func createCluster() error { if utils.DoesFolderExist(getFullPathToCluster()) { return nil } err := utils.CreateFolder(getFullPathToCluster()) if err != nil { return err } return nil } func loadDatabase(name string) (Database, error) { dbContentAsString, err := utils.LoadStringFromFile( getFullPathToDatabase(name), ) if err != nil { return Database{}, err } var database Database err = json.Unmarshal([]byte(dbContentAsString), &database) if err != nil { return Database{}, err } return database, nil } func safeDatabase(name string, database Database) error { databaseAsJSONBytes, err := json.MarshalIndent(database, "", "\t") if err != nil { return errors.New("could not marshal database: " + name) } err = utils.WriteStringToFile(getFullPathToDatabase(name), string(databaseAsJSONBytes)) if err != nil { return errors.New("could not write to file: " + getFullPathToDatabase(name)) } return nil } func getDeepCopyOfDatabase(name string) (Database, error) { originalDatabase, databaseExists := cluster[name] if !databaseExists { return nil, errors.New("database does not exist") } copyOfDatabase := make(Database) for key, value := range originalDatabase { copyOfDatabase[key] = value } return copyOfDatabase, nil }