Skip to content

Commit

Permalink
add delete endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
matronator committed Apr 25, 2024
1 parent 21fdde7 commit 133fb3b
Show file tree
Hide file tree
Showing 12 changed files with 126 additions and 33 deletions.
6 changes: 6 additions & 0 deletions .amockrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"host": "localhost",
"port": 8000,
"dir": "examples",
"initCount": 2
}
1 change: 1 addition & 0 deletions .github/workflows/releaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ jobs:
run: |
git config --global user.email "${{ secrets.EMAIL }}"
git config --global user.name "Matronator"
cp README.md ./npm/README.md
cd ./npm && npm version from-git --no-git-tag-version && npm publish
git fetch && git checkout main && git pull && git add . && git commit -am "Bump npm version" && git push origin --all
env:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ node_modules
go.work

# amock files
+.amock.json
.amockrc
.amock/

dist/
Expand Down
1 change: 1 addition & 0 deletions .idea/amock.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 28 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ Amock is a simple API mock server that uses JSON files to define entities from w
* [API Mock Server](#api-mock-server)
* [Instalation](#instalation)
* [npm (macOS, Linux, Windows)](#npm-macos-linux-windows)
* [GoBinaries (macOS, Linux)](#gobinaries-macos-linux)
* [Homebrew (macOS)](#homebrew-macos)
* [Manually download from releases (macOS, Linux, Windows)](#manually-download-from-releases-macos-linux-windows)
* [1. Move the binary to `/usr/local/bin` or some other folder in your PATH:](#1-move-the-binary-to-usrlocalbin-or-some-other-folder-in-your-path)
Expand Down Expand Up @@ -46,12 +45,6 @@ Amock is a simple API mock server that uses JSON files to define entities from w
npm install -g amock-cli
```

### GoBinaries (macOS, Linux)

```bash
curl -sf https://gobinaries.com/matronator/amock | sh
```

### Homebrew (macOS)

```bash
Expand Down Expand Up @@ -158,9 +151,23 @@ if( $PATH -notlike "*"+$amock_path+"*" ){

## Usage

After installing you can simply start the server by running:

```bash
amock
```

You can also optionally specify the host you want to use for the server by supplying it as the first argument like this:

```bash
amock localhost:1234
```

This will overwrite the host and port set in your config file and start the server on `localhost:1234`.

### Configuration

First you need to create a config file. The config file is a JSON/YAML/TOML file that defines the entities that the server will mock. Valid config file names are these in order of priority (the first one found will be used):
You need to create a config file for the server to be of any use. The config file is a JSON/YAML/TOML file that defines the entities that the server will mock and some other settings. Valid config file names are these in order of priority (the first one found will be used):

```json
[".amock.json", ".amockrc", ".amock.json.json", ".amock.json.yml",
Expand All @@ -179,7 +186,7 @@ Here is an example of a config file:
"user.json", // relative path to the entity file
"post.json"
],
"dir": "path/to/entities/folder", // default is empty
"dir": "relative/path/to/entities/dir", // default is empty
"initCount": 20 // default is 20 - number of entities to generate on server start
}
```
Expand All @@ -194,7 +201,7 @@ AMOCK_ENTITIES='[user.json, post.json]' # default is empty
AMOCK_INIT_COUNT=20
```

You must set either `entities` where you list individual files or `dir` where you specify a directory containing the entity files and all files in that directory will be used.
You must set either `entities` where you list individual files or `dir` where you specify a directory containing the entity files and all valid files in that directory will be used.

You can set both but files from `entities` will override files with the same name found in the `dir` directory. Both `entities` and `dir` are optional but at least one must be set and paths in both are relative to the config file.

Expand Down Expand Up @@ -255,13 +262,18 @@ This would generate an entity object `user` looking like this:

When defining your entity you can use the following types and the server will generate the data for you. Data is generated once on server start if not already in store and whenever a new entity is created without specifying the property (if it's not required).

The syntax for defining a property is `"<name>": "<type>.<subtype?>:<options>"` where `name` is the name of the property, `type` is the type of the property followed by a dot and an optional `subtype` and optionally a colon followed by `options` for the type.
The syntax for defining a property is

`"<name>": "<type>.<subtype?>:<options>"`

where `name` is the name of the property, `type` is the type of the property followed by a dot and an optional `subtype` and optionally a colon followed by `options` for the type.

##### Required and nullable properties

If the property name ends with `!` it is required and must be present in the request. If it ends with `?` it is nullable meaning that you can send a `null` value for it in your request.

You can check some examples of how to define entities in the [examples](/examples) folder.
> [!TIP]
> You can check some examples of how to define entities in the [examples](/examples) folder.
##### Types

Expand Down Expand Up @@ -309,7 +321,8 @@ You can use the following types and subtypes to define your properties:
"range": Random float in Range,
},
"date": {
"": Date, // no subtype
"": Date, // no subtype, but you can specify the format
[string: Date format (e.g. yyyy-MM-dd)],
"timestamp": Timestamp,
"day": Day,
"month": Month,
Expand All @@ -318,8 +331,8 @@ You can use the following types and subtypes to define your properties:
"future": Future,
"past": Past,
},
"bool": True or false,
"enum": Pick a random item from the list provided in options,
"bool": true or false,
"enum": Pick a random item from the list provided in options, separated by commas,
"id": {
"": Sequential ID, // no subtype
"sequence": Sequential ID,
Expand Down
29 changes: 27 additions & 2 deletions database.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,16 +239,41 @@ func WriteTable(table *Table, collection EntityCollection) error {
return err
}

func AppendTable(table *Table, entity Entity) error {
func AppendTable(table *Table, entity *Entity) error {
collection, err := ReadTable(table)

if err != nil {
return err
}

collection = append(collection, entity)
collection = append(collection, *entity)

err = WriteTable(table, collection)

return err
}

func RemoveById(table *Table, id string) error {
collection, err := ReadTable(table)

Debug("Removing entity", "id", id, "table", table.Name)

if err != nil {
return err
}

for i, entity := range collection {
if entity["id"] == id {
collection = append(collection[:i], collection[i+1:]...)
break
}
}

err = WriteTable(table, collection)

if err != nil {
return err
}

return nil
}
9 changes: 0 additions & 9 deletions examples/.amock.json

This file was deleted.

3 changes: 1 addition & 2 deletions examples/user.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,5 @@
"is_active": "bool",
"token": "string",
"created_at": "date.timestamp",
"updated_at": "date.timestamp",
"posts[]": "post.json"
"updated_at": "date.timestamp"
}
19 changes: 17 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ import (
"path"
"strconv"
"strings"
"text/tabwriter"

"github.com/ilyakaznacheev/cleanenv"
"github.com/jwalton/gchalk"
)

var ConfigPaths = []string{
".amock.json",
".amockrc",
".amockrc.json",
".amock.json.json",
".amock.json.yml",
".amock.json.yaml",
Expand Down Expand Up @@ -52,6 +53,8 @@ func init() {

config, _ = parseConfigFiles(ConfigPaths...)

Debug("Configuration loaded", "config", config)

if config == nil {
log.Fatal("No configuration file found")
}
Expand Down Expand Up @@ -118,9 +121,20 @@ func StartServer() {
fmt.Println(gchalk.Bold("Starting server at " + url))
fmt.Println("\nAvailable routes:")

writer := tabwriter.NewWriter(os.Stdout, 0, 8, 2, '\t', tabwriter.AlignRight)
router := InitHandlers(config, &db)

Debug("Initializing routes...")
Debug("Routes", "routes", Routes)

fmt.Println("-----------------------------------------------")

for _, route := range Routes {
fmt.Println(" " + gchalk.Bold(RequestMethodColor(route.Method, false)) + "\t" + url + route.Path + "\t" + gchalk.Dim("[entity: "+gchalk.WithItalic().Bold(strings.Split(route.Path, "/")[1])+"]"))
_, err := fmt.Fprintln(writer, gchalk.Bold(RequestMethodColor(route.Method, false))+"\t"+url+route.Path+"\t"+gchalk.Dim("[entity: "+gchalk.WithItalic().Bold(strings.Split(route.Path, "/")[1])+"]"))
writer.Flush()
if err != nil {
Error("Error writing to tabwriter", "error", err)
}
}
fmt.Println("")

Expand Down Expand Up @@ -173,6 +187,7 @@ func buildTablesFromConfig() {
for _, entry := range dir {
filename := entry.Name()
table, name := getOrCreateTable(filename, path.Join(config.Dir, filename))
Debug("Table "+gchalk.Bold(name)+" created from file "+gchalk.Bold(filename), "table", name, "file", filename)
db.Tables[name] = *table
}
}
Expand Down
6 changes: 6 additions & 0 deletions post.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"id": "id.uuid",
"user_id": "id.sequence",
"title!": "string.word",
"content": "string.paragraph"
}
20 changes: 18 additions & 2 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,22 @@ func InitHandlers(config *Config, db *Database) *httprouter.Router {
Debug("PUT request received", "table", table.Name)
handlePost(w, r, &table)
})

Routes = append(Routes, Route{"DELETE", "/" + table.Name + "/:id"})
router.DELETE("/"+table.Name+"/:id", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
Debug("DELETE request received", "table", table.Name)

err := RemoveById(&table, ps.ByName("id"))

if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")

_, _ = w.Write([]byte(`{"message": "Entity removed"}`))
})
}

Debug("Handlers initialized")
Expand Down Expand Up @@ -100,7 +116,7 @@ func handlePost(w http.ResponseWriter, r *http.Request, table *Table) {
return
}

err = AppendTable(newTable, *newEntity)
err = AppendTable(newTable, newEntity)

if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
Expand Down Expand Up @@ -133,7 +149,7 @@ func handlePost(w http.ResponseWriter, r *http.Request, table *Table) {
backup, _ := ReadTable(newTable)

for _, entity := range collection {
err = AppendTable(newTable, entity)
err = AppendTable(newTable, &entity)

if err != nil {
_ = WriteTable(newTable, backup)
Expand Down
20 changes: 20 additions & 0 deletions user.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"id": "id.sequence",
"name!": "string.firstname",
"surname!": "string.lastname",
"age": "number.int:18-64",
"birthday": "date:yyyy-MM-dd",
"money": "number.decimal:2,0-10000",
"email": "string.email",
"password": "string.password",
"username": "string.username",
"city?": "string.city",
"street?": "string.street",
"country?": "string.country:short",
"role": "enum:admin,user,moderator",
"is_active": "bool",
"token": "string",
"created_at": "date.timestamp",
"updated_at": "date.timestamp",
"posts[]": "post.json"
}

0 comments on commit 133fb3b

Please sign in to comment.