tsk-18: Create service user (#22)
Closes #18 Reviewed-on: phoenix/textsender-auth#22 Co-authored-by: phoenix <kundeng00@pm.me> Co-committed-by: phoenix <kundeng00@pm.me>
This commit is contained in:
@@ -107,20 +107,22 @@ jobs:
|
|||||||
echo "DB_PORT=$DB_PORT" >> .env
|
echo "DB_PORT=$DB_PORT" >> .env
|
||||||
echo "DB_SSLMODE=$DB_SSLMODE" >> .env
|
echo "DB_SSLMODE=$DB_SSLMODE" >> .env
|
||||||
|
|
||||||
|
echo "Initializing config"
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender_models_deploy_key
|
||||||
|
chmod 600 ~/.ssh/textsender_models_deploy_key
|
||||||
|
ssh-keyscan ${{ vars.MY_HOST }} >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
eval $(ssh-agent -s)
|
||||||
|
ssh-add -v ~/.ssh/textsender_models_deploy_key
|
||||||
|
|
||||||
|
go env -w GOPRIVATE='${{ secrets.GIT_HOST_ROOT }}'
|
||||||
|
|
||||||
|
echo "Creating local .gitconfig"
|
||||||
|
touch ~/.gitconfig
|
||||||
|
cat > ~/.gitconfig << "EOF"
|
||||||
|
[url "ssh://git@${{ secrets.GIT_HOST_ROOT }}"]
|
||||||
|
insteadOf = https://${{ secrets.GIT_HOST_ROOT }}
|
||||||
|
EOF
|
||||||
|
|
||||||
go test -v ./...
|
go test -v ./...
|
||||||
|
|
||||||
# - name: Run gofmt (optional)
|
|
||||||
# Uncomment to check code formatting with gofmt
|
|
||||||
# run: |
|
|
||||||
# if [ -n "$(gofmt -l.)" ]; then
|
|
||||||
# echo "Go code is not formatted. Please run 'gofmt -w.' to fix it."
|
|
||||||
# exit 1
|
|
||||||
# fi
|
|
||||||
|
|
||||||
# - name: Run golint (optional)
|
|
||||||
# Uncomment to check code style with golint
|
|
||||||
# run: |
|
|
||||||
# if [ -n "$(golint./...)" ]; then
|
|
||||||
# echo "Go code has style issues. Please run 'golint./...' to see them."
|
|
||||||
# exit 1
|
|
||||||
# fi
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint"
|
"git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint"
|
||||||
mdleware "git.kundeng.us/phoenix/textsender-auth/internal/middleware"
|
mdleware "git.kundeng.us/phoenix/textsender-auth/internal/middleware"
|
||||||
"git.kundeng.us/phoenix/textsender-auth/internal/model"
|
"git.kundeng.us/phoenix/textsender-auth/internal/model"
|
||||||
|
"git.kundeng.us/phoenix/textsender-auth/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @title textsender-auth
|
// @title textsender-auth
|
||||||
@@ -73,8 +74,11 @@ func main() {
|
|||||||
|
|
||||||
// Services
|
// Services
|
||||||
userStore := model.NewUserStore(db.Pool)
|
userStore := model.NewUserStore(db.Pool)
|
||||||
|
serviceStore := store.NewServiceStore(db.Pool)
|
||||||
|
|
||||||
userHandler := handler.NewUserHandler(userStore)
|
userHandler := handler.NewUserHandler(userStore)
|
||||||
loginHandler := handler.NewLoginHandler(userStore)
|
loginHandler := handler.NewLoginHandler(userStore)
|
||||||
|
serviceHandler := handler.NewServiceHandler(serviceStore)
|
||||||
|
|
||||||
router := chi.NewRouter()
|
router := chi.NewRouter()
|
||||||
|
|
||||||
@@ -85,6 +89,7 @@ func main() {
|
|||||||
|
|
||||||
router.Method("Post", endpoint.Register, http.HandlerFunc(userHandler.Register))
|
router.Method("Post", endpoint.Register, http.HandlerFunc(userHandler.Register))
|
||||||
router.Method("Post", endpoint.Login, http.HandlerFunc(loginHandler.Login))
|
router.Method("Post", endpoint.Login, http.HandlerFunc(loginHandler.Login))
|
||||||
|
router.Method("Post", endpoint.CreateServiceUser, http.HandlerFunc(serviceHandler.Register))
|
||||||
|
|
||||||
router.Method("GET", "/swagger/*", httpSwagger.Handler(
|
router.Method("GET", "/swagger/*", httpSwagger.Handler(
|
||||||
httpSwagger.URL(fmt.Sprintf("http://localhost:%s/swagger/doc.json", config.Port)),
|
httpSwagger.URL(fmt.Sprintf("http://localhost:%s/swagger/doc.json", config.Port)),
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"git.kundeng.us/phoenix/textsender-auth/internal/handler"
|
"git.kundeng.us/phoenix/textsender-auth/internal/handler"
|
||||||
"git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint"
|
"git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint"
|
||||||
"git.kundeng.us/phoenix/textsender-auth/internal/model"
|
"git.kundeng.us/phoenix/textsender-auth/internal/model"
|
||||||
|
"git.kundeng.us/phoenix/textsender-auth/internal/store"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testRouter *mux.Router
|
var testRouter *mux.Router
|
||||||
@@ -23,27 +24,30 @@ var testRouter *mux.Router
|
|||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
cfg := load()
|
cfg := load()
|
||||||
|
|
||||||
db, err := db.NewDatabase(cfg.GetDBConnString())
|
database, err := db.NewDatabase(cfg.GetDBConnString())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err.Error())
|
fmt.Println(err.Error())
|
||||||
panic("Failed to initialize database")
|
panic("Failed to initialize database")
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer database.Close()
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
err = db.ResetDatabase(ctx)
|
err = database.ResetDatabase(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err.Error())
|
fmt.Println(err.Error())
|
||||||
panic("Failed to initialize database")
|
panic("Failed to initialize database")
|
||||||
}
|
}
|
||||||
|
|
||||||
userStore := model.NewUserStore(db.Pool)
|
userStore := model.NewUserStore(database.Pool)
|
||||||
|
serviceStore := store.NewServiceStore(database.Pool)
|
||||||
userHandler := handler.NewUserHandler(userStore)
|
userHandler := handler.NewUserHandler(userStore)
|
||||||
loginHandler := handler.NewLoginHandler(userStore)
|
loginHandler := handler.NewLoginHandler(userStore)
|
||||||
|
serviceHandler := handler.NewServiceHandler(serviceStore)
|
||||||
|
|
||||||
testRouter = mux.NewRouter()
|
testRouter = mux.NewRouter()
|
||||||
testRouter.HandleFunc(endpoint.Register, userHandler.Register).Methods("POST")
|
testRouter.HandleFunc(endpoint.Register, userHandler.Register).Methods("POST")
|
||||||
testRouter.HandleFunc(endpoint.Login, loginHandler.Login).Methods("POST")
|
testRouter.HandleFunc(endpoint.Login, loginHandler.Login).Methods("POST")
|
||||||
|
testRouter.HandleFunc(endpoint.CreateServiceUser, serviceHandler.Register).Methods("POST")
|
||||||
|
|
||||||
code := m.Run()
|
code := m.Run()
|
||||||
os.Exit(code)
|
os.Exit(code)
|
||||||
|
|||||||
@@ -116,6 +116,57 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/service/register": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Create a service user that can send texts (requires JWT)",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"service users"
|
||||||
|
],
|
||||||
|
"summary": "Register service user",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Data to add user",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handler.ServiceCreationRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handler.ServiceCreationResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handler.ServiceCreationResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handler.ServiceCreationResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
@@ -186,6 +237,31 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"handler.ServiceCreationRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"passphrase": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handler.ServiceCreationResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/user.ServiceUser"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"token.Login": {
|
"token.Login": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -199,6 +275,23 @@ const docTemplate = `{
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"user.ServiceUser": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"date_created": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"passphrase": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|||||||
@@ -110,6 +110,57 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/service/register": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Create a service user that can send texts (requires JWT)",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"service users"
|
||||||
|
],
|
||||||
|
"summary": "Register service user",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Data to add user",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handler.ServiceCreationRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handler.ServiceCreationResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handler.ServiceCreationResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handler.ServiceCreationResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
@@ -180,6 +231,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"handler.ServiceCreationRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"passphrase": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handler.ServiceCreationResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/user.ServiceUser"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"token.Login": {
|
"token.Login": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -193,6 +269,23 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"user.ServiceUser": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"date_created": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"passphrase": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|||||||
@@ -43,6 +43,22 @@ definitions:
|
|||||||
username:
|
username:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
handler.ServiceCreationRequest:
|
||||||
|
properties:
|
||||||
|
passphrase:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handler.ServiceCreationResponse:
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/user.ServiceUser'
|
||||||
|
type: array
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
token.Login:
|
token.Login:
|
||||||
properties:
|
properties:
|
||||||
access_token:
|
access_token:
|
||||||
@@ -52,6 +68,17 @@ definitions:
|
|||||||
token_type:
|
token_type:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
user.ServiceUser:
|
||||||
|
properties:
|
||||||
|
date_created:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
passphrase:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
host: localhost:9080
|
host: localhost:9080
|
||||||
info:
|
info:
|
||||||
contact: {}
|
contact: {}
|
||||||
@@ -123,6 +150,38 @@ paths:
|
|||||||
summary: Register user
|
summary: Register user
|
||||||
tags:
|
tags:
|
||||||
- users
|
- users
|
||||||
|
/service/register:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Create a service user that can send texts (requires JWT)
|
||||||
|
parameters:
|
||||||
|
- description: Data to add user
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.ServiceCreationRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.ServiceCreationResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.ServiceCreationResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handler.ServiceCreationResponse'
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: Register service user
|
||||||
|
tags:
|
||||||
|
- service users
|
||||||
securityDefinitions:
|
securityDefinitions:
|
||||||
BearerAuth:
|
BearerAuth:
|
||||||
description: JWT Bearer Token
|
description: JWT Bearer Token
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module git.kundeng.us/phoenix/textsender-auth
|
|||||||
go 1.25.3
|
go 1.25.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.kundeng.us/phoenix/textsender-models v0.0.6-main-28b29802b9-556
|
git.kundeng.us/phoenix/textsender-models v0.0.8-main-3ab81c2b6d-556
|
||||||
github.com/go-chi/chi/v5 v5.2.3
|
github.com/go-chi/chi/v5 v5.2.3
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.kundeng.us/phoenix/textsender-models v0.0.6-main-28b29802b9-556 h1:2NI8cXaxt2rsXrhy/rDhgoOzvjLQwbhwr4nv2g35Ah8=
|
git.kundeng.us/phoenix/textsender-models v0.0.8-main-3ab81c2b6d-556 h1:iVE8Dg3aYugoZseneN9tQtdV2cymukspShF7dfExSVw=
|
||||||
git.kundeng.us/phoenix/textsender-models v0.0.6-main-28b29802b9-556/go.mod h1:lx5MCnOgGgsdpwzrfi9uph5xmkeb6H8AuexUNGss2no=
|
git.kundeng.us/phoenix/textsender-models v0.0.8-main-3ab81c2b6d-556/go.mod h1:lx5MCnOgGgsdpwzrfi9uph5xmkeb6H8AuexUNGss2no=
|
||||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ package endpoint
|
|||||||
// Endpoint for registering a user
|
// Endpoint for registering a user
|
||||||
const Register = "/api/v1/register"
|
const Register = "/api/v1/register"
|
||||||
const Login = "/api/v1/login"
|
const Login = "/api/v1/login"
|
||||||
|
const CreateServiceUser = "/api/v1/service/register"
|
||||||
|
|||||||
@@ -12,11 +12,12 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint"
|
"git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint"
|
||||||
|
"git.kundeng.us/phoenix/textsender-auth/internal/store/mock"
|
||||||
"git.kundeng.us/phoenix/textsender-auth/internal/utility"
|
"git.kundeng.us/phoenix/textsender-auth/internal/utility"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLogin(t *testing.T) {
|
func TestLogin(t *testing.T) {
|
||||||
mockstore := NewMockUserStore()
|
mockstore := mock.NewMockUserStore()
|
||||||
handler := NewLoginHandler(mockstore)
|
handler := NewLoginHandler(mockstore)
|
||||||
|
|
||||||
testUser := GetTestUser()
|
testUser := GetTestUser()
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint"
|
"git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint"
|
||||||
|
"git.kundeng.us/phoenix/textsender-auth/internal/store/mock"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateUserWithMock(t *testing.T) {
|
func TestCreateUserWithMock(t *testing.T) {
|
||||||
mockstore := NewMockUserStore()
|
mockstore := mock.NewMockUserStore()
|
||||||
handler := NewUserHandler(mockstore)
|
handler := NewUserHandler(mockstore)
|
||||||
|
|
||||||
testUser := GetTestUser()
|
testUser := GetTestUser()
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.kundeng.us/phoenix/textsender-models/pkg/user"
|
||||||
|
|
||||||
|
"git.kundeng.us/phoenix/textsender-auth/internal/store"
|
||||||
|
"git.kundeng.us/phoenix/textsender-auth/internal/utility"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServiceCreationRequest struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Passphrase string `json:"passphrase"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceCreationResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data []*user.ServiceUser `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceHandler struct {
|
||||||
|
ServiceStore store.ServiceStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServiceHandler(serviceStore store.ServiceStore) *ServiceHandler {
|
||||||
|
return &ServiceHandler{ServiceStore: serviceStore}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register godoc
|
||||||
|
// @Summary Register service user
|
||||||
|
// @Description Create a service user that can send texts (requires JWT)
|
||||||
|
// @Tags service users
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Param request body ServiceCreationRequest true "Data to add user"
|
||||||
|
// @Success 200 {object} ServiceCreationResponse
|
||||||
|
// @Failure 400 {object} ServiceCreationResponse
|
||||||
|
// @Failure 500 {object} ServiceCreationResponse
|
||||||
|
// @Router /service/register [post]
|
||||||
|
func (s *ServiceHandler) Register(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req ServiceCreationRequest
|
||||||
|
if err := ExtractFromRequest(r, &req); err != nil {
|
||||||
|
http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
var statusCode int
|
||||||
|
var resp ServiceCreationResponse
|
||||||
|
|
||||||
|
ctx := r.Context()
|
||||||
|
if exists, err := s.ServiceStore.CheckWithUsername(ctx, req.Username); err != nil {
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
resp.Message = err.Error()
|
||||||
|
} else {
|
||||||
|
if exists {
|
||||||
|
statusCode = http.StatusBadRequest
|
||||||
|
resp.Message = "Service user already exists"
|
||||||
|
} else {
|
||||||
|
hashing := utility.HashMash{Password: req.Passphrase}
|
||||||
|
if hashedPassword, err := hashing.HashPassword(); err != nil {
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
resp.Message = err.Error()
|
||||||
|
} else {
|
||||||
|
serviceUser := user.ServiceUser{Username: req.Username, Passphrase: hashedPassword}
|
||||||
|
if err := s.ServiceStore.Create(ctx, &serviceUser); err != nil {
|
||||||
|
statusCode = http.StatusInternalServerError
|
||||||
|
resp.Message = err.Error()
|
||||||
|
} else {
|
||||||
|
statusCode = http.StatusCreated
|
||||||
|
resp.Message = "Successful"
|
||||||
|
resp.Data = append(resp.Data, &serviceUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RespondWithJson(w, statusCode, &resp)
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint"
|
||||||
|
"git.kundeng.us/phoenix/textsender-auth/internal/store/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateServiceUserWithMock(t *testing.T) {
|
||||||
|
mockStore := mock.NewMockServiceUserStore()
|
||||||
|
handler := NewServiceHandler(mockStore)
|
||||||
|
|
||||||
|
testService := ServiceCreationRequest{Username: "swoon", Passphrase: "ewrewr329n12y3x2!2"}
|
||||||
|
jsonValue, err := json.Marshal(testService)
|
||||||
|
assert.NoError(t, err, "Error marshaling request")
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("POST", endpoint.CreateServiceUser, strings.NewReader(string(jsonValue)))
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler.Register(rr, req)
|
||||||
|
assert.Equal(t, http.StatusCreated, rr.Code)
|
||||||
|
|
||||||
|
var response ServiceCreationResponse
|
||||||
|
err = json.Unmarshal(rr.Body.Bytes(), &response)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package mock
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
|
"git.kundeng.us/phoenix/textsender-models/pkg/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockServiceUserStore struct {
|
||||||
|
ServiceUsers map[uuid.UUID]*user.ServiceUser
|
||||||
|
ServiceUsersByUsername map[string]*user.ServiceUser
|
||||||
|
mu sync.RWMutex
|
||||||
|
Error error // Optional: simulate errors
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMockServiceUserStore() *MockServiceUserStore {
|
||||||
|
return &MockServiceUserStore{
|
||||||
|
ServiceUsers: make(map[uuid.UUID]*user.ServiceUser),
|
||||||
|
ServiceUsersByUsername: make(map[string]*user.ServiceUser),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockServiceUserStore) Create(ctx context.Context, user *user.ServiceUser) error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
if m.Error != nil {
|
||||||
|
return m.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Id == uuid.Nil {
|
||||||
|
user.Id = uuid.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := m.ServiceUsersByUsername[user.Username]; exists {
|
||||||
|
return errors.New("service User with username already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.ServiceUsers[user.Id] = user
|
||||||
|
m.ServiceUsersByUsername[user.Username] = user
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockServiceUserStore) CheckWithUsername(ctx context.Context, username string) (bool, error) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
if m.Error != nil {
|
||||||
|
return false, m.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Error != nil {
|
||||||
|
return false, m.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
var exists bool
|
||||||
|
|
||||||
|
for _, serviceUser := range m.ServiceUsers {
|
||||||
|
if serviceUser.Username == username {
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return exists, nil
|
||||||
|
} else {
|
||||||
|
return exists, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package handler
|
package mock
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.kundeng.us/phoenix/textsender-models/pkg/user"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServiceStore interface {
|
||||||
|
CheckWithUsername(ctx context.Context, username string) (bool, error)
|
||||||
|
Create(ctx context.Context, serviceUser *user.ServiceUser) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type PGServiceStore struct {
|
||||||
|
db *pgxpool.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServiceStore(db *pgxpool.Pool) *PGServiceStore {
|
||||||
|
return &PGServiceStore{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PGServiceStore) CheckWithUsername(ctx context.Context, username string) (bool, error) {
|
||||||
|
var exists bool
|
||||||
|
query := `SELECT EXISTS(SELECT 1 FROM service_users WHERE Username = $1)`
|
||||||
|
|
||||||
|
if err := s.db.QueryRow(ctx, query, username).Scan(&exists); err != nil {
|
||||||
|
if err == pgx.ErrNoRows {
|
||||||
|
return exists, nil
|
||||||
|
} else {
|
||||||
|
return exists, fmt.Errorf("Error querying row: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return exists, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PGServiceStore) Create(ctx context.Context, serviceUser *user.ServiceUser) error {
|
||||||
|
query := `
|
||||||
|
INSERT INTO service_users (username, passphrase)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
RETURNING id, created
|
||||||
|
`
|
||||||
|
|
||||||
|
return s.db.QueryRow(ctx, query, serviceUser.Username, serviceUser.Passphrase).Scan(
|
||||||
|
&serviceUser.Id, &serviceUser.Created,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||||
|
|
||||||
DROP TABLE IF EXISTS users CASCADE;
|
DROP TABLE IF EXISTS users CASCADE;
|
||||||
|
DROP TABLE IF EXISTS service_users CASCADE;
|
||||||
|
|
||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
@@ -9,3 +10,9 @@ CREATE TABLE users (
|
|||||||
password TEXT NOT NULL
|
password TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE service_users (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
username TEXT NOT NULL,
|
||||||
|
passphrase TEXT NOT NULL,
|
||||||
|
created timestamptz DEFAULT now()
|
||||||
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user