diff --git a/Dockerfile b/Dockerfile index d29cffed..105bc34d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM hunterlong/statup:base-v0.57 +FROM hunterlong/statup:base-v0.59 MAINTAINER "Hunter Long (https://github.com/hunterlong)" # Locked version of Statup for 'latest' Docker tag diff --git a/Makefile b/Makefile index fbd628f7..07c1595e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION=0.58 +VERSION=0.59 BINARY_NAME=statup GOPATH:=$(GOPATH) GOCMD=go diff --git a/core/users.go b/core/users.go index 0f3e506f..f83fc898 100644 --- a/core/users.go +++ b/core/users.go @@ -54,7 +54,9 @@ func (u *User) Delete() error { // Update will update the user's record in database func (u *User) Update() error { - u.CreatedAt = time.Now() + u.Password = utils.HashPassword(u.Password) + u.ApiKey = utils.NewSHA1Hash(5) + u.ApiSecret = utils.NewSHA1Hash(10) return usersDB().Update(u).Error } diff --git a/dev/postman.json b/dev/postman.json new file mode 100644 index 00000000..734d071d --- /dev/null +++ b/dev/postman.json @@ -0,0 +1,455 @@ +{ + "info": { + "_postman_id": "d74ac4a3-8915-46e8-8ed2-5044ea4ce53b", + "name": "Statup", + "description": "Statup API Requests", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Main", + "item": [ + { + "name": "Statup Details", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{endpoint}}/api", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api" + ] + } + }, + "response": [] + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + } + }, + { + "name": "Services", + "item": [ + { + "name": "View All Services", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{endpoint}}/api/services", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "services" + ] + } + }, + "response": [] + }, + { + "name": "View Service", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{endpoint}}/api/services/1", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "services", + "1" + ] + } + }, + "response": [] + }, + { + "name": "Create Service", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"New Service\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 15,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0\n}" + }, + "url": { + "raw": "{{endpoint}}/api/services", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "services" + ] + } + }, + "response": [] + }, + { + "name": "Update Service", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Updated Service\",\n \"domain\": \"https://google.com\",\n \"expected\": \"\",\n \"expected_status\": 200,\n \"check_interval\": 60,\n \"type\": \"http\",\n \"method\": \"GET\",\n \"post_data\": \"\",\n \"port\": 0,\n \"timeout\": 10,\n \"order_id\": 0\n}" + }, + "url": { + "raw": "{{endpoint}}/api/services/19", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "services", + "19" + ] + } + }, + "response": [] + }, + { + "name": "Delete Service", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [], + "body": {}, + "url": { + "raw": "{{endpoint}}/api/services/1", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "services", + "1" + ] + } + }, + "response": [] + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "id": "4cd2ab82-e60d-45cd-9b74-cb4b5d893f4d", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "c7cb2b6d-289a-4073-b291-202bbec8cb44", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] + }, + { + "name": "Users", + "item": [ + { + "name": "View All Users", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{endpoint}}/api/users", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "users" + ] + } + }, + "response": [] + }, + { + "name": "View User", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [], + "body": {}, + "url": { + "raw": "{{endpoint}}/api/users/1", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "users", + "1" + ] + } + }, + "response": [] + }, + { + "name": "Create User", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"admin\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}" + }, + "url": { + "raw": "{{endpoint}}/api/users", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "users" + ] + } + }, + "response": [] + }, + { + "name": "Update User", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"adminupdated\",\n \"email\": \"info@email.com\",\n \"password\": \"password123\",\n \"admin\": true\n}" + }, + "url": { + "raw": "{{endpoint}}/api/users/4", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "users", + "4" + ] + } + }, + "response": [] + }, + { + "name": "Delete User", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [], + "body": {}, + "url": { + "raw": "{{endpoint}}/api/users/4", + "host": [ + "{{endpoint}}" + ], + "path": [ + "api", + "users", + "4" + ] + } + }, + "response": [] + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{api_key}}", + "type": "string" + } + ] + }, + "event": [ + { + "listen": "prerequest", + "script": { + "id": "9720db1a-bc4c-4e05-94ea-2782aaafb793", + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "id": "c667ae2d-41f3-4dea-ab62-3b544e2bc8f9", + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] + } + ] +} \ No newline at end of file diff --git a/dev/swagger.json b/dev/swagger.json new file mode 100644 index 00000000..2a6b2d6f --- /dev/null +++ b/dev/swagger.json @@ -0,0 +1,611 @@ +{ + "swagger": "2.0", + "info": { + "version": "1.0", + "title": "Statup", + "description": "Statup API Requests" + }, + "host": "example.com", + "basePath": "/", + "securityDefinitions": { + "auth": { + "type": "oauth2", + "flow": "implicit", + "authorizationUrl": "http://example.com", + "scopes": {} + } + }, + "schemes": [ + "http" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/api": { + "get": { + "description": "TODO: Add Description", + "summary": "Statup Details", + "tags": [ + "Main" + ], + "operationId": "ApiGet", + "produces": [ + "application/json" + ], + "parameters": [], + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "auth": [] + } + ] + } + }, + "/api/services": { + "get": { + "description": "TODO: Add Description", + "summary": "View All Services", + "tags": [ + "Services" + ], + "operationId": "ApiServicesGet", + "produces": [ + "application/json" + ], + "parameters": [], + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "auth": [] + } + ] + }, + "post": { + "description": "TODO: Add Description", + "summary": "Create Service", + "tags": [ + "Services" + ], + "operationId": "ApiServicesPost", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "Content-Type", + "in": "header", + "required": true, + "type": "string", + "description": "" + }, + { + "name": "Body", + "in": "body", + "required": true, + "description": "", + "schema": { + "$ref": "#/definitions/CreateServicerequest" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "auth": [] + } + ] + } + }, + "/api/services/1": { + "get": { + "description": "TODO: Add Description", + "summary": "View Service", + "tags": [ + "Services" + ], + "operationId": "ApiServices1Get", + "produces": [ + "application/json" + ], + "parameters": [], + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "auth": [] + } + ] + }, + "delete": { + "description": "TODO: Add Description", + "summary": "Delete Service", + "tags": [ + "Services" + ], + "operationId": "ApiServices1Delete", + "produces": [ + "application/json" + ], + "parameters": [], + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "auth": [] + } + ] + } + }, + "/api/services/19": { + "post": { + "description": "TODO: Add Description", + "summary": "Update Service", + "tags": [ + "Services" + ], + "operationId": "ApiServices19Post", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "Content-Type", + "in": "header", + "required": true, + "type": "string", + "description": "" + }, + { + "name": "Body", + "in": "body", + "required": true, + "description": "", + "schema": { + "$ref": "#/definitions/UpdateServicerequest" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "auth": [] + } + ] + } + }, + "/api/users": { + "get": { + "description": "TODO: Add Description", + "summary": "View All Users", + "tags": [ + "Users" + ], + "operationId": "ApiUsersGet", + "produces": [ + "application/json" + ], + "parameters": [], + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "auth": [] + } + ] + }, + "post": { + "description": "TODO: Add Description", + "summary": "Create User", + "tags": [ + "Users" + ], + "operationId": "ApiUsersPost", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "Content-Type", + "in": "header", + "required": true, + "type": "string", + "description": "" + }, + { + "name": "Body", + "in": "body", + "required": true, + "description": "", + "schema": { + "$ref": "#/definitions/CreateUserrequest" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "auth": [] + } + ] + } + }, + "/api/users/1": { + "get": { + "description": "TODO: Add Description", + "summary": "View User", + "tags": [ + "Users" + ], + "operationId": "ApiUsers1Get", + "produces": [ + "application/json" + ], + "parameters": [], + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "auth": [] + } + ] + } + }, + "/api/users/4": { + "post": { + "description": "TODO: Add Description", + "summary": "Update User", + "tags": [ + "Users" + ], + "operationId": "ApiUsers4Post", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "Content-Type", + "in": "header", + "required": true, + "type": "string", + "description": "" + }, + { + "name": "Body", + "in": "body", + "required": true, + "description": "", + "schema": { + "$ref": "#/definitions/UpdateUserrequest" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "auth": [] + } + ] + }, + "delete": { + "description": "TODO: Add Description", + "summary": "Delete User", + "tags": [ + "Users" + ], + "operationId": "ApiUsers4Delete", + "produces": [ + "application/json" + ], + "parameters": [], + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "auth": [] + } + ] + } + } + }, + "definitions": { + "CreateServicerequest": { + "title": "Create ServiceRequest", + "example": { + "name": "New Service", + "domain": "https://google.com", + "expected": "", + "expected_status": 200, + "check_interval": 15, + "type": "http", + "method": "GET", + "post_data": "", + "port": 0, + "timeout": 10, + "order_id": 0 + }, + "type": "object", + "properties": { + "name": { + "description": "", + "example": "New Service", + "type": "string" + }, + "domain": { + "description": "", + "example": "https://google.com", + "type": "string" + }, + "expected": { + "description": "", + "type": "string" + }, + "expected_status": { + "description": "", + "example": 200, + "type": "integer", + "format": "int32" + }, + "check_interval": { + "description": "", + "example": 15, + "type": "integer", + "format": "int32" + }, + "type": { + "description": "", + "example": "http", + "type": "string" + }, + "method": { + "description": "", + "example": "GET", + "type": "string" + }, + "post_data": { + "description": "", + "type": "string" + }, + "port": { + "description": "", + "example": 0, + "type": "integer", + "format": "int32" + }, + "timeout": { + "description": "", + "example": 10, + "type": "integer", + "format": "int32" + }, + "order_id": { + "description": "", + "example": 0, + "type": "integer", + "format": "int32" + } + }, + "required": [ + "name", + "domain", + "expected", + "expected_status", + "check_interval", + "type", + "method", + "post_data", + "port", + "timeout", + "order_id" + ] + }, + "UpdateServicerequest": { + "title": "Update ServiceRequest", + "example": { + "name": "Updated Service", + "domain": "https://google.com", + "expected": "", + "expected_status": 200, + "check_interval": 60, + "type": "http", + "method": "GET", + "post_data": "", + "port": 0, + "timeout": 10, + "order_id": 0 + }, + "type": "object", + "properties": { + "name": { + "description": "", + "example": "Updated Service", + "type": "string" + }, + "domain": { + "description": "", + "example": "https://google.com", + "type": "string" + }, + "expected": { + "description": "", + "type": "string" + }, + "expected_status": { + "description": "", + "example": 200, + "type": "integer", + "format": "int32" + }, + "check_interval": { + "description": "", + "example": 60, + "type": "integer", + "format": "int32" + }, + "type": { + "description": "", + "example": "http", + "type": "string" + }, + "method": { + "description": "", + "example": "GET", + "type": "string" + }, + "post_data": { + "description": "", + "type": "string" + }, + "port": { + "description": "", + "example": 0, + "type": "integer", + "format": "int32" + }, + "timeout": { + "description": "", + "example": 10, + "type": "integer", + "format": "int32" + }, + "order_id": { + "description": "", + "example": 0, + "type": "integer", + "format": "int32" + } + }, + "required": [ + "name", + "domain", + "expected", + "expected_status", + "check_interval", + "type", + "method", + "post_data", + "port", + "timeout", + "order_id" + ] + }, + "CreateUserrequest": { + "title": "Create UserRequest", + "example": { + "username": "admin", + "email": "info@email.com", + "password": "password123", + "admin": true + }, + "type": "object", + "properties": { + "username": { + "description": "", + "example": "admin", + "type": "string" + }, + "email": { + "description": "", + "example": "info@email.com", + "type": "string" + }, + "password": { + "description": "", + "example": "password123", + "type": "string" + }, + "admin": { + "description": "", + "example": true, + "type": "boolean" + } + }, + "required": [ + "username", + "email", + "password", + "admin" + ] + }, + "UpdateUserrequest": { + "title": "Update UserRequest", + "example": { + "username": "adminupdated", + "email": "info@email.com", + "password": "password123", + "admin": true + }, + "type": "object", + "properties": { + "username": { + "description": "", + "example": "adminupdated", + "type": "string" + }, + "email": { + "description": "", + "example": "info@email.com", + "type": "string" + }, + "password": { + "description": "", + "example": "password123", + "type": "string" + }, + "admin": { + "description": "", + "example": true, + "type": "boolean" + } + }, + "required": [ + "username", + "email", + "password", + "admin" + ] + } + } +} \ No newline at end of file diff --git a/handlers/api.go b/handlers/api.go index 3fbc1fef..0799f09b 100644 --- a/handlers/api.go +++ b/handlers/api.go @@ -26,10 +26,10 @@ import ( ) type ApiResponse struct { - Object string `json:"type"` - Method string `json:"method"` - Id int64 `json:"id"` Status string `json:"status"` + Object string `json:"type"` + Id int64 `json:"id"` + Method string `json:"method"` } func ApiIndexHandler(w http.ResponseWriter, r *http.Request) { @@ -37,7 +37,16 @@ func ApiIndexHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } - out := core.CoreApp + var out core.Core + out = *core.CoreApp + var services []types.ServiceInterface + for _, s := range out.Services { + service := s.Select() + service.Failures = nil + services = append(services, core.ReturnService(service)) + } + out.Services = services + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(out) } @@ -79,6 +88,8 @@ func ApiServiceHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } + + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(service) } @@ -100,6 +111,7 @@ func ApiCreateServiceHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(service) } @@ -117,12 +129,15 @@ func ApiServiceUpdateHandler(w http.ResponseWriter, r *http.Request) { var updatedService *types.Service decoder := json.NewDecoder(r.Body) decoder.Decode(&updatedService) + updatedService.Id = service.Id service = core.ReturnService(updatedService) err := service.Update(true) if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } + service.Check(true) + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(service) } @@ -148,6 +163,7 @@ func ApiServiceDeleteHandler(w http.ResponseWriter, r *http.Request) { Id: service.Id, Status: "success", } + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(output) } @@ -156,7 +172,14 @@ func ApiAllServicesHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return } - services := core.CoreApp.Services + allServices := core.CoreApp.Services + var services []types.ServiceInterface + for _, s := range allServices { + service := s.Select() + service.Failures = nil + services = append(services, core.ReturnService(service)) + } + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(services) } @@ -171,6 +194,7 @@ func ApiUserHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(user) } @@ -188,12 +212,14 @@ func ApiUserUpdateHandler(w http.ResponseWriter, r *http.Request) { var updateUser *types.User decoder := json.NewDecoder(r.Body) decoder.Decode(&updateUser) + updateUser.Id = user.Id user = core.ReturnUser(updateUser) err = user.Update() if err != nil { http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(user) } @@ -219,6 +245,7 @@ func ApiUserDeleteHandler(w http.ResponseWriter, r *http.Request) { Id: user.Id, Status: "success", } + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(output) } @@ -228,6 +255,7 @@ func ApiAllUsersHandler(w http.ResponseWriter, r *http.Request) { return } users, _ := core.SelectAllUsers() + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(users) } @@ -255,6 +283,7 @@ func ApiCreateUsersHandler(w http.ResponseWriter, r *http.Request) { Id: uId, Status: "success", } + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(output) } diff --git a/types/failure.go b/types/failure.go index 99630ed8..c94e38f5 100644 --- a/types/failure.go +++ b/types/failure.go @@ -8,7 +8,7 @@ type Failure struct { Id int64 `gorm:"primary_key;column:id" json:"id"` Issue string `gorm:"column:issue" json:"issue"` Method string `gorm:"column:method" json:"method,omitempty"` - Service int64 `gorm:"index;column:service" json:"service_id"` + Service int64 `gorm:"index;column:service" json:"-"` CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` FailureInterface `gorm:"-" json:"-"` } diff --git a/types/service.go b/types/service.go index 35c33ea4..af1eed2e 100644 --- a/types/service.go +++ b/types/service.go @@ -38,8 +38,6 @@ type Service struct { Latency float64 `gorm:"-" json:"latency"` Online24Hours float32 `gorm:"-" json:"24_hours_online"` AvgResponse string `gorm:"-" json:"avg_response"` - Failures []interface{} `gorm:"-" json:"failures"` - Checkins []*Checkin `gorm:"-" json:"checkins"` Running chan bool `gorm:"-" json:"-"` Checkpoint time.Time `gorm:"-" json:"-"` SleepDuration time.Duration `gorm:"-" json:"-"` @@ -47,6 +45,8 @@ type Service struct { LastStatusCode int `gorm:"-" json:"status_code"` LastOnline time.Time `gorm:"-" json:"last_online"` DnsLookup float64 `gorm:"-" json:"dns_lookup_time"` + Failures []interface{} `gorm:"-" json:"failures,omitempty"` + Checkins []*Checkin `gorm:"-" json:"checkins,omitempty"` } type ServiceInterface interface {