first commit

This commit is contained in:
2022-12-13 07:42:44 +08:00
commit 7ef2644e20
46 changed files with 7810 additions and 0 deletions

100
pkg/api/api.go Normal file
View File

@@ -0,0 +1,100 @@
package api
import (
"database/sql"
"log"
"os"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
_ "github.com/lib/pq"
)
var db *sql.DB
func init() {
var err error
db, err = sql.Open("postgres", os.Getenv("POSTGRES"))
if err != nil {
log.Fatal(err)
}
log.Println("Successfully connected to postgres database")
// install tables
if len(os.Args) >= 2 {
if os.Args[1] == "install" {
install()
os.Exit(0)
}
// drop tables
if os.Args[1] == "drop" {
drop()
os.Exit(0)
}
// reinstall
if os.Args[1] == "reinstall" {
drop()
install()
os.Exit(0)
}
// fake data
if os.Args[1] == "fake" {
fakeData()
os.Exit(0)
}
// unknown Args
log.Println("Unknown args", os.Args)
os.Exit(2)
}
}
func NewAPI() *gin.Engine {
router := gin.Default()
// session
store := cookie.NewStore([]byte("Miku saves the world!"))
router.Use(sessions.Sessions("ais", store))
// entry point html
router.GET("/", func(c *gin.Context) {
c.File("./web/public/index.html")
})
group := router.Group("/api")
// json error middleware
group.Use(func(c *gin.Context) {
c.Next()
if len(c.Errors) > 0 {
c.JSON(-1, gin.H{
"errors": c.Errors.Errors(),
"note": "gin api error handler",
})
}
})
group.POST("/login", handelLogin)
group.GET("/login", handelGetLoginSession)
group.POST("/register", handelRegister)
group.POST("/logout", handelLogout)
customer := group.Group("/customer")
customer.GET("/market", handleGetMarket)
customer.GET("/market/distance", handleGetMarketByDistance)
customer.GET("/market/:marketid", handleGetGoods)
customer.POST("/market/:marketid/:goodsid", handleBuy)
customer.GET("/purchase", handleGetPurchaseHistory)
customer.DELETE("/purchase/:purchaseid", handleDeletePurchaseHistory)
customer.GET("/purchase/report", handleCustomerReport)
supplier := group.Group("/supplier")
supplier.GET("/goods", handleGetGodsBySupplier)
supplier.POST("/goods", handleCreateGoods)
return router
}

38
pkg/api/drop.go Normal file
View File

@@ -0,0 +1,38 @@
package api
import (
"log"
"strings"
)
var dropSQLString = `
drop table purchase;
drop table tags_on_goods;
drop table goods;
drop table tag;
drop table market;
drop table users;
`
func drop() {
sqls := strings.Split(dropSQLString, "\n\n")
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
for _, sql := range sqls {
log.Println("Dropting table with SQL", sql)
_, err = tx.Exec(sql)
if err != nil {
tx.Rollback()
log.Fatal(err)
}
}
tx.Commit()
log.Println("Successfully drop all tables")
}

22
pkg/api/encrypt.go Normal file
View File

@@ -0,0 +1,22 @@
package api
import (
"log"
"golang.org/x/crypto/bcrypt"
)
func EncryptPassword(password string) string {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost)
if err != nil {
log.Println("Warning: Failed to hash password, fallback to plaintext password")
return password
}
return string(hash)
}
func ComparePassword(hashedPassword string, plainTextPassword string) error {
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(plainTextPassword))
return err
}

88
pkg/api/fake.go Normal file
View File

@@ -0,0 +1,88 @@
package api
import (
"database/sql"
"fmt"
"log"
"math/rand"
"github.com/brianvoe/gofakeit/v6"
)
func fakeData() {
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
fakeUsers(tx)
fakeSupplier(tx)
fakeMarket(tx)
tx.Commit()
}
func fakeUsers(tx *sql.Tx) {
for i := 0; i < 10; i++ {
username := gofakeit.Username()
password := gofakeit.Password(true, true, true, true, true, 1)
encryptedPassword := EncryptPassword(password)
_, err := tx.Exec(`insert into users (username, password) values ($1, $2)`,
username, encryptedPassword)
if err != nil {
tx.Rollback()
log.Fatal(err)
}
log.Println("fake users", username, password)
}
}
func fakeSupplier(tx *sql.Tx) {
for i := 0; i < 10; i++ {
username := gofakeit.Username()
password := EncryptPassword(gofakeit.Password(true, true, true, true, true, 1))
_, err := tx.Exec(`insert into users (username, password, role) values ($1, $2, 2)`,
username, password)
if err != nil {
tx.Rollback()
log.Fatal(err)
}
log.Println("fake supplier", username, password)
}
}
func fakeMarket(tx *sql.Tx) {
for i := 0; i < 10; i++ {
addr := gofakeit.Address()
name := addr.State
description := addr.Address
location := fmt.Sprintf("(%f, %f)", addr.Latitude, addr.Longitude)
row := tx.QueryRow(`insert into market (name, description, location) values ($1, $2, $3) returning id`,
name, description, location)
var mid int64
err := row.Scan(&mid)
if err != nil {
tx.Rollback()
log.Fatal(err)
}
log.Println("fake market", name, description, location)
fakeProduct(tx, int64(mid))
}
}
func fakeProduct(tx *sql.Tx, mid int64) {
for i := 0; i < 10; i++ {
name := gofakeit.BeerName()
description := gofakeit.BeerStyle()
quantity := rand.Intn(390)
price := gofakeit.Price(39, 390)
_, err := tx.Exec(`insert into goods (name, description, supplier_id, market_id, quantity, price) values ($1, $2, $3, $4, $5, $6)`,
name, description, 1, mid, quantity, price)
if err != nil {
tx.Rollback()
log.Fatal(err)
}
log.Println("fake goods", name, description, quantity, price)
}
}

1
pkg/api/handle_buy.go Normal file
View File

@@ -0,0 +1 @@
package api

169
pkg/api/handle_goods.go Normal file
View File

@@ -0,0 +1,169 @@
package api
import (
"log"
"strconv"
"time"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
type Goods struct {
Id int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
SupplierId int64 `json:"supplier_id"`
MarketId int64 `json:"market_id"`
Quantity int64 `json:"quantity"`
Price string `json:"price"`
}
func handleGetGoods(c *gin.Context) {
marketId, err := strconv.ParseInt(c.Param("marketid"), 10, 64)
if err != nil {
c.AbortWithError(500, err)
return
}
log.Println("select", marketId)
rows, err := db.Query(`select id, name, description, quantity, price from goods where market_id = $1 order by name, description`, marketId)
if err != nil {
c.AbortWithError(500, err)
return
}
ret := make([]Goods, 0)
var g Goods
for rows.Next() {
err = rows.Scan(&g.Id, &g.Name, &g.Description, &g.Quantity, &g.Price)
if err != nil {
c.AbortWithError(500, err)
return
}
ret = append(ret, g)
}
c.JSON(200, gin.H{
"goods": ret,
})
}
func handleBuy(c *gin.Context) {
session := sessions.Default(c)
userId := session.Get("userid").(int64)
goodsId := c.Param("goodsid")
tx, err := db.Begin()
if err != nil {
c.AbortWithError(500, err)
return
}
_, err = tx.Exec(`insert into purchase (user_id, goods_id) values ($1, $2)`, userId, goodsId)
if err != nil {
tx.Rollback()
c.AbortWithError(500, err)
return
}
_, err = tx.Exec(`update goods set quantity = quantity - 1 where id = $1`, goodsId)
if err != nil {
tx.Rollback()
c.AbortWithError(500, err)
return
}
tx.Commit()
c.JSON(200, gin.H{})
}
func handleGetPurchaseHistory(c *gin.Context) {
session := sessions.Default(c)
userId := session.Get("userid").(int64)
rows, err := db.Query(`
select p.id, p.quantity, p.purchased_time, g.name, g.price
from purchase p
join goods g on p.goods_id = g.id
where p.user_id = $1
order by p.purchased_time desc
`, userId)
if err != nil {
c.AbortWithError(500, err)
return
}
type Ret struct {
Id int64 `json:"id"`
Quantity int64 `json:"quantity"`
PurchasedTime time.Time `json:"purchased_time"`
Goods Goods `json:"goods"`
}
ret := make([]Ret, 0)
for rows.Next() {
var i Ret
err = rows.Scan(&i.Id, &i.Quantity, &i.PurchasedTime, &i.Goods.Name, &i.Goods.Price)
if err != nil {
c.AbortWithError(500, err)
return
}
ret = append(ret, i)
}
c.JSON(200, ret)
}
func handleDeletePurchaseHistory(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("purchaseid"), 10, 64)
if err != nil {
c.AbortWithError(500, err)
return
}
_, err = db.Exec(`delete from purchase where id = $1`, id)
if err != nil {
c.AbortWithError(500, err)
return
}
c.JSON(200, gin.H{})
}
func handleGetGodsBySupplier(c *gin.Context) {
session := sessions.Default(c)
userId := session.Get("userid").(int64)
rows, err := db.Query(`select id, name, description, quantity, price from goods where supplier_id = $1 order by name, description`, userId)
if err != nil {
c.AbortWithError(500, err)
return
}
ret := make([]Goods, 0)
for rows.Next() {
var g Goods
err = rows.Scan(&g.Id, &g.Name, &g.Description, &g.Quantity, &g.Price)
if err != nil {
c.AbortWithError(500, err)
return
}
ret = append(ret, g)
}
c.JSON(200, ret)
}
func handleCreateGoods(c *gin.Context) {
session := sessions.Default(c)
userId := session.Get("userid").(int64)
g := &Goods{}
err := c.BindJSON(g)
if err != nil {
c.AbortWithError(500, err)
return
}
g.SupplierId = userId
_, err = db.Exec(`insert into goods (name, description, supplier_id, market_id, quantity, price) values ($1, $2, $3, $4, $5, $6)`,
g.Name, g.Description, g.SupplierId, g.MarketId, g.Quantity,g.Price)
if err != nil {
c.AbortWithError(500, err)
return
}
c.JSON(200, gin.H{})
}

60
pkg/api/handle_market.go Normal file
View File

@@ -0,0 +1,60 @@
package api
import "github.com/gin-gonic/gin"
type Market struct {
Id int64 `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Location string `json:"location"`
}
func handleGetMarket(c *gin.Context) {
markets := make([]Market, 0)
rows, err := db.Query(`select id, name, description, location from market`)
if err != nil {
c.AbortWithError(500, err)
return
}
for rows.Next() {
var market Market
err = rows.Scan(&market.Id, &market.Name, &market.Description, &market.Location)
if err != nil {
c.AbortWithError(500, err)
return
}
markets = append(markets, market)
}
c.JSON(200, gin.H{
"markets": markets,
})
}
func handleGetMarketByDistance(c *gin.Context) {
markets := make([]Market, 0)
point := c.Query("point")
rows, err := db.Query(`select id, name, description, location, location<->$1 as dist from market order by dist`, point)
if err != nil {
c.AbortWithError(500, err)
return
}
var x string
for rows.Next() {
var market Market
err = rows.Scan(&market.Id, &market.Name, &market.Description, &market.Location, &x)
if err != nil {
c.AbortWithError(500, err)
return
}
markets = append(markets, market)
}
c.JSON(200, gin.H{
"markets": markets,
})
}

28
pkg/api/handle_report.go Normal file
View File

@@ -0,0 +1,28 @@
package api
import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
func handleCustomerReport(c *gin.Context) {
session := sessions.Default(c)
userId := session.Get("userid").(int64)
row := db.QueryRow(`select sum(g.price)
from purchase p
join goods g on p.goods_id = g.id
where p.user_id = $1`,
userId)
var sumPrice string
err := row.Scan(&sumPrice)
if err != nil {
c.AbortWithError(500, err)
return
}
c.JSON(200, gin.H{
"sum": sumPrice,
})
}

100
pkg/api/handle_user.go Normal file
View File

@@ -0,0 +1,100 @@
package api
import (
"time"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
type User struct {
Id int64 `json:"id"`
Username string `json:"username"`
Password string `json:"password"`
Balance string `json:"balance"`
Location string `json:"location"`
Role int64 `json:"role"`
RegisterTime time.Time `json:"register_time"`
}
var SESSION_NAME = "ais"
func handelLogout(c *gin.Context) {
session := sessions.Default(c)
session.Clear()
session.Save()
c.JSON(200, gin.H{})
}
func handelGetLoginSession(c *gin.Context) {
session := sessions.Default(c)
userId := session.Get("userid")
if userId == nil {
c.JSON(200, gin.H{})
return
}
user := &User{}
row := db.QueryRow(`select id, username, balance, location, role from users where id=$1`, userId)
err := row.Scan(&user.Id, &user.Username, &user.Balance, &user.Location, &user.Role)
if err != nil {
c.AbortWithError(403, err)
return
}
c.JSON(200, user)
}
func handelLogin(c *gin.Context) {
user := &User{}
err := c.BindJSON(user)
if err != nil {
c.AbortWithError(500, err)
return
}
var encryptedPassowrd string
row := db.QueryRow(`select id, username, balance, location, role, password from users where username=$1`,
user.Username)
err = row.Scan(&user.Id, &user.Username, &user.Balance, &user.Location, &user.Role, &encryptedPassowrd)
if err != nil {
c.AbortWithError(403, err)
return
}
// validate password
err = ComparePassword(encryptedPassowrd, user.Password)
if err != nil {
c.AbortWithError(403, err)
return
}
// set session
session := sessions.Default(c)
session.Set("userid", user.Id)
session.Save()
c.JSON(200, user)
}
func handelRegister(c *gin.Context) {
user := &User{}
err := c.BindJSON(user)
if err != nil {
c.AbortWithError(401, err)
return
}
encryptedPassowrd := EncryptPassword(user.Password)
ret := db.QueryRow(`insert into users(username, password) values ($1, $2) returning id`,
user.Username, encryptedPassowrd)
err = ret.Scan(&user.Id)
if err != nil {
c.AbortWithError(401, err)
return
}
c.JSON(200, gin.H{})
}

75
pkg/api/install.go Normal file
View File

@@ -0,0 +1,75 @@
package api
import (
"log"
"strings"
)
var initSQLString string = `create table users(
id serial primary key,
username varchar(30) not null unique,
password varchar(64) not null,
balance money not null default 0,
role integer not null default 1,
location point not null default '(0, 0)',
registerd_time timestamp not null default now()
);
create table market (
id serial primary key,
name varchar(100) not null,
description text not null,
location point not null
)
create table tag (
id serial primary key,
name varchar(30) not null
);
create table goods (
id serial primary key,
name varchar(100) not null,
description text not null,
create_time timestamp not null default now(),
supplier_id integer not null references users(id),
market_id integer not null references market(id),
quantity numeric not null check (quantity >= 0),
price money not null,
data jsonb
);
create table tags_on_goods(
tag_id integer not null references tag(id),
goods_id integer not null references goods(id),
primary key (tag_id, goods_id)
);
create table purchase (
id serial primary key,
user_id integer not null references users(id),
goods_id integer not null references goods(id),
quantity numeric not null default 1,
purchased_time timestamp not null default now()
);
insert into users (username, password) values ('a', 'a');
`
func install() {
sqls := strings.Split(initSQLString, "\n\n")
tx, err := db.Begin()
if err != nil {
log.Fatal(err)
}
for _, sql := range sqls {
log.Println("Installing table with SQL", sql)
_, err = tx.Exec(sql)
if err != nil {
tx.Rollback()
log.Fatal(err)
}
}
tx.Commit()
log.Println("Successfully install all tables")
}