1
0
mirror of https://github.com/mainflux/mainflux.git synced 2025-04-24 13:48:49 +08:00

MF-488 - Remove Thing type (app or device) (#718)

* MF-488 - Remove Thing type (app or device)

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
This commit is contained in:
Manuel Imperiale 2019-04-20 14:09:11 +02:00 committed by Drasko DRASKOVIC
parent 44615c5ff0
commit b97deb50b2
40 changed files with 130 additions and 308 deletions

View File

@ -10,7 +10,7 @@
Mainflux is modern, scalable, secure open source and patent-free IoT cloud platform written in Go.
It accepts user, device, and application connections over various network protocols (i.e. HTTP,
It accepts user and thing connections over various network protocols (i.e. HTTP,
MQTT, WebSocket, CoAP), thus making a seamless bridge between them. It is used as the IoT middleware
for building complex IoT solutions.

View File

@ -29,7 +29,7 @@ Enabling and disabling Thing (adding Thing to/from whitelist) is as simple as co
Switching between states `Active` and `Inactive` enables and disables Thing, respectively.
Thing configuration also contains the so-called `external ID` and `external key`. An external ID is a unique identifier of the device/app for the corresponding Thing. For example, the device MAC address is a good choice for external ID. External key is a secret key that is used for authentication during the bootstrapping procedure.
Thing configuration also contains the so-called `external ID` and `external key`. An external ID is a unique identifier of corresponding Thing. For example, a device MAC address is a good choice for external ID. External key is a secret key that is used for authentication during the bootstrapping procedure.
## Configuration

View File

@ -16,11 +16,6 @@ import (
mfsdk "github.com/mainflux/mainflux/sdk/go"
)
const (
thingType = "device"
chanName = "channel"
)
var (
// ErrNotFound indicates a non-existent entity request.
ErrNotFound = errors.New("non-existent entity")
@ -332,7 +327,7 @@ func (bs bootstrapService) thing(key, id string) (mfsdk.Thing, error) {
var err error
if id == "" {
thingID, err = bs.sdk.CreateThing(mfsdk.Thing{Type: thingType}, key)
thingID, err = bs.sdk.CreateThing(mfsdk.Thing{}, key)
if err != nil {
return mfsdk.Thing{}, err
}

View File

@ -1,7 +1,7 @@
swagger: "2.0"
info:
title: Mainflux Bootstrap service
description: HTTP API for managing platform devices and applications configuration.
description: HTTP API for managing platform things configuration.
version: "1.0.0"
consumes:
- "application/json"
@ -56,7 +56,7 @@ paths:
- $ref: "#/parameters/Name"
responses:
200:
description: |
description: |
Data retrieved. Configs from this list don't contain channels.
schema:
$ref: "#/definitions/ConfigList"
@ -160,7 +160,7 @@ paths:
put:
summary: Updates channels the thing is connected to
description: |
Update connections performs update of the channel list corresponding
Update connections performs update of the channel list corresponding
Thing is connected to.
tags:
- configs
@ -435,4 +435,4 @@ definitions:
type: string
required:
- content
- name
- name

View File

@ -26,12 +26,12 @@ mainflux-cli users token john.doe@email.com password
### System Provisioning
#### Create Thing (type Device)
```
mainflux-cli things create '{"type":"device", "name":"myDevice"}' <user_auth_token>
mainflux-cli things create '{"name":"myDevice"}' <user_auth_token>
```
#### Create Thing (type Application)
```
mainflux-cli things create '{"type":"app", "name":"myDevice"}' <user_auth_token>
mainflux-cli things create '{"name":"myDevice"}' <user_auth_token>
```
#### Update Thing

View File

@ -22,8 +22,8 @@ import (
var errMalformedCSV = errors.New("malformed CSV")
func createThing(name, kind, token string) (mfxsdk.Thing, error) {
id, err := sdk.CreateThing(mfxsdk.Thing{Name: name, Type: kind}, token)
func createThing(name, token string) (mfxsdk.Thing, error) {
id, err := sdk.CreateThing(mfxsdk.Thing{Name: name}, token)
if err != nil {
return mfxsdk.Thing{}, err
}
@ -36,7 +36,6 @@ func createThing(name, kind, token string) (mfxsdk.Thing, error) {
m := mfxsdk.Thing{
ID: id,
Name: name,
Type: kind,
Key: t.Key,
}
@ -87,12 +86,12 @@ var cmdProvision = []cobra.Command{
return
}
if len(l) < 2 {
if len(l) < 1 {
logError(errMalformedCSV)
return
}
m, err := createThing(l[0], l[1], args[1])
m, err := createThing(l[0], args[1])
if err != nil {
logError(err)
return
@ -187,12 +186,8 @@ var cmdProvision = []cobra.Command{
// Create things
for i := 0; i < numThings; i++ {
n := fmt.Sprintf("d%d", i)
k := "device"
if i%2 != 0 {
k = "app"
}
m, err := createThing(n, k, ut)
m, err := createThing(n, ut)
if err != nil {
logError(err)
return

BIN
cmd/cli/cli Executable file

Binary file not shown.

View File

@ -26,11 +26,8 @@ an access token. Once logged into the system, user can manage his resources (i.e
things and channels) in CRUD fashion, and define access control policies by
connecting them.
`Thing` represents devices and applications connected to Mainflux. There are
two types of "things" supported at the moment: `device` and `app`. While device
is used to represent any physical device connected to the platform, app represents
any 3rd party service that uses the platform for message exchange with other
"things".
`Thing` represents devices (or applications) connected to Mainflux that uses the
platform for message exchange with other "things".
`Channel` represents a communication channel. It serves as message topic that
can be consumed by all of the things connected to it.

View File

@ -81,14 +81,9 @@ mainflux-cli users token john.doe@email.com password
```
### System Provisioning
#### Create Thing (type Device)
#### Create Thing
```
mainflux-cli things create '{"type":"device", "name":"myDevice"}' <user_auth_token>
```
#### Create Thing (type Application)
```
mainflux-cli things create '{"type":"app", "name":"myDevice"}' <user_auth_token>
mainflux-cli things create '{"name":"myThing"}' <user_auth_token>
```
#### Update Thing

View File

@ -235,14 +235,14 @@ Please assure that MQTT microservice has `node_modules` installed, as explained
> N.B. `make rundev` actually calls helper script `scripts/run.sh`, so you can inspect this script for the details.
## Events
In order to be easily integratable system, Mainflux is using [Redis Streams](https://redis.io/topics/streams-intro)
as an event log for event sourcing. Services that are publishing events to Redis Streams
In order to be easily integratable system, Mainflux is using [Redis Streams](https://redis.io/topics/streams-intro)
as an event log for event sourcing. Services that are publishing events to Redis Streams
are `things` service, `bootstrap` service, and `mqtt` adapter.
### Things Service
For every operation that has side effects (that is changing service state) `things`
service will generate new event and publish it to Redis Stream called `mainflux.things`.
Every event has its own event ID that is automatically generated and `operation`
For every operation that has side effects (that is changing service state) `things`
service will generate new event and publish it to Redis Stream called `mainflux.things`.
Every event has its own event ID that is automatically generated and `operation`
field that can have one of the following values:
- `thing.create` for thing creation,
- `thing.update` for thing update,
@ -253,34 +253,32 @@ field that can have one of the following values:
- `channel.update` for channel update,
- `channel.remove` for channel removal.
By fetching and processing these events you can reconstruct `things` service state.
If you store some of your custom data in `metadata` field, this is the perfect
way to fetch it and process it. If you want to integrate through
By fetching and processing these events you can reconstruct `things` service state.
If you store some of your custom data in `metadata` field, this is the perfect
way to fetch it and process it. If you want to integrate through
[docker-compose.yml](https://github.com/mainflux/mainflux/blob/master/docker/docker-compose.yml)
you can use `mainflux-es-redis` service. Just connect to it and consume events
you can use `mainflux-es-redis` service. Just connect to it and consume events
from Redis Stream named `mainflux.things`.
#### Thing create event
Whenever thing is created, `things` service will generate new `create` event. This
Whenever thing is created, `things` service will generate new `create` event. This
event will have the following format:
```
1) "1555334740911-0"
2) 1) "type"
2) "device"
3) "operation"
4) "thing.create"
5) "name"
6) "d0"
7) "id"
8) "3c36273a-94ea-4802-84d6-a51de140112e"
9) "owner"
10) "john.doe@email.com"
11) "metadata"
12) "{}"
2) 1) "operation"
2) "thing.create"
3) "name"
4) "d0"
5) "id"
6) "3c36273a-94ea-4802-84d6-a51de140112e"
7) "owner"
8) "john.doe@email.com"
9) "metadata"
10) "{}"
```
As you can see from this example, every odd field represents field name while every
As you can see from this example, every odd field represents field name while every
even field represents field value. This is standard event format for Redis Streams.
If you want to extract `metadata` field from this event, you'll have to read it as
string first, and then you can deserialize it to some structured format.
@ -296,8 +294,6 @@ This event will have the following format:
4) "weio"
5) "id"
6) "3c36273a-94ea-4802-84d6-a51de140112e"
7) "type"
8) "device"
```
Note that thing update event will contain only those fields that were updated using
update endpoint.
@ -340,7 +336,7 @@ Whenever channel instance is updated, `things` service will generate and publish
5) "operation"
6) "channel.update"
```
Note that update channel event will contain only those fields that were updated using
Note that update channel event will contain only those fields that were updated using
update channel endpoint.
#### Channel remove event
@ -381,8 +377,8 @@ format:
6) "thing.disconnect"
```
> **Note:** Every one of these events will omit fields that were not used or are not
relevant for specific operation. Also, field ordering is not guaranteed, so DO NOT
> **Note:** Every one of these events will omit fields that were not used or are not
relevant for specific operation. Also, field ordering is not guaranteed, so DO NOT
rely on it.
### Bootstrap Service
@ -396,9 +392,9 @@ the following event types:
- `thing.state_change` for device state change,
- `thing.update_connections` for device connection update.
If you want to integrate through
If you want to integrate through
[docker-compose.yml](https://github.com/mainflux/mainflux/blob/master/docker/addons/bootstrap/docker-compose.yml)
you can use `mainflux-es-redis` service. Just connect to it and consume events
you can use `mainflux-es-redis` service. Just connect to it and consume events
from Redis Stream named `mainflux.bootstrap`.
#### Configuration create event
@ -510,9 +506,9 @@ Events that are coming from MQTT adapter have following fields:
- `event_type` can have two possible values, connect and disconnect,
- `instance` represents MQTT adapter instance.
If you want to integrate through
If you want to integrate through
[docker-compose.yml](https://github.com/mainflux/mainflux/blob/master/docker/docker-compose.yml)
you can use `mainflux-es-redis` service. Just connect to it and consume events
you can use `mainflux-es-redis` service. Just connect to it and consume events
from Redis Stream named `mainflux.mqtt`.
Example of connect event:
@ -526,7 +522,6 @@ Example of connect event:
6) "connect"
7) "instance"
8) "mqtt-adapter-1"
```
Example of disconnect event:

View File

@ -50,13 +50,11 @@ Output of the command is something like this:
"id": "513d02d2-16c1-4f23-98be-9e12f8fee898",
"key": "69590b3a-9d76-4baa-adae-9b5fec0ea14f",
"name": "d0",
"type": "device"
},
{
"id": "bf78ca98-2fef-4cfc-9f26-e02da5ecdf67",
"key": "840c1ea1-2e8d-4809-a6d3-3433a5c489d2",
"name": "d1",
"type": "app"
}
]
@ -106,4 +104,4 @@ mainflux-http | {"level":"info","message":"Method publish took 336.685µs to c
mainflux-normalizer | {"level":"info","message":"Method normalize took 108.126µs to complete without errors.","ts":"2019-01-08T22:19:30.149500543Z"}
```
This proves that messages have been well send through the system, via protocol adapter (`mainflux-http`) and `normalizer` service which corectly parsed messages.
This proves that messages have been well send through the system, via protocol adapter (`mainflux-http`) and `normalizer` service which correctly parsed messages.

View File

@ -2,7 +2,7 @@
Mainflux is modern, scalable, secure open source and patent-free IoT cloud platform written in Go.
It accepts user, device, and application connections over various network protocols (i.e. HTTP,
It accepts user and thing connections over various network protocols (i.e. HTTP,
MQTT, WebSocket, CoAP), thus making a seamless bridge between them. It is used as the IoT middleware
for building complex IoT solutions.

View File

@ -35,15 +35,14 @@ Response should look like this:
Before proceeding, make sure that you have created a new account, and obtained
an authorization key.
### Provisioning devices
### Provisioning things
Devices are provisioned by executing request `POST /things`, with a
`"type":"device"` specified in JSON payload. Note that you will also need
`user_auth_token` in order to provision things (both devices and application)
Things are provisioned by executing request `POST /things` with a JSON payload.
Note that you will also need `user_auth_token` in order to provision things
that belong to this particular user.
```
curl -s -S -i --cacert docker/ssl/certs/mainflux-server.crt --insecure -X POST -H "Content-Type: application/json" -H "Authorization: <user_auth_token>" https://localhost/things -d '{"type":"device", "name":"weio"}'
curl -s -S -i --cacert docker/ssl/certs/mainflux-server.crt --insecure -X POST -H "Content-Type: application/json" -H "Authorization: <user_auth_token>" https://localhost/things -d '{"name":"weio"}'
```
Response will contain `Location` header whose value represents path to newly
@ -57,26 +56,6 @@ Date: Tue, 10 Apr 2018 10:02:59 GMT
Content-Length: 0
```
### Provisioning applications
Applications are provisioned by executing HTTP request `POST /things`, with
`"type":"app"` specified in JSON payload.
```
curl -s -S -i --cacert docker/ssl/certs/mainflux-server.crt --insecure -X POST -H "Content-Type: application/json" -H "Authorization: <user_auth_token>" https://localhost/things -d '{"type":"app", "name":"myapp"}'
```
Response will contain `Location` header whose value represents path to newly
created thing (same as for devices):
```
HTTP/1.1 201 Created
Content-Type: application/json
Location: /things/cb63f852-2d48-44f0-a0cf-e450496c6c92
Date: Tue, 10 Apr 2018 10:33:17 GMT
Content-Length: 0
```
### Retrieving provisioned things
In order to retrieve data of provisioned things that is written in database, you
@ -102,13 +81,11 @@ Content-Length: 1105
"things": [
{
"id": "81380742-7116-4f6f-9800-14fe464f6773",
"type": "device",
"name": "weio",
"key": "7aa91f7a-cbea-4fed-b427-07e029577590"
},
{
"id": "cb63f852-2d48-44f0-a0cf-e450496c6c92",
"type": "app",
"name": "myapp",
"key": "cbf02d60-72f2-4180-9f82-2c957db929d1"
}
@ -237,14 +214,12 @@ Response that you'll get should look like this:
"things": [
{
"id": "3ffb3880-d1e6-4edd-acd9-4294d013f35b",
"type": "device",
"name": "d0",
"key": "b1996995-237a-4552-94b2-83ec2e92a040",
"metadata": "{}"
},
{
"id": "94d166d6-6477-43dc-93b7-5c3707dbef1e",
"type": "app",
"name": "d1",
"key": "e4588a68-6028-4740-9f12-c356796aebe8",
"metadata": "{}"
@ -281,7 +256,7 @@ Response that you'll get should look like this:
}
```
If you want to disconnect your device from the channel, send following request:
If you want to disconnect your thing from the channel, send following request:
```
curl -s -S -i --cacert docker/ssl/certs/mainflux-server.crt --insecure -X DELETE -H "Authorization: <user_auth_token>" https://localhost/channels/<channel_id>/things/<thing_id>

View File

@ -16,7 +16,7 @@ import io.gatling.http.request.builder.HttpRequestBuilder.toActionBuilder
final class CreateAndRetrieveThings extends TestCase {
override def prepareAndExecute(): SetUp = {
val token = authenticate()
val thing = """{"type":"device", "name":"weio"}"""
val thing = """{"name":"weio"}"""
val scn = scenario("create and retrieve things")
.exec(http("create thing")

View File

@ -14,7 +14,7 @@ import scalaj.http.Http
abstract class PublishMessages extends TestCase {
def makeThing(token: String): (String, String) = {
val thing = """{"type":"device", "name":"weio"}"""
val thing = """{"name":"weio"}"""
val id = Http(s"$ThingsURL/things")
.postData(thing)

View File

@ -41,7 +41,7 @@ go get github.com/mainflux/mainflux
cd $GOPATH/src/github.com/mainflux/mainflux
# compile the app; make sure to set the proper GOOS value
# compile the service; make sure to set the proper GOOS value
make normalizer
# copy binary to bin

View File

@ -9,6 +9,7 @@ package cassandra
import (
"fmt"
"github.com/gocql/gocql"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/readers"

View File

@ -1,3 +1,3 @@
c1
c2
c3
channel_1
channel_2
channel_3

1 c1 channel_1
2 c2 channel_2
3 c3 channel_3

View File

@ -1,8 +1,10 @@
d1,device
d2,device
d3,device
d4,device
d5,device
a1,app
a2,app
a3,app
thing_1
thing_2
thing_3
thing_4
thing_5
thing_6
thing_7
thing_8
thing_9
thing_10

1 d1 thing_1 device
2 d2 thing_2 device
3 d3 thing_3 device
4 d4 thing_4 device
5 d5 thing_5 device
6 a1 thing_6 app
7 a2 thing_7 app
8 a3 thing_8 app
9 thing_9
10 thing_10

View File

@ -7,7 +7,7 @@
#
###
# Provisions example user, device and channel on a clean Mainflux installation.
# Provisions example user, thing and channel on a clean Mainflux installation.
#
# Expects a running Mainflux installation.
#
@ -33,11 +33,11 @@ curl -s -S --cacert docker/ssl/certs/mainflux-server.crt --insecure -X POST -H "
JWTTOKEN=$(curl -s -S --cacert docker/ssl/certs/mainflux-server.crt --insecure -X POST -H "Content-Type: application/json" https://localhost/tokens -d '{"email":"'"$EMAIL"'", "password":"'"$PASSWORD"'"}' | grep -Po "token\":\"\K(.*)(?=\")")
printf "JWT TOKEN for user is $JWTTOKEN \n"
#provision device
printf "Provisioning device with name $DEVICE \n"
curl -s -S --cacert docker/ssl/certs/mainflux-server.crt --insecure -X POST -H "Content-Type: application/json" -H "Authorization: $JWTTOKEN" https://localhost/things -d '{"type":"device", "name":"'"$DEVICE"'"}'
#provision thing
printf "Provisioning thing with name $DEVICE \n"
curl -s -S --cacert docker/ssl/certs/mainflux-server.crt --insecure -X POST -H "Content-Type: application/json" -H "Authorization: $JWTTOKEN" https://localhost/things -d '{"name":"'"$DEVICE"'"}'
#get device token
#get thing token
DEVICETOKEN=$(curl -s -S --cacert docker/ssl/certs/mainflux-server.crt --insecure -H "Authorization: $JWTTOKEN" https://localhost/things/1 | grep -Po "key\":\"\K(.*)(?=\")")
printf "Device token is $DEVICETOKEN \n"
@ -45,6 +45,6 @@ printf "Device token is $DEVICETOKEN \n"
printf "Provisioning channel with name $CHANNEL \n"
curl -s -S --cacert docker/ssl/certs/mainflux-server.crt --insecure -X POST -H "Content-Type: application/json" -H "Authorization: $JWTTOKEN" https://localhost/channels -d '{"name":"'"$CHANNEL"'"}'
#connect device to channel
printf "Connecting device to channel \n"
#connect thing to channel
printf "Connecting thing to channel \n"
curl -s -S --cacert docker/ssl/certs/mainflux-server.crt --insecure -X PUT -H "Authorization: $JWTTOKEN" https://localhost/channels/1/things/1

View File

@ -244,7 +244,7 @@ func TestChannelsByThing(t *testing.T) {
var channels []sdk.Channel
mainfluxSDK := sdk.NewSDK(sdkConf)
th := sdk.Thing{Type: "device", Name: "test_device"}
th := sdk.Thing{Name: "test_device"}
tid, err := mainfluxSDK.CreateThing(th, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

View File

@ -84,7 +84,6 @@ type User struct {
// Thing represents mainflux thing.
type Thing struct {
ID string `json:"id,omitempty"`
Type string `json:"type"`
Name string `json:"name,omitempty"`
Key string `json:"key,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`

View File

@ -36,7 +36,7 @@ const (
var (
metadata = map[string]interface{}{"meta": "data"}
metadata2 = map[string]interface{}{"meta": "data2"}
thing = sdk.Thing{ID: "1", Type: "device", Name: "test_device", Metadata: metadata}
thing = sdk.Thing{ID: "1", Name: "test_device", Metadata: metadata}
emptyThing = sdk.Thing{}
)
@ -87,6 +87,13 @@ func TestCreateThing(t *testing.T) {
err: nil,
location: "1",
},
{
desc: "create new empty thing",
thing: emptyThing,
token: token,
err: nil,
location: "2",
},
{
desc: "create new thing with empty token",
thing: thing,
@ -101,13 +108,6 @@ func TestCreateThing(t *testing.T) {
err: sdk.ErrUnauthorized,
location: "",
},
{
desc: "create new epmty thing",
thing: emptyThing,
token: token,
err: sdk.ErrInvalidArgs,
location: "",
},
}
for _, tc := range cases {
loc, err := mainfluxSDK.CreateThing(tc.thing, tc.token)
@ -191,7 +191,7 @@ func TestThings(t *testing.T) {
mainfluxSDK := sdk.NewSDK(sdkConf)
for i := 1; i < 101; i++ {
th := sdk.Thing{ID: strconv.Itoa(i), Type: "device", Name: "test_device", Metadata: metadata}
th := sdk.Thing{ID: strconv.Itoa(i), Name: "test_device", Metadata: metadata}
mainfluxSDK.CreateThing(th, token)
th.Key = fmt.Sprintf("%s%012d", keyPrefix, 2*i)
things = append(things, th)
@ -290,7 +290,7 @@ func TestThingsByChannel(t *testing.T) {
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
for i := 1; i < 101; i++ {
th := sdk.Thing{Type: "device", Name: "test_device", Metadata: metadata}
th := sdk.Thing{Name: "test_device", Metadata: metadata}
tid, err := mainfluxSDK.CreateThing(th, token)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
th.ID = tid
@ -408,7 +408,6 @@ func TestUpdateThing(t *testing.T) {
desc: "update existing thing",
thing: sdk.Thing{
ID: id,
Type: "app",
Name: "test_app",
Metadata: metadata2,
},
@ -419,7 +418,6 @@ func TestUpdateThing(t *testing.T) {
desc: "update non-existing thing",
thing: sdk.Thing{
ID: "0",
Type: "device",
Name: "test_device",
Metadata: metadata,
},
@ -430,7 +428,6 @@ func TestUpdateThing(t *testing.T) {
desc: "update channel with invalid id",
thing: sdk.Thing{
ID: "",
Type: "device",
Name: "test_device",
Metadata: metadata,
},
@ -441,7 +438,6 @@ func TestUpdateThing(t *testing.T) {
desc: "update channel with invalid token",
thing: sdk.Thing{
ID: id,
Type: "app",
Name: "test_app",
Metadata: metadata2,
},
@ -452,7 +448,6 @@ func TestUpdateThing(t *testing.T) {
desc: "update channel with empty token",
thing: sdk.Thing{
ID: id,
Type: "app",
Name: "test_app",
Metadata: metadata2,
},

View File

@ -1,10 +1,9 @@
# Things
Things service provides an HTTP API for managing platform resources: devices,
applications and channels. Through this API clients are able to do the following
actions:
Things service provides an HTTP API for managing platform resources: things and channels.
Through this API clients are able to do the following actions:
- provision new things (i.e. devices & applications)
- provision new things
- create new channels
- "connect" things into the channels

View File

@ -25,7 +25,7 @@ import (
const wrongID = ""
var (
thing = things.Thing{Type: "app", Name: "test_app", Metadata: map[string]interface{}{"test": "test"}}
thing = things.Thing{Name: "test_app", Metadata: map[string]interface{}{"test": "test"}}
channel = things.Channel{Name: "test", Metadata: map[string]interface{}{"test": "test"}}
)

View File

@ -23,7 +23,6 @@ func addThingEndpoint(svc things.Service) endpoint.Endpoint {
}
thing := things.Thing{
Type: req.Type,
Name: req.Name,
Metadata: req.Metadata,
}
@ -50,7 +49,6 @@ func updateThingEndpoint(svc things.Service) endpoint.Endpoint {
thing := things.Thing{
ID: req.id,
Type: req.Type,
Name: req.Name,
Metadata: req.Metadata,
}
@ -80,7 +78,6 @@ func viewThingEndpoint(svc things.Service) endpoint.Endpoint {
res := viewThingRes{
ID: thing.ID,
Owner: thing.Owner,
Type: thing.Type,
Name: thing.Name,
Key: thing.Key,
Metadata: thing.Metadata,
@ -114,7 +111,6 @@ func listThingsEndpoint(svc things.Service) endpoint.Endpoint {
view := viewThingRes{
ID: thing.ID,
Owner: thing.Owner,
Type: thing.Type,
Name: thing.Name,
Key: thing.Key,
Metadata: thing.Metadata,
@ -151,7 +147,6 @@ func listThingsByChannelEndpoint(svc things.Service) endpoint.Endpoint {
view := viewThingRes{
ID: thing.ID,
Owner: thing.Owner,
Type: thing.Type,
Key: thing.Key,
Name: thing.Name,
Metadata: thing.Metadata,

View File

@ -35,7 +35,7 @@ const (
)
var (
thing = things.Thing{Type: "app", Name: "test_app", Metadata: map[string]interface{}{"test": "data"}}
thing = things.Thing{Name: "test_app", Metadata: map[string]interface{}{"test": "data"}}
channel = things.Channel{Name: "test", Metadata: map[string]interface{}{"test": "data"}}
)
@ -90,7 +90,6 @@ func TestAddThing(t *testing.T) {
defer ts.Close()
data := toJSON(thing)
invalidData := toJSON(things.Thing{Type: "foo"})
cases := []struct {
desc string
@ -109,12 +108,12 @@ func TestAddThing(t *testing.T) {
location: "/things/1",
},
{
desc: "add thing with invalid data",
req: invalidData,
desc: "add thing with empty JSON request",
req: "{}",
contentType: contentType,
auth: token,
status: http.StatusBadRequest,
location: "",
status: http.StatusCreated,
location: "/things/2",
},
{
desc: "add thing with invalid auth token",
@ -140,14 +139,6 @@ func TestAddThing(t *testing.T) {
status: http.StatusBadRequest,
location: "",
},
{
desc: "add thing with empty JSON request",
req: "{}",
contentType: contentType,
auth: token,
status: http.StatusBadRequest,
location: "",
},
{
desc: "add thing with empty request",
req: "",
@ -190,7 +181,6 @@ func TestUpdateThing(t *testing.T) {
defer ts.Close()
data := toJSON(thing)
invalidData := toJSON(things.Thing{Type: "foo"})
sth, _ := svc.AddThing(token, thing)
cases := []struct {
@ -209,6 +199,14 @@ func TestUpdateThing(t *testing.T) {
auth: token,
status: http.StatusOK,
},
{
desc: "update thing with empty JSON request",
req: "{}",
id: sth.ID,
contentType: contentType,
auth: token,
status: http.StatusOK,
},
{
desc: "update non-existent thing",
req: data,
@ -217,14 +215,6 @@ func TestUpdateThing(t *testing.T) {
auth: token,
status: http.StatusNotFound,
},
{
desc: "update thing with invalid data",
req: invalidData,
id: sth.ID,
contentType: contentType,
auth: token,
status: http.StatusBadRequest,
},
{
desc: "update thing with invalid id",
req: data,
@ -257,14 +247,6 @@ func TestUpdateThing(t *testing.T) {
auth: token,
status: http.StatusBadRequest,
},
{
desc: "update thing with empty JSON request",
req: "{}",
id: sth.ID,
contentType: contentType,
auth: token,
status: http.StatusBadRequest,
},
{
desc: "update thing with empty request",
req: "",
@ -308,7 +290,6 @@ func TestViewThing(t *testing.T) {
thres := thingRes{
ID: sth.ID,
Type: sth.Type,
Name: sth.Name,
Key: sth.Key,
Metadata: sth.Metadata,
@ -387,7 +368,6 @@ func TestListThings(t *testing.T) {
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
thres := thingRes{
ID: sth.ID,
Type: sth.Type,
Name: sth.Name,
Key: sth.Key,
Metadata: sth.Metadata,
@ -535,7 +515,6 @@ func TestListThingsByChannel(t *testing.T) {
thres := thingRes{
ID: sth.ID,
Type: sth.Type,
Name: sth.Name,
Key: sth.Key,
Metadata: sth.Metadata,
@ -1540,7 +1519,6 @@ func TestDisconnnect(t *testing.T) {
type thingRes struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name,omitempty"`
Key string `json:"key"`
Metadata map[string]interface{} `json:"metadata,omitempty"`

View File

@ -29,7 +29,6 @@ func (req identityReq) validate() error {
type addThingReq struct {
key string
Type string `json:"type"`
Name string `json:"name,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
@ -39,17 +38,12 @@ func (req addThingReq) validate() error {
return things.ErrUnauthorizedAccess
}
if req.Type == "" {
return things.ErrMalformedEntity
}
return nil
}
type updateThingReq struct {
key string
id string
Type string `json:"type"`
Name string `json:"name,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
@ -59,7 +53,7 @@ func (req updateThingReq) validate() error {
return things.ErrUnauthorizedAccess
}
if req.id == "" || req.Type == "" {
if req.id == "" {
return things.ErrMalformedEntity
}

View File

@ -41,8 +41,7 @@ func TestIdentityReqValidation(t *testing.T) {
func TestAddThingReqValidation(t *testing.T) {
key := uuid.NewV4().String()
valid := things.Thing{Type: "app"}
invalid := things.Thing{ID: "0", Type: ""}
valid := things.Thing{}
cases := map[string]struct {
thing things.Thing
@ -58,18 +57,12 @@ func TestAddThingReqValidation(t *testing.T) {
thing: valid,
key: "", err: things.ErrUnauthorizedAccess,
},
"empty thing type": {
thing: invalid,
key: key,
err: things.ErrMalformedEntity,
},
}
for desc, tc := range cases {
req := addThingReq{
key: tc.key,
Name: tc.thing.Name,
Type: tc.thing.Type,
Metadata: tc.thing.Metadata,
}
@ -80,8 +73,7 @@ func TestAddThingReqValidation(t *testing.T) {
func TestUpdateThingReqValidation(t *testing.T) {
key := uuid.NewV4().String()
valid := things.Thing{ID: "1", Type: "app"}
invalid := things.Thing{ID: "0", Type: ""}
valid := things.Thing{ID: "1"}
cases := map[string]struct {
thing things.Thing
@ -101,12 +93,6 @@ func TestUpdateThingReqValidation(t *testing.T) {
key: "",
err: things.ErrUnauthorizedAccess,
},
"empty thing type": {
thing: invalid,
id: valid.ID,
key: key,
err: things.ErrMalformedEntity,
},
"empty thing id": {
thing: valid,
id: "",
@ -120,7 +106,6 @@ func TestUpdateThingReqValidation(t *testing.T) {
key: tc.key,
id: tc.id,
Name: tc.thing.Name,
Type: tc.thing.Type,
Metadata: tc.thing.Metadata,
}

View File

@ -89,7 +89,6 @@ func (res thingRes) Empty() bool {
type viewThingRes struct {
ID string `json:"id"`
Owner string `json:"-"`
Type string `json:"type"`
Name string `json:"name,omitempty"`
Key string `json:"key"`
Metadata map[string]interface{} `json:"metadata,omitempty"`

View File

@ -216,7 +216,6 @@ func TestMultiChannelRetrievalByThing(t *testing.T) {
tid, err := thingRepo.Save(things.Thing{
ID: idp.ID(),
Owner: email,
Type: "device",
})
require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
n := uint64(10)

View File

@ -55,7 +55,6 @@ func migrateDB(db *sqlx.DB) error {
`CREATE TABLE IF NOT EXISTS things (
id UUID,
owner VARCHAR(254),
type VARCHAR(10) NOT NULL,
key CHAR(36) UNIQUE NOT NULL,
name TEXT,
metadata JSON,

View File

@ -32,8 +32,8 @@ func NewThingRepository(db *sqlx.DB, log logger.Logger) things.ThingRepository {
}
func (tr thingRepository) Save(thing things.Thing) (string, error) {
q := `INSERT INTO things (id, owner, type, name, key, metadata)
VALUES (:id, :owner, :type, :name, :key, :metadata);`
q := `INSERT INTO things (id, owner, name, key, metadata)
VALUES (:id, :owner, :name, :key, :metadata);`
dbth, err := toDBThing(thing)
if err != nil {
@ -84,7 +84,7 @@ func (tr thingRepository) Update(thing things.Thing) error {
}
func (tr thingRepository) RetrieveByID(owner, id string) (things.Thing, error) {
q := `SELECT name, type, key, metadata FROM things WHERE id = $1 AND owner = $2;`
q := `SELECT name, key, metadata FROM things WHERE id = $1 AND owner = $2;`
dbth := dbThing{
ID: id,
@ -119,7 +119,7 @@ func (tr thingRepository) RetrieveByKey(key string) (string, error) {
}
func (tr thingRepository) RetrieveAll(owner string, offset, limit uint64) things.ThingsPage {
q := `SELECT id, name, type, key, metadata FROM things
q := `SELECT id, name, key, metadata FROM things
WHERE owner = :owner ORDER BY id LIMIT :limit OFFSET :offset;`
params := map[string]interface{}{
@ -173,7 +173,7 @@ func (tr thingRepository) RetrieveAll(owner string, offset, limit uint64) things
}
func (tr thingRepository) RetrieveByChannel(owner, channel string, offset, limit uint64) things.ThingsPage {
q := `SELECT id, type, name, key, metadata
q := `SELECT id, name, key, metadata
FROM things th
INNER JOIN connections co
ON th.id = co.thing_id
@ -248,7 +248,6 @@ func (tr thingRepository) Remove(owner, id string) error {
type dbThing struct {
ID string `db:"id"`
Owner string `db:"owner"`
Type string `db:"type"`
Name string `db:"name"`
Key string `db:"key"`
Metadata string `db:"metadata"`
@ -263,7 +262,6 @@ func toDBThing(th things.Thing) (dbThing, error) {
return dbThing{
ID: th.ID,
Owner: th.Owner,
Type: th.Type,
Name: th.Name,
Key: th.Key,
Metadata: string(data),
@ -279,7 +277,6 @@ func toThing(dbth dbThing) (things.Thing, error) {
return things.Thing{
ID: dbth.ID,
Owner: dbth.Owner,
Type: dbth.Type,
Name: dbth.Name,
Key: dbth.Key,
Metadata: metadata,

View File

@ -34,7 +34,6 @@ var (
type createThingEvent struct {
id string
owner string
kind string
name string
metadata map[string]interface{}
}
@ -43,7 +42,6 @@ func (cte createThingEvent) Encode() map[string]interface{} {
val := map[string]interface{}{
"id": cte.id,
"owner": cte.owner,
"type": cte.kind,
"operation": thingCreate,
}
@ -65,7 +63,6 @@ func (cte createThingEvent) Encode() map[string]interface{} {
type updateThingEvent struct {
id string
kind string
name string
metadata map[string]interface{}
}
@ -73,7 +70,6 @@ type updateThingEvent struct {
func (ute updateThingEvent) Encode() map[string]interface{} {
val := map[string]interface{}{
"id": ute.id,
"type": ute.kind,
"operation": thingUpdate,
}

View File

@ -42,7 +42,6 @@ func (es eventStore) AddThing(key string, thing things.Thing) (things.Thing, err
event := createThingEvent{
id: sth.ID,
owner: sth.Owner,
kind: sth.Type,
name: sth.Name,
metadata: sth.Metadata,
}
@ -63,7 +62,6 @@ func (es eventStore) UpdateThing(key string, thing things.Thing) error {
event := updateThingEvent{
id: thing.ID,
kind: thing.Type,
name: thing.Name,
metadata: thing.Metadata,
}

View File

@ -67,7 +67,6 @@ func TestAddThing(t *testing.T) {
{
desc: "create thing successfully",
thing: things.Thing{
Type: "app",
Name: "a",
Metadata: map[string]interface{}{"test": "test"},
},
@ -77,18 +76,10 @@ func TestAddThing(t *testing.T) {
"id": "1",
"name": "a",
"owner": email,
"type": "app",
"metadata": "{\"test\":\"test\"}",
"operation": thingCreate,
},
},
{
desc: "create invalid thing",
thing: things.Thing{Type: "a", Name: "a"},
key: token,
err: things.ErrMalformedEntity,
event: nil,
},
}
lastID := "0"
@ -118,7 +109,7 @@ func TestUpdateThing(t *testing.T) {
svc := newService(map[string]string{token: email})
// Create thing without sending event.
th := things.Thing{Type: "app", Name: "a", Metadata: map[string]interface{}{"test": "test"}}
th := things.Thing{Name: "a", Metadata: map[string]interface{}{"test": "test"}}
sth, err := svc.AddThing(token, th)
require.Nil(t, err, fmt.Sprintf("unexpected error %s", err))
@ -135,7 +126,6 @@ func TestUpdateThing(t *testing.T) {
desc: "update existing thing successfully",
thing: things.Thing{
ID: sth.ID,
Type: "app",
Name: "a",
Metadata: map[string]interface{}{"test": "test"},
},
@ -144,22 +134,10 @@ func TestUpdateThing(t *testing.T) {
event: map[string]interface{}{
"id": sth.ID,
"name": "a",
"type": "app",
"metadata": "{\"test\":\"test\"}",
"operation": thingUpdate,
},
},
{
desc: "update invalid thing",
thing: things.Thing{
ID: strconv.FormatUint(math.MaxUint64, 10),
Type: "a",
Name: "a",
},
key: token,
err: things.ErrMalformedEntity,
event: nil,
},
}
lastID := "0"
@ -189,7 +167,7 @@ func TestViewThing(t *testing.T) {
svc := newService(map[string]string{token: email})
// Create thing without sending event.
sth, err := svc.AddThing(token, things.Thing{Type: "app", Name: "a"})
sth, err := svc.AddThing(token, things.Thing{Name: "a"})
require.Nil(t, err, fmt.Sprintf("unexpected error %s", err))
essvc := redis.NewEventStoreMiddleware(svc, redisClient)
@ -204,7 +182,7 @@ func TestListThings(t *testing.T) {
svc := newService(map[string]string{token: email})
// Create thing without sending event.
_, err := svc.AddThing(token, things.Thing{Type: "app", Name: "a"})
_, err := svc.AddThing(token, things.Thing{Name: "a"})
require.Nil(t, err, fmt.Sprintf("unexpected error %s", err))
essvc := redis.NewEventStoreMiddleware(svc, redisClient)
@ -219,7 +197,7 @@ func TestListThingsByChannel(t *testing.T) {
svc := newService(map[string]string{token: email})
// Create thing without sending event.
sth, err := svc.AddThing(token, things.Thing{Type: "app", Name: "a"})
sth, err := svc.AddThing(token, things.Thing{Name: "a"})
require.Nil(t, err, fmt.Sprintf("unexpected error %s", err))
sch, err := svc.CreateChannel(token, things.Channel{Name: "a"})
require.Nil(t, err, fmt.Sprintf("unexpected error %s", err))
@ -238,7 +216,7 @@ func TestRemoveThing(t *testing.T) {
svc := newService(map[string]string{token: email})
// Create thing without sending event.
sth, err := svc.AddThing(token, things.Thing{Type: "app", Name: "a"})
sth, err := svc.AddThing(token, things.Thing{Name: "a"})
require.Nil(t, err, fmt.Sprintf("unexpected error %s", err))
svc = redis.NewEventStoreMiddleware(svc, redisClient)
@ -450,7 +428,7 @@ func TestListChannelsByThing(t *testing.T) {
svc := newService(map[string]string{token: email})
// Create thing without sending event.
sth, err := svc.AddThing(token, things.Thing{Type: "app", Name: "a"})
sth, err := svc.AddThing(token, things.Thing{Name: "a"})
require.Nil(t, err, fmt.Sprintf("unexpected error %s", err))
sch, err := svc.CreateChannel(token, things.Channel{Name: "a"})
require.Nil(t, err, fmt.Sprintf("unexpected error %s", err))
@ -527,7 +505,7 @@ func TestConnectEvent(t *testing.T) {
svc := newService(map[string]string{token: email})
// Create thing and channel that will be connected.
sth, err := svc.AddThing(token, things.Thing{Type: "device", Name: "a"})
sth, err := svc.AddThing(token, things.Thing{Name: "a"})
require.Nil(t, err, fmt.Sprintf("unexpected error %s", err))
sch, err := svc.CreateChannel(token, things.Channel{Name: "a"})
require.Nil(t, err, fmt.Sprintf("unexpected error %s", err))
@ -591,7 +569,7 @@ func TestDisconnectEvent(t *testing.T) {
svc := newService(map[string]string{token: email})
// Create thing and channel that will be connected.
sth, err := svc.AddThing(token, things.Thing{Type: "device", Name: "a"})
sth, err := svc.AddThing(token, things.Thing{Name: "a"})
require.Nil(t, err, fmt.Sprintf("unexpected error %s", err))
sch, err := svc.CreateChannel(token, things.Channel{Name: "a"})
require.Nil(t, err, fmt.Sprintf("unexpected error %s", err))

View File

@ -26,7 +26,7 @@ const (
)
var (
thing = things.Thing{Type: "app", Name: "test"}
thing = things.Thing{Name: "test"}
channel = things.Channel{Name: "test"}
)
@ -52,29 +52,17 @@ func TestAddThing(t *testing.T) {
err error
}{
{
desc: "add new app",
thing: things.Thing{Type: "app", Name: "a"},
key: token,
err: nil,
},
{
desc: "add new device",
thing: things.Thing{Type: "device", Name: "b"},
desc: "add new thing",
thing: things.Thing{Name: "a"},
key: token,
err: nil,
},
{
desc: "add thing with wrong credentials",
thing: things.Thing{Type: "app", Name: "d"},
thing: things.Thing{Name: "d"},
key: wrongValue,
err: things.ErrUnauthorizedAccess,
},
{
desc: "add thing with invalid type",
thing: things.Thing{Type: "invalid", Name: "d"},
key: wrongValue,
err: things.ErrMalformedEntity,
},
}
for _, tc := range cases {
@ -86,7 +74,7 @@ func TestAddThing(t *testing.T) {
func TestUpdateThing(t *testing.T) {
svc := newService(map[string]string{token: email})
saved, _ := svc.AddThing(token, thing)
other := things.Thing{ID: wrongID, Type: "app", Key: "x"}
other := things.Thing{ID: wrongID, Key: "x"}
cases := []struct {
desc string
@ -112,12 +100,6 @@ func TestUpdateThing(t *testing.T) {
key: token,
err: things.ErrNotFound,
},
{
desc: "update thing with invalid type",
thing: things.Thing{Type: "invalid", Name: "d"},
key: wrongValue,
err: things.ErrMalformedEntity,
},
}
for _, tc := range cases {

View File

@ -1,7 +1,7 @@
swagger: "2.0"
info:
title: Mainflux things service
description: HTTP API for managing platform devices, applications and channels.
description: HTTP API for managing platform things and channels.
version: "1.0.0"
consumes:
- "application/json"
@ -262,7 +262,7 @@ paths:
summary: Removes a channel
description: |
Removes a channel. The service will ensure that the subscribed apps and
devices are unsubscribed from the removed channel.
things are unsubscribed from the removed channel.
tags:
- channels
parameters:
@ -448,12 +448,6 @@ definitions:
id:
type: string
description: Unique thing identifier generated by the service.
type:
type: string
enum:
- app
- device
description: Type of the thing.
name:
type: string
description: Free-form thing name.
@ -470,12 +464,6 @@ definitions:
ThingReq:
type: object
properties:
type:
type: string
enum:
- app
- device
description: Type of the thing.
name:
type: string
description: Free-form thing name.

View File

@ -7,14 +7,11 @@
package things
import "strings"
// Thing represents a Mainflux thing. Each thing is owned by one user, and
// it is assigned with the unique identifier and (temporary) access key.
type Thing struct {
ID string
Owner string
Type string
Name string
Key string
Metadata map[string]interface{}
@ -27,17 +24,8 @@ type ThingsPage struct {
Things []Thing
}
var thingTypes = map[string]bool{
"app": true,
"device": true,
}
// Validate returns an error if thing representation is invalid.
func (c *Thing) Validate() error {
if c.Type = strings.ToLower(c.Type); !thingTypes[c.Type] {
return ErrMalformedEntity
}
return nil
}

View File

@ -1,6 +1,6 @@
# Users service
Users service provides an HTTP API for managing users. Through this API clients
Users service provides an HTTP API for managing users. Through this API clients
are able to do the following actions:
- register new accounts
@ -74,7 +74,7 @@ go get github.com/mainflux/mainflux
cd $GOPATH/src/github.com/mainflux/mainflux
# compile the app
# compile the service
make users
# copy binary to bin