1
0
mirror of https://github.com/raseels-repos/golang-saas-starter-kit.git synced 2025-06-17 00:17:59 +02:00

Completed signup package and hooked up to web-api. Can use the swagger

ui to signup a new account.
This commit is contained in:
Lee Brown
2019-06-25 02:40:29 -08:00
parent 957bd9bf36
commit 2fbda74a73
21 changed files with 1110 additions and 167 deletions

View File

@ -1,16 +1,26 @@
FROM golang:alpine3.9 AS build_base
FROM golang:1.12.6-alpine3.9 AS build_base
LABEL maintainer="lee@geeksinthewoods.com"
RUN apk --update --no-cache add \
git
git build-base gcc
RUN go get -u github.com/swaggo/swag/cmd/swag
# Hack to get swag init to work correctly.
RUN GO111MODULE=off go get gopkg.in/go-playground/validator.v9 && \
GO111MODULE=off go get github.com/go-playground/universal-translator && \
GO111MODULE=off go get github.com/leodido/go-urn && \
GO111MODULE=off go get github.com/lib/pq/oid && \
GO111MODULE=off go get github.com/lib/pq/scram && \
GO111MODULE=off go get github.com/tinylib/msgp/msgp && \
GO111MODULE=off go get gopkg.in/DataDog/dd-trace-go.v1/ddtrace
# go to base project
# Install swag with go modules enabled.
RUN GO111MODULE=on go get -u github.com/swaggo/swag/cmd/swag
# Change dir to project base.
WORKDIR $GOPATH/src/gitlab.com/geeks-accelerator/oss/saas-starter-kit/example-project
# enable go modules
# Enable go modules.
ENV GO111MODULE="on"
COPY go.mod .
COPY go.sum .
@ -18,10 +28,10 @@ RUN go mod download
FROM build_base AS builder
# copy shared packages
# Copy shared packages.
COPY internal ./internal
# copy cmd specific package
# Copy cmd specific packages.
COPY cmd/web-api ./cmd/web-api
COPY cmd/web-api/templates /templates
#COPY cmd/web-api/static /static

View File

@ -1,6 +1,6 @@
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag at
// 2019-06-24 20:15:37.524606 -0800 AKDT m=+13.872100491
// 2019-06-25 02:19:21.144417 -0800 AKDT m=+51.040366621
package docs
@ -16,10 +16,10 @@ var doc = `{
"info": {
"description": "This is a sample server celler server.",
"title": "SaaS Example API",
"termsOfService": "/terms",
"termsOfService": "http://example.com/terms",
"contact": {
"name": "API Support",
"url": "/support",
"url": "http://example.com/support",
"email": "support@geeksinthewoods.com"
},
"license": {
@ -40,6 +40,9 @@ var doc = `{
"produces": [
"application/json"
],
"tags": [
"account"
],
"summary": "Read returns the specified account from the system.",
"operationId": "get-string-by-int",
"parameters": [
@ -88,6 +91,123 @@ var doc = `{
}
}
}
},
"/signup": {
"post": {
"description": "Signup creates a new account and user in the system.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"signup"
],
"summary": "Signup handles new account creation.",
"parameters": [
{
"description": "Signup details",
"name": "data",
"in": "body",
"required": true,
"schema": {
"type": "object",
"$ref": "#/definitions/signup.SignupRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"$ref": "#/definitions/signup.SignupResponse"
},
"headers": {
"Token": {
"type": "string",
"description": "qwerty"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"$ref": "#/definitions/web.Error"
}
},
"403": {
"description": "Forbidden",
"schema": {
"type": "object",
"$ref": "#/definitions/web.Error"
}
}
}
}
},
"/users/{id}": {
"get": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Read returns the specified user from the system.",
"operationId": "get-string-by-int",
"parameters": [
{
"type": "integer",
"description": "User ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"$ref": "#/definitions/user.User"
},
"headers": {
"Token": {
"type": "string",
"description": "qwerty"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"$ref": "#/definitions/web.Error"
}
},
"403": {
"description": "Forbidden",
"schema": {
"type": "object",
"$ref": "#/definitions/web.Error"
}
},
"404": {
"description": "Not Found",
"schema": {
"type": "object",
"$ref": "#/definitions/web.Error"
}
}
}
}
}
},
"definitions": {
@ -95,10 +215,12 @@ var doc = `{
"type": "object",
"properties": {
"address1": {
"type": "string"
"type": "string",
"example": "221 Tatitlek Ave"
},
"address2": {
"type": "string"
"type": "string",
"example": "Box #1832"
},
"archived_at": {
"type": "string"
@ -107,36 +229,166 @@ var doc = `{
"type": "string"
},
"city": {
"type": "string"
"type": "string",
"example": "Valdez"
},
"country": {
"type": "string"
"type": "string",
"example": "USA"
},
"created_at": {
"type": "string"
},
"id": {
"type": "string"
"type": "string",
"example": "c4653bf9-5978-48b7-89c5-95704aebb7e2"
},
"name": {
"type": "string"
"type": "string",
"example": "Company Name"
},
"region": {
"type": "string"
"type": "string",
"example": "AK"
},
"signup_user_id": {
"type": "string"
},
"status": {
"type": "AccountStatus"
"type": "string",
"example": "active"
},
"timezone": {
"type": "string"
"type": "string",
"example": "America/Anchorage"
},
"updated_at": {
"type": "string"
},
"zipcode": {
"type": "string",
"example": "99686"
}
}
},
"signup.SignupRequest": {
"type": "object",
"properties": {
"account": {
"type": "object",
"required": [
"name",
"address1",
"city",
"region",
"country",
"zipcode"
],
"properties": {
"address1": {
"type": "string",
"example": "221 Tatitlek Ave"
},
"address2": {
"type": "string",
"example": "Box #1832"
},
"city": {
"type": "string",
"example": "Valdez"
},
"country": {
"type": "string",
"example": "USA"
},
"name": {
"type": "string",
"example": "Company {RANDOM_UUID}"
},
"region": {
"type": "string",
"example": "AK"
},
"timezone": {
"type": "string",
"example": "America/Anchorage"
},
"zipcode": {
"type": "string",
"example": "99686"
}
}
},
"user": {
"type": "object",
"required": [
"name",
"email",
"password"
],
"properties": {
"email": {
"type": "string",
"example": "{RANDOM_EMAIL}"
},
"name": {
"type": "string",
"example": "Gabi May"
},
"password": {
"type": "string",
"example": "SecretString"
},
"password_confirm": {
"type": "string",
"example": "SecretString"
}
}
}
}
},
"signup.SignupResponse": {
"type": "object",
"properties": {
"account": {
"type": "object",
"$ref": "#/definitions/account.Account"
},
"user": {
"type": "object",
"$ref": "#/definitions/user.User"
}
}
},
"user.User": {
"type": "object",
"required": [
"name"
],
"properties": {
"archived_at": {
"type": "string"
},
"created_at": {
"type": "string"
},
"email": {
"type": "string",
"example": "gabi@geeksinthewoods.com"
},
"id": {
"type": "string",
"example": "d69bdef7-173f-4d29-b52c-3edc60baf6a2"
},
"name": {
"type": "string",
"example": "Gabi May"
},
"timezone": {
"type": "string",
"example": "America/Anchorage"
},
"updated_at": {
"type": "string"
}
}

View File

@ -3,10 +3,10 @@
"info": {
"description": "This is a sample server celler server.",
"title": "SaaS Example API",
"termsOfService": "/terms",
"termsOfService": "http://example.com/terms",
"contact": {
"name": "API Support",
"url": "/support",
"url": "http://example.com/support",
"email": "support@geeksinthewoods.com"
},
"license": {
@ -27,6 +27,9 @@
"produces": [
"application/json"
],
"tags": [
"account"
],
"summary": "Read returns the specified account from the system.",
"operationId": "get-string-by-int",
"parameters": [
@ -75,6 +78,123 @@
}
}
}
},
"/signup": {
"post": {
"description": "Signup creates a new account and user in the system.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"signup"
],
"summary": "Signup handles new account creation.",
"parameters": [
{
"description": "Signup details",
"name": "data",
"in": "body",
"required": true,
"schema": {
"type": "object",
"$ref": "#/definitions/signup.SignupRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"$ref": "#/definitions/signup.SignupResponse"
},
"headers": {
"Token": {
"type": "string",
"description": "qwerty"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"$ref": "#/definitions/web.Error"
}
},
"403": {
"description": "Forbidden",
"schema": {
"type": "object",
"$ref": "#/definitions/web.Error"
}
}
}
}
},
"/users/{id}": {
"get": {
"description": "get string by ID",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Read returns the specified user from the system.",
"operationId": "get-string-by-int",
"parameters": [
{
"type": "integer",
"description": "User ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "object",
"$ref": "#/definitions/user.User"
},
"headers": {
"Token": {
"type": "string",
"description": "qwerty"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"$ref": "#/definitions/web.Error"
}
},
"403": {
"description": "Forbidden",
"schema": {
"type": "object",
"$ref": "#/definitions/web.Error"
}
},
"404": {
"description": "Not Found",
"schema": {
"type": "object",
"$ref": "#/definitions/web.Error"
}
}
}
}
}
},
"definitions": {
@ -82,10 +202,12 @@
"type": "object",
"properties": {
"address1": {
"type": "string"
"type": "string",
"example": "221 Tatitlek Ave"
},
"address2": {
"type": "string"
"type": "string",
"example": "Box #1832"
},
"archived_at": {
"type": "string"
@ -94,36 +216,166 @@
"type": "string"
},
"city": {
"type": "string"
"type": "string",
"example": "Valdez"
},
"country": {
"type": "string"
"type": "string",
"example": "USA"
},
"created_at": {
"type": "string"
},
"id": {
"type": "string"
"type": "string",
"example": "c4653bf9-5978-48b7-89c5-95704aebb7e2"
},
"name": {
"type": "string"
"type": "string",
"example": "Company Name"
},
"region": {
"type": "string"
"type": "string",
"example": "AK"
},
"signup_user_id": {
"type": "string"
},
"status": {
"type": "AccountStatus"
"type": "string",
"example": "active"
},
"timezone": {
"type": "string"
"type": "string",
"example": "America/Anchorage"
},
"updated_at": {
"type": "string"
},
"zipcode": {
"type": "string",
"example": "99686"
}
}
},
"signup.SignupRequest": {
"type": "object",
"properties": {
"account": {
"type": "object",
"required": [
"name",
"address1",
"city",
"region",
"country",
"zipcode"
],
"properties": {
"address1": {
"type": "string",
"example": "221 Tatitlek Ave"
},
"address2": {
"type": "string",
"example": "Box #1832"
},
"city": {
"type": "string",
"example": "Valdez"
},
"country": {
"type": "string",
"example": "USA"
},
"name": {
"type": "string",
"example": "Company {RANDOM_UUID}"
},
"region": {
"type": "string",
"example": "AK"
},
"timezone": {
"type": "string",
"example": "America/Anchorage"
},
"zipcode": {
"type": "string",
"example": "99686"
}
}
},
"user": {
"type": "object",
"required": [
"name",
"email",
"password"
],
"properties": {
"email": {
"type": "string",
"example": "{RANDOM_EMAIL}"
},
"name": {
"type": "string",
"example": "Gabi May"
},
"password": {
"type": "string",
"example": "SecretString"
},
"password_confirm": {
"type": "string",
"example": "SecretString"
}
}
}
}
},
"signup.SignupResponse": {
"type": "object",
"properties": {
"account": {
"type": "object",
"$ref": "#/definitions/account.Account"
},
"user": {
"type": "object",
"$ref": "#/definitions/user.User"
}
}
},
"user.User": {
"type": "object",
"required": [
"name"
],
"properties": {
"archived_at": {
"type": "string"
},
"created_at": {
"type": "string"
},
"email": {
"type": "string",
"example": "gabi@geeksinthewoods.com"
},
"id": {
"type": "string",
"example": "d69bdef7-173f-4d29-b52c-3edc60baf6a2"
},
"name": {
"type": "string",
"example": "Gabi May"
},
"timezone": {
"type": "string",
"example": "America/Anchorage"
},
"updated_at": {
"type": "string"
}
}

View File

@ -3,36 +3,134 @@ definitions:
account.Account:
properties:
address1:
example: 221 Tatitlek Ave
type: string
address2:
example: 'Box #1832'
type: string
archived_at:
type: string
billing_user_id:
type: string
city:
example: Valdez
type: string
country:
example: USA
type: string
created_at:
type: string
id:
example: c4653bf9-5978-48b7-89c5-95704aebb7e2
type: string
name:
example: Company Name
type: string
region:
example: AK
type: string
signup_user_id:
type: string
status:
type: AccountStatus
example: active
type: string
timezone:
example: America/Anchorage
type: string
updated_at:
type: string
zipcode:
example: "99686"
type: string
type: object
signup.SignupRequest:
properties:
account:
properties:
address1:
example: 221 Tatitlek Ave
type: string
address2:
example: 'Box #1832'
type: string
city:
example: Valdez
type: string
country:
example: USA
type: string
name:
example: Company {RANDOM_UUID}
type: string
region:
example: AK
type: string
timezone:
example: America/Anchorage
type: string
zipcode:
example: "99686"
type: string
required:
- name
- address1
- city
- region
- country
- zipcode
type: object
user:
properties:
email:
example: '{RANDOM_EMAIL}'
type: string
name:
example: Gabi May
type: string
password:
example: SecretString
type: string
password_confirm:
example: SecretString
type: string
required:
- name
- email
- password
type: object
type: object
signup.SignupResponse:
properties:
account:
$ref: '#/definitions/account.Account'
type: object
user:
$ref: '#/definitions/user.User'
type: object
type: object
user.User:
properties:
archived_at:
type: string
created_at:
type: string
email:
example: gabi@geeksinthewoods.com
type: string
id:
example: d69bdef7-173f-4d29-b52c-3edc60baf6a2
type: string
name:
example: Gabi May
type: string
timezone:
example: America/Anchorage
type: string
updated_at:
type: string
required:
- name
type: object
web.Error:
properties:
err:
@ -49,12 +147,12 @@ info:
contact:
email: support@geeksinthewoods.com
name: API Support
url: /support
url: http://example.com/support
description: This is a sample server celler server.
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
termsOfService: /terms
termsOfService: http://example.com/terms
title: SaaS Example API
version: '{{.Version}}'
paths:
@ -98,6 +196,88 @@ paths:
$ref: '#/definitions/web.Error'
type: object
summary: Read returns the specified account from the system.
tags:
- account
/signup:
post:
consumes:
- application/json
description: Signup creates a new account and user in the system.
parameters:
- description: Signup details
in: body
name: data
required: true
schema:
$ref: '#/definitions/signup.SignupRequest'
type: object
produces:
- application/json
responses:
"200":
description: OK
headers:
Token:
description: qwerty
type: string
schema:
$ref: '#/definitions/signup.SignupResponse'
type: object
"400":
description: Bad Request
schema:
$ref: '#/definitions/web.Error'
type: object
"403":
description: Forbidden
schema:
$ref: '#/definitions/web.Error'
type: object
summary: Signup handles new account creation.
tags:
- signup
/users/{id}:
get:
consumes:
- application/json
description: get string by ID
operationId: get-string-by-int
parameters:
- description: User ID
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
headers:
Token:
description: qwerty
type: string
schema:
$ref: '#/definitions/user.User'
type: object
"400":
description: Bad Request
schema:
$ref: '#/definitions/web.Error'
type: object
"403":
description: Forbidden
schema:
$ref: '#/definitions/web.Error'
type: object
"404":
description: Not Found
schema:
$ref: '#/definitions/web.Error'
type: object
summary: Read returns the specified user from the system.
tags:
- user
securityDefinitions:
OAuth2Password:
flow: password

View File

@ -42,6 +42,7 @@ func (a *Account) Find(ctx context.Context, w http.ResponseWriter, r *http.Reque
// Read godoc
// @Summary Read returns the specified account from the system.
// @Description get string by ID
// @Tags account
// @ID get-string-by-int
// @Accept json
// @Produce json

View File

@ -53,6 +53,12 @@ func API(shutdown chan os.Signal, log *log.Logger, masterDB *sqlx.DB, redis *red
app.Handle("PATCH", "/v1/accounts/:id/archive", a.Archive, mid.Authenticate(authenticator), mid.HasRole(auth.RoleAdmin))
app.Handle("DELETE", "/v1/accounts/:id", a.Delete, mid.Authenticate(authenticator), mid.HasRole(auth.RoleAdmin))
// Register signup endpoints.
s := Signup{
MasterDB: masterDB,
}
app.Handle("POST", "/v1/signup", s.Signup)
// Register project.
p := Project{
MasterDB: masterDB,

View File

@ -0,0 +1,58 @@
package handlers
import (
"context"
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/account"
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/auth"
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/web"
"geeks-accelerator/oss/saas-starter-kit/example-project/internal/signup"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
"net/http"
)
// Signup represents the Signup API method handler set.
type Signup struct {
MasterDB *sqlx.DB
// ADD OTHER STATE LIKE THE LOGGER AND CONFIG HERE.
}
// Signup godoc
// @Summary Signup handles new account creation.
// @Description Signup creates a new account and user in the system.
// @Tags signup
// @Accept json
// @Produce json
// @Param data body signup.SignupRequest true "Signup details"
// @Success 200 {object} signup.SignupResponse
// @Header 200 {string} Token "qwerty"
// @Failure 400 {object} web.Error
// @Failure 403 {object} web.Error
// @Router /signup [post]
func (c *Signup) Signup(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
v, ok := ctx.Value(web.KeyValues).(*web.Values)
if !ok {
return web.NewShutdownError("web value missing from context")
}
// Claims are optional as authentication is not required ATM for this method.
claims, _ := ctx.Value(auth.Key).(auth.Claims)
var req signup.SignupRequest
if err := web.Decode(r, &req); err != nil {
return errors.Wrap(err, "")
}
res, err := signup.Signup(ctx, claims, c.MasterDB, req, v.Now)
if err != nil {
switch err {
case account.ErrForbidden:
return web.NewRequestError(err, http.StatusForbidden)
default:
return errors.Wrapf(err, "User: %+v", &req)
}
}
return web.RespondJson(ctx, w, res, http.StatusCreated)
}

View File

@ -44,7 +44,20 @@ func (u *User) Find(ctx context.Context, w http.ResponseWriter, r *http.Request,
return web.RespondJson(ctx, w, res, http.StatusOK)
}
// Read returns the specified user from the system.
// Read godoc
// @Summary Read returns the specified user from the system.
// @Description get string by ID
// @Tags user
// @ID get-string-by-int
// @Accept json
// @Produce json
// @Param id path int true "User ID"
// @Success 200 {object} user.User
// @Header 200 {string} Token "qwerty"
// @Failure 400 {object} web.Error
// @Failure 403 {object} web.Error
// @Failure 404 {object} web.Error
// @Router /users/{id} [get]
func (u *User) Read(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
claims, ok := ctx.Value(auth.Key).(auth.Claims)
if !ok {