Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add code from previous repo #1

Merged
merged 5 commits into from
Dec 27, 2024
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
23 changes: 23 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Test build

on:
pull_request:
branches:
- main

jobs:
main:
name: Build and run
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Run tests
run: |
go test

- name: Check if binary builds
run: |
go build .

2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
BSD 2-Clause License

Copyright (c) 2024, Go Phings
Copyright (c) 2023-2024, Mikolaj Gasior <[email protected]>

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Expand Down
195 changes: 194 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,195 @@
# crud
Create CRUD endpoints and store in the database with ease

[![Go Reference](https://pkg.go.dev/badge/github.com/go-phings/crud.svg)](https://pkg.go.dev/github.com/go-phings/crud) [![Go Report Card](https://goreportcard.com/badge/github.com/go-phings/crud)](https://goreportcard.com/report/github.com/go-phings/crud)

Package `crud` is meant to create REST API HTTP endpoint for simple data management.

HTTP endpoint can be set to allow creating, updating, removing new object, along with returning its details,
or list of objects. All requests and responses are in the JSON format.

## Example usage

### TL;DR
Check code in the `*_test.go` files, starting with `main_test.go` to get examples.

### Defining structs (models)
Models are defined with structs as follows (take a closer look at the tags):

```
type User struct {
ID int `json:"user_id"`
Flags int `json:"flags"`
Name string `json:"name" crud:"req lenmin:2 lenmax:50"`
Email string `json:"email" crud:"req"`
Password string `json:"password"`
EmailActivationKey string `json:"email_activation_key" crud:""`
CreatedAt int `json:"created_at"`
CreatedByUserID int `json:"created_by_user_id"`
}

type Session struct {
ID int `json:"session_id"`
Flags int `json:"flags"`
Key string `json:"key" crud:"uniq lenmin:32 lenmax:50"`
ExpiresAt int `json:"expires_at"`
UserID int `json:"user_id" crud:"req"`
}

type Something struct {
ID int `json:"something_id"`
Flags int `json:"flags"`
Email string `json:"email" crud:"req"`
Age int `json:"age" crud:"req valmin:18 valmax:130 val:18"`
Price int `json:"price" crud:"req valmin:0 valmax:9900 val:100"`
CurrencyRate int `json:"currency_rate" crud:"req valmin:40000 valmax:61234 val:10000"`
PostCode string `json:"post_code" crud:"req val:32-600"`
}
```


#### Field tags
Struct tags define ORM behaviour. `crud` parses tags such as `crud`, `http` and various tags starting with
`crud_`. Apart from the last one, a tag define many properties which are separated with space char, and if they
contain a value other than bool (true, false), it is added after semicolon char.
See below list of all the tags with examples.

Tag | Example | Explanation
--- | --- | ---
`crud` | `crud:"req valmin:0 valmax:130 val:18"` | Struct field properties defining its valid value for model. See RESTAPI Field Properties for more info
`crud_val` | `crud_val:"Default value"` | Struct field default value
`crud_regexp` | `crud_regexp:"^[0-9]{2}\\-[0-9]{3}$"` | Regular expression that struct field must match
`crud_testvalpattern` | `crud_testvalpattern:DD-DDD` | Very simple pattern for generating valid test value (used for tests). In the string, `D` is replaced with a digit


##### CRUD Field Properties
Property | Explanation
--- | ---
`req` | Field is required
`uniq` | Field has to be unique (like `UNIQUE` on the database column)
`valmin` | If field is numeric, this is minimal value for the field
`valmax` | If field is numeric, this is maximal value for the field
`val` | Default value for the field. If the value is not a simple, short alphanumeric, use the `crud_val` tag for it
`lenmin` | If field is string, this is a minimal length of the field value
`lenmax` | If field is string, this is a maximal length of the field value
`password` | Field is a password, and a function that generates it can be attached (see `ControllerConfig`)
`hidden` | Field's value will not be shown when listing item(s)


### Database storage
Currently, `crud` supports only PostgreSQL as a storage for objects.

#### Controller
To perform model database actions, a `Controller` object must be created. See below example that modify object(s)
in the database.

```
import (
crud "github.com/go-phings/crud"
)
```

```
// Create connection with sql
conn, _ := sql.Open("postgres", fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", dbHost, dbPort, dbUser, dbPass, dbName))
defer conn.Close()

// Create RESTAPI controller and an instance of a struct
c := crud.NewController(conn, "app1_", nil)
user := &User{}

err = c.CreateTable(user) // Run 'CREATE TABLE'
```

#### ControllerConfig

`crud.&ControllerConfig{}` can be passed to a constructor. It contains the following fields:

* `TagName` can be used to change the name of the tag in struct (by the default it is `crud`)
* `PasswordGenerator` - when a field is a `password` (see field tags above), a function can be attached, that generates the value to be put in the database

### HTTP Endpoints
With `crud`, HTTP endpoints can be created to manage objects stored in the database.

If User struct is used for HTTP endpoint, fields such as `Password` will be present when listing users. Therefore,
it's necessary to create new structs to define CRUD endpoints' input and/or output. These structs unfortunately need
validation tags (which can be different than the ones from "main" struct).

In below example, `User_Create` defines input fields when creating a User, `User_Update` defines fields that are
meant to change when permorming update, `User_UpdatePassword` is an additional struct just for updating User
password, and finally - fields in `User_List` will be visible when listing users or reading one user. (You can
define these as you like).
```
type User_Create {
ID int `json:"user_id"`
Name string `json:"name" crud:"req lenmin:2 lenmax:50"`
Email string `json:"email" crud:"req"`
Password string `json:"password"`
}
type User_Update {
ID int `json:"user_id"`
Name string `json:"name" crud:"req lenmin:2 lenmax:50"`
Email string `json:"email" crud:"req"`
}
type User_UpdatePassword {
ID int `json:"user_id"`
Password string `json:"password"`
}
type User_List {
ID int `json:"user_id"`
Name string `json:"name"
}
```

```
var parentFunc = func() interface{} { return &User; }
var createFunc = func() interface{} { return &User_Create; }
var readFunc = func() interface{} { return &User_List; }
var updateFunc = func() interface{} { return &User_Update; }
var listFunc = func() interface{} { return &User_List; }

var updatePasswordFunc = func() interface{} { return &User_UpdatePassword; }

http.HandleFunc("/users/", c.Handler("/users/", parentFunc, HandlerOptions{
CreateConstructor: createFunc, // input fields (and JSON payload) for creating
ReadConstructor: readFunc, // output fields (and JSON output) for reading
UpdateConstructor: updateFunc, // input fields (and JSON payload) for updating
ListConstructor: listFunc, // fields to appear when listing items (and JSON output)
}))
http.HandleFunc("/users/password/", c.Handler("/users/password/", parentFunc, HandlerOptions{
UpdateConstructor: updatePasswordFunc, // input fields for that one updating endpoint
Operations: OpUpdate, // only updating will be allowed
}))
log.Fatal(http.ListenAndServe(":9001", nil))
```

In the example, `/users/` CRUDL endpoint is created and it allows to:
* create new User by sending JSON payload using PUT method
* update existing User by sending JSON payload to `/users/:id` with PUT method
* get existing User details with making GET request to `/users/:id`
* delete existing User with DELETE request to `/users/:id`
* get list of Users with making GET request to `/users/` with optional query parameters such as `limit`, `offset` to slice the returned list and `filter_` params (eg. `filter_email`) to filter out records with by specific fields

When creating or updating an object, JSON payload with object details is
required. It should match the struct used for Create and Update operations.
In this case, `User_Create` and `User_Update`.

```
{
"email": "[email protected]",
"name": "Jane Doe",
...
}
```

Output from the endpoint is in JSON format as well and it follows below
structure:

```
{
"ok": 1,
"err_text": "...",
"data": {
...
}
}
```
8 changes: 8 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Package crud is meant to create REST API HTTP endpoint for simple data management.
//
// HTTP endpoint can be set to allow creating, updating, removing new object, along with returning its details,
// or list of objects. All requests and responses are in the JSON format.
//
// Please follow GitHub page for an example:
// https://github.com/go-phings/crud/
package crud
30 changes: 30 additions & 0 deletions err.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package crud

// ErrController wraps original error that occurred in Err with name of the operation/step that failed, which is
// in Op field
type ErrController struct {
Op string
Err error
}

func (e ErrController) Error() string {
return e.Err.Error()
}

func (e ErrController) Unwrap() error {
return e.Err
}

// ErrValidation wraps error occurring during object validation
type ErrValidation struct {
Fields map[string]int
Err error
}

func (e ErrValidation) Error() string {
return e.Err.Error()
}

func (e ErrValidation) Unwrap() error {
return e.Err
}
10 changes: 10 additions & 0 deletions funcs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package crud

import "reflect"

func getStructName(u interface{}) string {
v := reflect.ValueOf(u)
i := reflect.Indirect(v)
s := i.Type()
return s.Name()
}
40 changes: 40 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module github.com/go-phings/crud

go 1.23.4

require (
github.com/go-phings/struct-db-postgres v0.7.0
github.com/go-phings/struct-validator v0.4.7
github.com/lib/pq v1.10.9
github.com/ory/dockertest/v3 v3.11.0
)

require (
dario.cat/mergo v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/containerd/continuity v0.4.3 // indirect
github.com/docker/cli v26.1.4+incompatible // indirect
github.com/docker/docker v27.1.1+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/go-phings/struct-sql-postgres v0.7.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/mikolajgs/struct-validator v0.4.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opencontainers/runc v1.1.13 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
golang.org/x/sys v0.27.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
Loading