Compare commits

...

10 Commits

Author SHA1 Message Date
Muyao CHEN
4cdfe753c8 doc: add diary 2024-10-06 00:17:45 +02:00
Muyao CHEN
332871d403 feat: route up to the controller level 2024-10-06 00:15:29 +02:00
Muyao CHEN
c00cbf35f1 feat: Integrate db infra 2024-10-05 23:51:11 +02:00
Muyao CHEN
1d753783ce fix: addlicense ignore sqlc dir 2024-10-05 23:23:35 +02:00
Muyao CHEN
a6abdf504d fix: change sqlc output directory 2024-10-05 23:23:09 +02:00
Muyao CHEN
426471d3b1 fix: change db configs in the config file 2024-10-05 23:22:49 +02:00
Muyao CHEN
8edea3f0b8 fix: change helpers to config.go and add some default configs 2024-10-05 23:22:19 +02:00
Muyao CHEN
d7b007b851 feat: introduce sqlc for the datastore layer 2024-10-05 18:58:14 +02:00
Muyao CHEN
8f9e552696 feat: start to use openapi 2024-10-05 18:02:42 +02:00
Muyao CHEN
28bc869c64 feat: Add regitry and adapter level code for user/admin 2024-10-05 16:41:49 +02:00
28 changed files with 1511 additions and 795 deletions

View File

@ -42,7 +42,7 @@ format: # format code.
.PHONY: add-copyright
add-copyright: # add license to file headers.
@addlicense -v -f $(ROOT_DIR)/LICENSE $(ROOT_DIR) --skip-files=database.yml --skip-dirs=$(OUTPUT_DIR),deployment,migrations,configs
@addlicense -v -f $(ROOT_DIR)/LICENSE $(ROOT_DIR) --skip-files=database.yml --skip-dirs=$(OUTPUT_DIR),deployment,migrations,configs,sqlc
.PHONY: swagger
swagger: # Run swagger.

View File

@ -197,3 +197,10 @@ type User struct {
```
Use Buffalo pop `Soda CLI` to create database migrations.
### 2024/10/07
Implement the architecture design for User entity.
Checked out OpenAPI, and found that it was not that simple at all. It needs
a whole package of knowledge about the web development!

View File

@ -0,0 +1,828 @@
# MIT License
#
# Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
swagger: "2.0"
openapi: 3.1.0
info:
title: Swagger Petstore - OpenAPI 3.1
description: |-
This is a sample Pet Store Server based on the OpenAPI 3.1 specification. You can find out more about
Swagger at [https://swagger.io](https://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!
You can now help us improve the API whether it's by making changes to the definition itself or to the code.
That way, with time, we can improve the API in general, and expose some of the new features in OAS3.
Some useful links:
- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)
- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)
termsOfService: http://swagger.io/terms/
contact:
email: apiteam@swagger.io
license:
name: Apache 2.0
url: http://www.apache.org/licenses/LICENSE-2.0.html
version: 1.0.11
externalDocs:
description: Find out more about Swagger
url: http://swagger.io
servers:
- url: https://petstore3.swagger.io/api/v3
tags:
- name: pet
description: Everything about your Pets
externalDocs:
description: Find out more
url: http://swagger.io
- name: store
description: Access to Petstore orders
externalDocs:
description: Find out more about our store
url: http://swagger.io
- name: user
description: Operations about user
paths:
/pet:
put:
tags:
- pet
summary: Update an existing pet
description: Update an existing pet by Id
operationId: updatePet
requestBody:
description: Update an existent pet in the store
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/Pet'
required: true
responses:
'200':
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid ID supplied
'404':
description: Pet not found
'422':
description: Validation exception
security:
- petstore_auth:
- write:pets
- read:pets
post:
tags:
- pet
summary: Add a new pet to the store
description: Add a new pet to the store
operationId: addPet
requestBody:
description: Create a new pet in the store
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/Pet'
required: true
responses:
'200':
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid input
'422':
description: Validation exception
security:
- petstore_auth:
- write:pets
- read:pets
/pet/findByStatus:
get:
tags:
- pet
summary: Finds Pets by status
description: Multiple status values can be provided with comma separated strings
operationId: findPetsByStatus
parameters:
- name: status
in: query
description: Status values that need to be considered for filter
required: false
explode: true
schema:
type: string
default: available
enum:
- available
- pending
- sold
responses:
'200':
description: successful operation
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid status value
security:
- petstore_auth:
- write:pets
- read:pets
/pet/findByTags:
get:
tags:
- pet
summary: Finds Pets by tags
description: Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
operationId: findPetsByTags
parameters:
- name: tags
in: query
description: Tags to filter by
required: false
explode: true
schema:
type: array
items:
type: string
responses:
'200':
description: successful operation
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid tag value
security:
- petstore_auth:
- write:pets
- read:pets
/pet/{petId}:
get:
tags:
- pet
summary: Find pet by ID
description: Returns a single pet
operationId: getPetById
parameters:
- name: petId
in: path
description: ID of pet to return
required: true
schema:
type: integer
format: int64
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid ID supplied
'404':
description: Pet not found
security:
- api_key: []
- petstore_auth:
- write:pets
- read:pets
post:
tags:
- pet
summary: Updates a pet in the store with form data
description: ''
operationId: updatePetWithForm
parameters:
- name: petId
in: path
description: ID of pet that needs to be updated
required: true
schema:
type: integer
format: int64
- name: name
in: query
description: Name of pet that needs to be updated
schema:
type: string
- name: status
in: query
description: Status of pet that needs to be updated
schema:
type: string
responses:
'400':
description: Invalid input
security:
- petstore_auth:
- write:pets
- read:pets
delete:
tags:
- pet
summary: Deletes a pet
description: delete a pet
operationId: deletePet
parameters:
- name: api_key
in: header
description: ''
required: false
schema:
type: string
- name: petId
in: path
description: Pet id to delete
required: true
schema:
type: integer
format: int64
responses:
'400':
description: Invalid pet value
security:
- petstore_auth:
- write:pets
- read:pets
/pet/{petId}/uploadImage:
post:
tags:
- pet
summary: uploads an image
description: ''
operationId: uploadFile
parameters:
- name: petId
in: path
description: ID of pet to update
required: true
schema:
type: integer
format: int64
- name: additionalMetadata
in: query
description: Additional Metadata
required: false
schema:
type: string
requestBody:
content:
application/octet-stream:
schema:
type: string
format: binary
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponse'
security:
- petstore_auth:
- write:pets
- read:pets
/store/inventory:
get:
tags:
- store
summary: Returns pet inventories by status
description: Returns a map of status codes to quantities
operationId: getInventory
responses:
'200':
description: successful operation
content:
application/json:
schema:
type: object
additionalProperties:
type: integer
format: int32
security:
- api_key: []
/store/order:
post:
tags:
- store
summary: Place an order for a pet
description: Place a new order in the store
operationId: placeOrder
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
application/xml:
schema:
$ref: '#/components/schemas/Order'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/Order'
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
'400':
description: Invalid input
'422':
description: Validation exception
/store/order/{orderId}:
get:
tags:
- store
summary: Find purchase order by ID
description: For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
operationId: getOrderById
parameters:
- name: orderId
in: path
description: ID of order that needs to be fetched
required: true
schema:
type: integer
format: int64
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
application/xml:
schema:
$ref: '#/components/schemas/Order'
'400':
description: Invalid ID supplied
'404':
description: Order not found
delete:
tags:
- store
summary: Delete purchase order by ID
description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
operationId: deleteOrder
parameters:
- name: orderId
in: path
description: ID of the order that needs to be deleted
required: true
schema:
type: integer
format: int64
responses:
'400':
description: Invalid ID supplied
'404':
description: Order not found
/user:
post:
tags:
- user
summary: Create user
description: This can only be done by the logged in user.
operationId: createUser
requestBody:
description: Created user object
content:
application/json:
schema:
$ref: '#/components/schemas/User'
application/xml:
schema:
$ref: '#/components/schemas/User'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/User'
responses:
default:
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/User'
application/xml:
schema:
$ref: '#/components/schemas/User'
/user/createWithList:
post:
tags:
- user
summary: Creates list of users with given input array
description: Creates list of users with given input array
operationId: createUsersWithListInput
requestBody:
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
responses:
'200':
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/User'
application/xml:
schema:
$ref: '#/components/schemas/User'
default:
description: successful operation
/user/login:
get:
tags:
- user
summary: Logs user into the system
description: ''
operationId: loginUser
parameters:
- name: username
in: query
description: The user name for login
required: false
schema:
type: string
- name: password
in: query
description: The password for login in clear text
required: false
schema:
type: string
responses:
'200':
description: successful operation
headers:
X-Rate-Limit:
description: calls per hour allowed by the user
schema:
type: integer
format: int32
X-Expires-After:
description: date in UTC when token expires
schema:
type: string
format: date-time
content:
application/xml:
schema:
type: string
application/json:
schema:
type: string
'400':
description: Invalid username/password supplied
/user/logout:
get:
tags:
- user
summary: Logs out current logged in user session
description: ''
operationId: logoutUser
parameters: []
responses:
default:
description: successful operation
/user/{username}:
get:
tags:
- user
summary: Get user by user name
description: ''
operationId: getUserByName
parameters:
- name: username
in: path
description: 'The name that needs to be fetched. Use user1 for testing. '
required: true
schema:
type: string
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/User'
application/xml:
schema:
$ref: '#/components/schemas/User'
'400':
description: Invalid username supplied
'404':
description: User not found
put:
tags:
- user
summary: Update user
description: This can only be done by the logged in user.
operationId: updateUser
parameters:
- name: username
in: path
description: name that need to be deleted
required: true
schema:
type: string
requestBody:
description: Update an existent user in the store
content:
application/json:
schema:
$ref: '#/components/schemas/User'
application/xml:
schema:
$ref: '#/components/schemas/User'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/User'
responses:
default:
description: successful operation
delete:
tags:
- user
summary: Delete user
description: This can only be done by the logged in user.
operationId: deleteUser
parameters:
- name: username
in: path
description: The name that needs to be deleted
required: true
schema:
type: string
responses:
'400':
description: Invalid username supplied
'404':
description: User not found
components:
schemas:
Order:
type: object
properties:
id:
type: integer
format: int64
examples: [10]
petId:
type: integer
format: int64
examples: [198772]
quantity:
type: integer
format: int32
examples: [7]
shipDate:
type: string
format: date-time
status:
type: string
description: Order Status
examples: [approved]
enum:
- placed
- approved
- delivered
complete:
type: boolean
xml:
name: order
Customer:
type: object
properties:
id:
type: integer
format: int64
examples: [100000]
username:
type: string
examples: [fehguy]
address:
type: array
xml:
name: addresses
wrapped: true
items:
$ref: '#/components/schemas/Address'
xml:
name: customer
Address:
type: object
properties:
street:
type: string
examples: [437 Lytton]
city:
type: string
examples: [Palo Alto]
state:
type: string
examples: [CA]
zip:
type: string
examples: ['94301']
xml:
name: address
Category:
type: object
properties:
id:
type: integer
format: int64
examples: [1]
name:
type: string
examples: [Dogs]
xml:
name: category
User:
type: object
properties:
id:
type: integer
format: int64
examples: [10]
username:
type: string
examples: [theUser]
firstName:
type: string
examples: [John]
lastName:
type: string
examples: [James]
email:
type: string
examples: [john@email.com]
password:
type: string
examples: ['12345']
phone:
type: string
examples: ['12345']
userStatus:
type: integer
description: User Status
format: int32
examples: [1]
xml:
name: user
Tag:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
xml:
name: tag
Pet:
required:
- name
- photoUrls
type: object
properties:
id:
type: integer
format: int64
examples: [10]
name:
type: string
examples: [doggie]
category:
$ref: '#/components/schemas/Category'
photoUrls:
type: array
xml:
wrapped: true
items:
type: string
xml:
name: photoUrl
tags:
type: array
xml:
wrapped: true
items:
$ref: '#/components/schemas/Tag'
status:
type: string
description: pet status in the store
enum:
- available
- pending
- sold
xml:
name: pet
ApiResponse:
type: object
properties:
code:
type: integer
format: int32
type:
type: string
message:
type: string
xml:
name: '##default'
requestBodies:
Pet:
description: Pet object that needs to be added to the store
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
UserArray:
description: List of user object
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
securitySchemes:
petstore_auth:
type: oauth2
flows:
implicit:
authorizationUrl: https://petstore3.swagger.io/oauth/authorize
scopes:
write:pets: modify pets in your account
read:pets: read your pets
api_key:
type: apiKey
name: api_key
in: header

View File

@ -21,795 +21,79 @@
# SOFTWARE.
swagger: "2.0"
openapi: "3.1.0"
openapi: 3.1.0
info:
title: Howmuch expense-sharing
title: Howmuch Expense-sharing app
description: |-
A simple web application that can help you share your expense with your
friends.
contact:
email: vinchent@vinchent.vinchent
email: vinchent@vinchent.xyz
license:
name: MIT
url: https://opensource.org/license/MIT
version: 0.0.1
# externalDocs:
# description: Find out more about Swagger
# url: http://swagger.io
version: '0.0.1'
servers:
- url: http://localhost:8080/v1
- url: https:/localhost:8000/v1
tags:
- name: event
description: Event with your friends
- name: expense
description: Who paid how much for whom else
- name: user
description: Operations about user
paths:
/event:
put:
/signup:
post:
tags:
- event
summary: Update an existing pet
description: Update an existing pet by Id
operationId: updatePet
- user
description: Sign up as a new user
requestBody:
description: Update an existent pet in the store
description: Sign up
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/Pet'
required: true
$ref: '#/components/schemas/UserSignUpRequest'
responses:
'200':
description: Successful operation
'400':
description: Client side error
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid ID supplied
'404':
description: Pet not found
'422':
description: Validation exception
security:
- petstore_auth:
- write:pets
- read:pets
post:
tags:
- event
summary: Add a new pet to the store
description: Add a new pet to the store
operationId: addPet
requestBody:
description: Create a new pet in the store
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/Pet'
required: true
responses:
'200':
description: Successful operation
$ref: '#/components/schemas/ErrResponse'
'500':
description: Server side error
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid input
'422':
description: Validation exception
security:
- petstore_auth:
- write:pets
- read:pets
/event/findByStatus:
get:
tags:
- event
summary: Finds Pets by status
description: Multiple status values can be provided with comma separated strings
operationId: findPetsByStatus
parameters:
- name: status
in: query
description: Status values that need to be considered for filter
required: false
explode: true
schema:
type: string
default: available
enum:
- available
- pending
- sold
responses:
'200':
description: successful operation
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid status value
security:
- petstore_auth:
- write:pets
- read:pets
/event/findByTags:
get:
tags:
- event
summary: Finds Pets by tags
description: Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
operationId: findPetsByTags
parameters:
- name: tags
in: query
description: Tags to filter by
required: false
explode: true
schema:
type: array
items:
type: string
responses:
'200':
description: successful operation
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid tag value
security:
- petstore_auth:
- write:pets
- read:pets
/event/{eventId}:
get:
tags:
- event
summary: Find pet by ID
description: Returns a single pet
operationId: getPetById
parameters:
- name: petId
in: path
description: ID of pet to return
required: true
schema:
type: integer
format: int64
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid ID supplied
'404':
description: Pet not found
security:
- api_key: []
- petstore_auth:
- write:pets
- read:pets
post:
tags:
- event
summary: Updates a pet in the store with form data
description: ''
operationId: updatePetWithForm
parameters:
- name: petId
in: path
description: ID of pet that needs to be updated
required: true
schema:
type: integer
format: int64
- name: name
in: query
description: Name of pet that needs to be updated
schema:
type: string
- name: status
in: query
description: Status of pet that needs to be updated
schema:
type: string
responses:
'400':
description: Invalid input
security:
- petstore_auth:
- write:pets
- read:pets
delete:
tags:
- event
summary: Deletes a pet
description: delete a pet
operationId: deletePet
parameters:
- name: api_key
in: header
description: ''
required: false
schema:
type: string
- name: petId
in: path
description: Pet id to delete
required: true
schema:
type: integer
format: int64
responses:
'400':
description: Invalid pet value
security:
- petstore_auth:
- write:pets
- read:pets
/event/{eventId}/uploadImage:
post:
tags:
- event
summary: uploads an image
description: ''
operationId: uploadFile
parameters:
- name: petId
in: path
description: ID of pet to update
required: true
schema:
type: integer
format: int64
- name: additionalMetadata
in: query
description: Additional Metadata
required: false
schema:
type: string
requestBody:
content:
application/octet-stream:
schema:
type: string
format: binary
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponse'
security:
- petstore_auth:
- write:pets
- read:pets
/expense/inventory:
get:
tags:
- expense
summary: Returns pet inventories by status
description: Returns a map of status codes to quantities
operationId: getInventory
responses:
'200':
description: successful operation
content:
application/json:
schema:
type: object
additionalProperties:
type: integer
format: int32
security:
- api_key: []
/expense/order:
post:
tags:
- expense
summary: Place an order for a pet
description: Place a new order in the store
operationId: placeOrder
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
application/xml:
schema:
$ref: '#/components/schemas/Order'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/Order'
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
'400':
description: Invalid input
'422':
description: Validation exception
/expense/order/{orderId}:
get:
tags:
- expense
summary: Find purchase order by ID
description: For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
operationId: getOrderById
parameters:
- name: orderId
in: path
description: ID of order that needs to be fetched
required: true
schema:
type: integer
format: int64
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
application/xml:
schema:
$ref: '#/components/schemas/Order'
'400':
description: Invalid ID supplied
'404':
description: Order not found
delete:
tags:
- expense
summary: Delete purchase order by ID
description: For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
operationId: deleteOrder
parameters:
- name: orderId
in: path
description: ID of the order that needs to be deleted
required: true
schema:
type: integer
format: int64
responses:
'400':
description: Invalid ID supplied
'404':
description: Order not found
/user:
post:
tags:
- user
summary: Create user
description: This can only be done by the logged in user.
operationId: createUser
requestBody:
description: Created user object
content:
application/json:
schema:
$ref: '#/components/schemas/User'
application/xml:
schema:
$ref: '#/components/schemas/User'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/User'
responses:
default:
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/User'
application/xml:
schema:
$ref: '#/components/schemas/User'
/user/createWithList:
post:
tags:
- user
summary: Creates list of users with given input array
description: Creates list of users with given input array
operationId: createUsersWithListInput
requestBody:
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
responses:
'200':
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/User'
application/xml:
schema:
$ref: '#/components/schemas/User'
default:
description: successful operation
/user/login:
get:
tags:
- user
summary: Logs user into the system
description: ''
operationId: loginUser
parameters:
- name: username
in: query
description: The user name for login
required: false
schema:
type: string
- name: password
in: query
description: The password for login in clear text
required: false
schema:
type: string
responses:
'200':
description: successful operation
headers:
X-Rate-Limit:
description: calls per hour allowed by the user
schema:
type: integer
format: int32
X-Expires-After:
description: date in UTC when token expires
schema:
type: string
format: date-time
content:
application/xml:
schema:
type: string
application/json:
schema:
type: string
'400':
description: Invalid username/password supplied
/user/logout:
get:
tags:
- user
summary: Logs out current logged in user session
description: ''
operationId: logoutUser
parameters: []
responses:
default:
description: successful operation
/user/{username}:
get:
tags:
- user
summary: Get user by user name
description: ''
operationId: getUserByName
parameters:
- name: username
in: path
description: 'The name that needs to be fetched. Use user1 for testing. '
required: true
schema:
type: string
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/User'
application/xml:
schema:
$ref: '#/components/schemas/User'
'400':
description: Invalid username supplied
'404':
description: User not found
put:
tags:
- user
summary: Update user
description: This can only be done by the logged in user.
operationId: updateUser
parameters:
- name: username
in: path
description: name that need to be deleted
required: true
schema:
type: string
requestBody:
description: Update an existent user in the store
content:
application/json:
schema:
$ref: '#/components/schemas/User'
application/xml:
schema:
$ref: '#/components/schemas/User'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/User'
responses:
default:
description: successful operation
delete:
tags:
- user
summary: Delete user
description: This can only be done by the logged in user.
operationId: deleteUser
parameters:
- name: username
in: path
description: The name that needs to be deleted
required: true
schema:
type: string
responses:
'400':
description: Invalid username supplied
'404':
description: User not found
$ref: '#/components/schemas/ErrResponse'
components:
schemas:
Order:
UserSignUpRequest:
type: object
properties:
id:
type: integer
format: int64
examples: [10]
petId:
type: integer
format: int64
examples: [198772]
quantity:
type: integer
format: int32
examples: [7]
shipDate:
type: string
format: date-time
status:
type: string
description: Order Status
examples: [approved]
enum:
- placed
- approved
- delivered
complete:
type: boolean
xml:
name: order
Customer:
type: object
properties:
id:
type: integer
format: int64
examples: [100000]
username:
type: string
examples: [fehguy]
address:
type: array
xml:
name: addresses
wrapped: true
items:
$ref: '#/components/schemas/Address'
xml:
name: customer
Address:
type: object
properties:
street:
type: string
examples: [437 Lytton]
city:
type: string
examples: [Palo Alto]
state:
type: string
examples: [CA]
zip:
type: string
examples: ['94301']
xml:
name: address
Category:
type: object
properties:
id:
type: integer
format: int64
examples: [1]
name:
type: string
examples: [Dogs]
xml:
name: category
User:
type: object
properties:
id:
type: integer
format: int64
examples: [10]
username:
type: string
examples: [theUser]
firstName:
type: string
examples: [John]
lastName:
type: string
examples: [James]
email:
type: string
examples: [john@email.com]
example: bruce@wayne.com
first_name:
type: string
example: Bruce
last_name:
type: string
example: Wayne
password:
type: string
examples: ['12345']
phone:
type: string
examples: ['12345']
userStatus:
type: integer
description: User Status
format: int32
examples: [1]
xml:
name: user
Tag:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
xml:
name: tag
Pet:
example: verystrongpassword
required:
- name
- photoUrls
type: object
properties:
id:
type: integer
format: int64
examples: [10]
name:
type: string
examples: [doggie]
category:
$ref: '#/components/schemas/Category'
photoUrls:
type: array
xml:
wrapped: true
items:
type: string
xml:
name: photoUrl
tags:
type: array
xml:
wrapped: true
items:
$ref: '#/components/schemas/Tag'
status:
type: string
description: pet status in the store
enum:
- available
- pending
- sold
xml:
name: pet
ApiResponse:
- email
- fist_name
- last_name
- password
ErrResponse:
type: object
properties:
code:
type: integer
format: int32
type:
type: string
example: FailedOperation.UserAlreadyExists
message:
type: string
xml:
name: '##default'
requestBodies:
Pet:
description: Pet object that needs to be added to the store
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
UserArray:
description: List of user object
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
securitySchemes:
petstore_auth:
type: oauth2
flows:
implicit:
authorizationUrl: https://petstore3.swagger.io/oauth/authorize
scopes:
write:pets: modify pets in your account
read:pets: read your pets
api_key:
type: apiKey
name: api_key
in: header
example: "User already exists."

View File

@ -1,24 +1,18 @@
dev-mode: true
web:
addr: :8080
addr: :8000
shutdown-timeout: 10
db:
# DB host
host: 127.0.0.1
# DB username
username: howmuch
username: postgres
# DB password
password: howmuch
password: example
# DB name
database: howmuch
# max idle connections
max-idle-connections: 100
# max open connections
max-open-connections: 100
# max connection life time
max-connection-life-time: 10s
log:
level: debug

12
go.mod
View File

@ -8,6 +8,7 @@ require (
github.com/gin-gonic/gin v1.10.0
github.com/google/uuid v1.6.0
github.com/gosuri/uitable v0.0.4
github.com/jackc/pgx/v5 v5.7.1
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.19.0
@ -29,8 +30,12 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
@ -54,10 +59,11 @@ require (
github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

26
go.sum
View File

@ -35,8 +35,8 @@ github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBEx
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -46,6 +46,14 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@ -127,19 +135,21 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@ -0,0 +1,52 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package controller
import (
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/core"
)
type Admin interface {
CreateUser(core.Context)
GetUserById(core.Context)
UpdateUser(core.Context)
DeleteUser(core.Context)
ListUsers(core.Context)
}
type AdminController struct{}
func (ac *AdminController) CreateUser(core.Context) {
}
func (ac *AdminController) GetUserById(core.Context) {
}
func (ac *AdminController) UpdateUser(core.Context) {
}
func (ac *AdminController) DeleteUser(core.Context) {
}
func (ac *AdminController) ListUsers(core.Context) {
}

View File

@ -0,0 +1,31 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package controller
// AppController is the controller structure that holds service controllers.
type AppController struct {
// User must implement User interface
User interface{ User }
Admin interface{ Admin }
}

View File

@ -0,0 +1,57 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package controller
import (
"net/http"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/core"
)
// User is the user controller interface, it describes all the handlers
// that need to be implemented for the /user endpoint
type User interface {
Signup(core.Context)
UpdateInfo(core.Context)
Login(core.Context)
Logout(core.Context)
ChangePassword(core.Context)
}
type UserController struct{}
func (uc *UserController) Signup(ctx core.Context) {
ctx.JSON(http.StatusOK, "hello")
}
func (uc *UserController) UpdateInfo(ctx core.Context) {
}
func (uc *UserController) Login(ctx core.Context) {
}
func (uc *UserController) Logout(ctx core.Context) {
}
func (uc *UserController) ChangePassword(ctx core.Context) {
}

View File

@ -0,0 +1,27 @@
-- MIT License
--
-- Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
-- name: InsertUser :one
INSERT INTO "user" (
email, first_name, last_name, password, created_at, updated_at
) VALUES ( $1, $2, $3, $4, $5, $6 )
RETURNING *;

View File

@ -40,8 +40,25 @@ const (
configType = "yaml"
)
func defaultConfig() {
viper.SetDefault("dev-mode", true)
// web
viper.SetDefault("web.addr", ":8000")
viper.SetDefault("web.shutdown-timeout", "10")
// db
viper.SetDefault("db.host", "localhost")
viper.SetDefault("db.port", 5432)
viper.SetDefault("db.username", "postgres")
viper.SetDefault("db.password", "example")
viper.SetDefault("db.database", "howmuch")
viper.SetDefault("db.sslmode", "disable")
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
defaultConfig()
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
@ -65,7 +82,7 @@ func initConfig() {
viper.SetEnvKeyReplacer(replacer)
if err := viper.ReadInConfig(); err != nil {
log.ErrorLog("Failed to read viper configuration file", "err", err)
log.ErrorLog("Failed to read viper configuration file, use default config", "err", err)
return
}

View File

@ -0,0 +1,32 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
package sqlc
import (
"context"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
)
type DBTX interface {
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
QueryRow(context.Context, string, ...interface{}) pgx.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
return &Queries{
db: tx,
}
}

View File

@ -0,0 +1,26 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
package sqlc
import (
"github.com/jackc/pgx/v5/pgtype"
)
type Admin struct {
ID int32
Email string
Password string
AccessLevel int32
}
type User struct {
ID int32
Email string
FirstName string
LastName string
Password string
CreatedAt pgtype.Timestamp
UpdatedAt pgtype.Timestamp
}

View File

@ -0,0 +1,72 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
// source: user.sql
package sqlc
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const insertUser = `-- name: InsertUser :one
INSERT INTO "user" (
email, first_name, last_name, password, created_at, updated_at
) VALUES ( $1, $2, $3, $4, $5, $6 )
RETURNING id, email, first_name, last_name, password, created_at, updated_at
`
type InsertUserParams struct {
Email string
FirstName string
LastName string
Password string
CreatedAt pgtype.Timestamp
UpdatedAt pgtype.Timestamp
}
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
func (q *Queries) InsertUser(ctx context.Context, arg InsertUserParams) (User, error) {
row := q.db.QueryRow(ctx, insertUser,
arg.Email,
arg.FirstName,
arg.LastName,
arg.Password,
arg.CreatedAt,
arg.UpdatedAt,
)
var i User
err := row.Scan(
&i.ID,
&i.Email,
&i.FirstName,
&i.LastName,
&i.Password,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}

View File

@ -31,12 +31,11 @@ import (
"syscall"
"time"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/core"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/errno"
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/infra/datastore"
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/infra/router"
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/registry"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/log"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/middleware"
"git.vinchent.xyz/vinchent/howmuch/pkg/version/verflag"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@ -97,6 +96,7 @@ to share their expense of an event or a trip`,
}
func run() error {
// Set Gin running mode
isDev := viper.GetBool("dev-mode")
if isDev {
gin.SetMode(gin.DebugMode)
@ -104,32 +104,37 @@ func run() error {
gin.SetMode(gin.ReleaseMode)
}
r := gin.Default()
// Init DB
dbPool, err := datastore.NewDB(
fmt.Sprintf(
"host=%s port=%d dbname=%s user=%s password=%s sslmode=%s",
viper.GetString("db.host"),
viper.GetInt("db.port"),
viper.GetString("db.database"),
viper.GetString("db.username"),
viper.GetString("db.password"),
viper.GetString("db.sslmode"),
),
)
if err != nil {
log.FatalLog("DB connection failure", "err", err)
}
defer dbPool.Close()
// Middlewares
corsCfg := cors.DefaultConfig()
corsCfg.AllowAllOrigins = true
corsCfg.AllowHeaders = append(corsCfg.AllowHeaders, "Authorization", "Accept", "X-CSRF-Token")
r.Use(cors.New(corsCfg))
// Register the core service
r := registry.NewRegistry(dbPool)
r.Use(middleware.RequestID())
engine := gin.Default()
r.NoRoute(func(ctx *gin.Context) {
core.WriteResponse(ctx, errno.PageNotFoundErr, nil)
})
r.GET("/", func(ctx *gin.Context) {
// time.Sleep(10 * time.Second) // Test shutdown
core.WriteResponse(ctx, nil, gin.H{
"message": "how much?",
})
})
engine = router.Routes(engine, r.NewAppController())
server := http.Server{
Addr: viper.GetString("web.addr"),
Handler: r,
Handler: engine,
}
log.InfoLog("Server running", "port", viper.GetString("web.addr"))
go func() {
if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
log.FatalLog(err.Error())

View File

@ -0,0 +1,44 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package datastore
import (
"context"
"github.com/jackc/pgx/v5/pgxpool"
)
// NewDB creates a new database for the application
func NewDB(dsn string) (*pgxpool.Pool, error) {
conn, err := pgxpool.New(context.Background(), dsn)
if err != nil {
return nil, err
}
// Ping test the conn
if err = conn.Ping(context.Background()); err != nil {
return nil, err
}
return conn, err
}

View File

@ -0,0 +1,54 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package router
import (
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/controller"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/core"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/errno"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/middleware"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func Routes(engine *gin.Engine, c controller.AppController) *gin.Engine {
// Middlewares
// Cors
corsCfg := cors.DefaultConfig()
corsCfg.AllowAllOrigins = true
corsCfg.AllowHeaders = append(corsCfg.AllowHeaders, "Authorization", "Accept", "X-CSRF-Token")
engine.Use(cors.New(corsCfg))
// Use my request id middleware
// TODO: I might use the community version later
engine.Use(middleware.RequestID())
// Route for the 404 error
engine.NoRoute(func(ctx *gin.Context) {
core.WriteResponse(ctx, errno.PageNotFoundErr, nil)
})
engine.POST("/signup", func(ctx *gin.Context) { c.User.Signup(ctx) })
return engine
}

View File

@ -0,0 +1,30 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package registry
import "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/controller"
// NewUserController returns an admin controller's implementation
func (r *registry) NewAdminController() controller.Admin {
return &controller.AdminController{}
}

View File

@ -0,0 +1,57 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package registry
import (
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/controller"
"github.com/jackc/pgx/v5/pgxpool"
)
// registry is an implementation of Registry interface.
// It needs a db connection to provide the necessary support.
// It might holds other drivers when the projects grows. For example
// the object needed to connect to Redis or Kafka.
type registry struct {
db *pgxpool.Pool
}
// Registry returns a new app controller that will be used by main()/run()
// AppController is essentially the struct that holds all the controllers,
// classed by their domains.
type Registry interface {
NewAppController() controller.AppController
}
// NewRegistry returns a new Registry's implementation.
func NewRegistry(db *pgxpool.Pool) Registry {
return &registry{db: db}
}
// NewAppController creates a new AppController with controller struct for
// each domain.
func (r *registry) NewAppController() controller.AppController {
return controller.AppController{
User: r.NewUserController(),
Admin: r.NewAdminController(),
}
}

View File

@ -0,0 +1,30 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package registry
import "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/controller"
// NewUserController returns a user controller's implementation
func (r *registry) NewUserController() controller.User {
return &controller.UserController{}
}

View File

@ -1,3 +1,25 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package core
type Context interface {

31
sqlc.yml Normal file
View File

@ -0,0 +1,31 @@
# MIT License
#
# Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
version: "2"
sql:
- engine: "postgresql"
queries: "internal/app/adapter/repo/sqlc"
schema: "migrations"
gen:
go:
out: "internal/app/controller/repo/sqlc"
sql_package: "pgx/v5"