Compare commits

...

10 Commits

Author SHA1 Message Date
fallen-angle
9e3638885d finish 2022-05-16 19:55:59 +08:00
fallen-angle
7598280fc1 feat: management && notify 2022-05-04 20:06:21 +08:00
fallen-angle
e58bafd0d3 Merge branch 'master' of https://mygitea.fallen-angle.com/fallen-angle/nCovTrack-Backend 2022-04-30 11:32:04 +08:00
fallen-angle
2482141d0f comm: swagger doc 2022-04-30 11:30:55 +08:00
Zhaolong
28c65b73c3 fix: 'province' & AreaInfo add parent 2022-04-28 00:19:30 +08:00
Zhaolong
09b8e8e262 fix: statistic upstream url change & save article to private 2022-04-27 23:12:20 +08:00
fallen-angle
b92598ecc4 Merge branch 'master' of https://mygitea.fallen-angle.com/fallen-angle/nCovTrack-Backend
role merge
2022-04-27 22:08:30 +08:00
fallen-angle
22cb5ec61f fix: add role limit 2022-04-27 22:08:02 +08:00
fallen-angle
64a820a6df fix: add role limit 2022-04-27 19:12:51 +08:00
fallen-angle
fc347a4140 feat: temp upload 2022-04-13 19:48:10 +08:00
53 changed files with 5673 additions and 322 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
package config
package global
type ServerConfig struct {
Listen string `yaml:"listen"`
@@ -11,6 +11,8 @@ type ServerConfig struct {
Redis RedisConfig `yaml:"redis"`
Jwt JwtConfig `yaml:"jwt"`
Email EmailConfig `yaml:"email"`
Bos BosConfig `yaml:"bos"`
Kafka KafkaCofig `yaml:"kafka"`
}
type MySQLConfig struct {
@@ -39,3 +41,15 @@ type EmailConfig struct {
Account string `yaml:"account"`
Password string `yaml:"password"`
}
type BosConfig struct {
AccessKey string `yaml:"accessKey"`
SecretKey string `yaml:"secretKey"`
Domain string `yaml:"domain"`
}
type KafkaCofig struct {
Servers []string `yaml:"servers"`
Topic string `yaml:"topic"`
Partition int `yaml:"partition"`
}

View File

@@ -3,22 +3,22 @@ package global
import (
"errors"
"fmt"
"nCovTrack-Backend/config"
"net/http"
"github.com/baidubce/bce-sdk-go/services/bos"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis"
"go.uber.org/zap"
"gorm.io/gorm"
"net/http"
)
var (
ServerSettings config.ServerConfig
ServerSettings ServerConfig
Db *gorm.DB
RootRouter *gin.RouterGroup
Logger *zap.SugaredLogger
HttpClient map[string]*http.Client
Redis *redis.Client
BosClient *bos.Client
)
func GetListenOn() string {
@@ -34,10 +34,46 @@ func GetHttpClient(key string) (*http.Client, error) {
}
const (
CHINA_NCOV_STATISTIC_URL = "https://view.inews.qq.com/g2/getOnsInfo?name=disease_h5"
ENV_NOLOG = "nolog"
TOKEN_EXPIRE_DAYS = 15
CHINA_NCOV_STATISTIC_URL = "https://api.inews.qq.com/newsqa/v1/query/inner/publish/modules/list?modules=statisGradeCityDetail,diseaseh5Shelf"
CHINA_NCOV_STATISTIC_TREND_URL = "https://api.inews.qq.com/newsqa/v1/query/inner/publish/modules/list?modules=chinaDayList,chinaDayAddList"
ENV_NOLOG = "nolog"
TOKEN_EXPIRE_DAYS = 15
FACKER_HOST = "http://myhost.fallen-angle.com:5000/"
REGISTER_REDIS_KEY = "register_key"
CHANGEPWD_REDIS_KEY = "changepwd_key"
)
var (
ID_ROLE_MAP = map[int]string{
0: "SYSTEM",
4: "VOLUNTEER",
8: "WORKER",
12: "ADMIN",
}
ROLE_ID_MAP = map[string]int{
"SYSTEM": 0,
"VOLUNTEER": 4,
"WORKER": 8,
"ADMIN": 12,
}
HEALTH_SITUATION_ID_MAP = map[string]int{
"OTHER": 0,
"PATIENT": 1,
"CONTACT": 2,
"SUB_CONTACT": 3,
}
MEASURE_SITUATION_ID_MAP = map[string]int{
"NO_MEASURE": 0,
"NO_RISK": 1,
"TREATING": 2,
"CENTRALIZED": 3,
"HOME": 4,
}
PCR_RESULT_ID_MAP = map[string]int{
"NONE": 0,
"NEGATIVE": 1,
"POSITIVE": 2,
}
KafkaProducerChan = make(chan []byte)
)

20
go.mod
View File

@@ -18,6 +18,12 @@ require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/Shopify/sarama v1.32.0 // indirect
github.com/baidubce/bce-sdk-go v0.9.112 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/eapache/go-resiliency v1.2.0 // indirect
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 // indirect
github.com/eapache/queue v1.1.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
@@ -30,13 +36,21 @@ require (
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
github.com/jcmturner/gofork v1.0.0 // indirect
github.com/jcmturner/gokrb5/v8 v8.4.2 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.4 // indirect
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.15.2 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
@@ -48,6 +62,8 @@ require (
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/onsi/gomega v1.18.0 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/spf13/afero v1.8.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
@@ -58,8 +74,8 @@ require (
github.com/ugorji/go/codec v1.2.6 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.9 // indirect

54
go.sum
View File

@@ -44,8 +44,13 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/sarama v1.32.0 h1:P+RUjEaRU0GMMbYexGMDyrMkLhbbBVUVISDywi+IlFU=
github.com/Shopify/sarama v1.32.0/go.mod h1:+EmJJKZWVT/faR9RcOxJerP+LId4iWdQPBGLy1Y1Njs=
github.com/Shopify/toxiproxy/v2 v2.3.0/go.mod h1:KvQTtB6RjCJY4zqNJn7C7JDFgsG5uoHYDirfUfpIm0c=
github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs=
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
github.com/baidubce/bce-sdk-go v0.9.112 h1:qHzFxG7fwGbXCv+1smcbWFhWl6iwoXDVzPn9TUtrlss=
github.com/baidubce/bce-sdk-go v0.9.112/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -61,6 +66,12 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q=
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -69,6 +80,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
@@ -142,6 +155,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -155,6 +170,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -178,6 +194,12 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
@@ -185,6 +207,17 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.2 h1:6ZIM6b/JJN0X8UM43ZOM6Z4SJzla+a/u7scXFJzodkA=
github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
@@ -201,6 +234,9 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.2 h1:3WH+AG7s2+T8o3nrM/8u2rdqUEcQhmga7smjrT41nAw=
github.com/klauspost/compress v1.15.2/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
@@ -258,6 +294,8 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -266,6 +304,8 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -274,6 +314,7 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/afero v1.8.0 h1:5MmtuhAgYeU6qpa7w7bP0dv6MBYuup0vekhSpSkoq60=
@@ -311,6 +352,9 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.0/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -338,6 +382,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
@@ -345,6 +390,9 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+Wr
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI=
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -417,10 +465,13 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba h1:6u6sik+bn/y7vILcYkK3iwTBWN7WtBvB0+SZswQnbf8=
golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -454,6 +505,7 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -496,12 +548,14 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=

View File

@@ -1,14 +1,16 @@
package handler
import (
"github.com/gin-gonic/gin"
"nCovTrack-Backend/global"
"nCovTrack-Backend/models"
"nCovTrack-Backend/service/article"
"nCovTrack-Backend/utils"
"strconv"
"github.com/gin-gonic/gin"
)
// SaveArticleHandler save an article
//SaveArticleHandler save an article
// @Tags Article
// @Accept json
// @Produce json
@@ -19,33 +21,64 @@ import (
// @Param Token header string true "token"
func SaveArticleHandler(c *gin.Context) {
jsonMap := bindJson(c)
claims := utils.ClaimsFromHeader(c)
if claims.Role != global.ROLE_ID_MAP["ADMIN"] {
Forbidden(c)
return
}
if jsonMap == nil {
RequestErr(c, map[string]interface{}{"URI": c.Request.RequestURI})
return
}
colMap := models.MapJ2c[models.BackArticle](jsonMap, true)
if ok := article.SaveArticle(colMap); !ok {
if ok := article.SaveArticle(claims, colMap); !ok {
ServerErr(c, "Save Failed")
return
}
utils.Succ(c, jsonMap)
}
// GetAllArticlesHandler get all article
//ListPublishedArticlesHandler get all article
// @Tags Article
// @Accept json
// @Produce json
// @Summary get all articles
// @Description Admin can get not published article
// @Success 200 {object} utils.GinResponse{data=[]models.BackArticle}
// @Success 200 {object} utils.GinResponse{data=[]models.ListArticle}
// @Router /article/list [get]
// @Param Token header string false "token"
func GetAllArticlesHandler(c *gin.Context) {
// TODO: admin need to show more articles
articles := article.ListAllArticles()
func ListPublishedArticlesHandler(c *gin.Context) {
articles := article.ListPublishedArticles()
utils.Succ(c, articles)
}
// DeleteArticleHandler delete article
//ListPublishedArticlesHandler get the user's article
// @Tags Article
// @Accept json
// @Produce json
// @Summary get user's articles
// @Success 200 {object} utils.GinResponse{data=[]models.BackArticle}
// @Router /article/list/{published} [get]
// @Param Token header string false "token"
// @Param published path string true "string enums" Enums(published, notpublished)
func ListArticlesByUser(c *gin.Context) {
published := c.Param("published")
claims := utils.ClaimsFromHeader(c)
if claims.Role != global.ROLE_ID_MAP["ADMIN"] {
Forbidden(c)
return
}
var articles *[]models.ListArticle
if published == "published" {
articles = article.ListPublishedArticlesByUser(claims.ID)
} else if published == "notpublished" {
articles = article.ListNotPublishedArticlesByUser(claims.ID)
} else {
UrlNotFound(c)
return
}
utils.Succ(c, articles)
}
//DeleteArticleHandler delete article
// @Tags Article
// @Accept json
// @Produce json
@@ -56,6 +89,11 @@ func GetAllArticlesHandler(c *gin.Context) {
// @Param id path string true "id"
func DeleteArticleHandler(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
claims := utils.ClaimsFromHeader(c)
if claims.Role != global.ROLE_ID_MAP["ADMIN"] {
Forbidden(c)
return
}
if err != nil {
RequestErr(c, map[string]interface{}{"URI": c.Request.RequestURI})
return
@@ -67,15 +105,14 @@ func DeleteArticleHandler(c *gin.Context) {
utils.Succ(c, nil)
}
// GetArticleHandler get an article
//GetArticleHandler get an article
// @Tags Article
// @Accept json
// @Produce json
// @Summary get all articles
// @Summary get an articles
// @Description Admin can get not published article
// @Success 200 {object} utils.GinResponse{data=models.BackArticle}
// @Router /article/{id} [get]
// @Param Token header string false "token"
// @Param id path string true "id"
func GetArticleHandler(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
@@ -84,7 +121,6 @@ func GetArticleHandler(c *gin.Context) {
return
}
res := article.GetArticleById(id)
//TODO: if not admin, will not show not published article
if res == nil {
DataNotFound(c, nil)
return
@@ -92,17 +128,22 @@ func GetArticleHandler(c *gin.Context) {
utils.Succ(c, res)
}
// PublishArticleHandler publish an article
//PublishArticleHandler publish an article
// @Tags Article
// @Accept json
// @Produce json
// @Summary get all articles
// @Summary publish an articles
// @Success 200 {object} utils.GinResponse{}
// @Router /article/{id}/publish [post]
// @Param Token header string true "token"
// @Param id path string true "id"
func PublishArticleHandler(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
claims := utils.ClaimsFromHeader(c)
if claims.Role != global.ROLE_ID_MAP["ADMIN"] {
Forbidden(c)
return
}
if err != nil {
RequestErr(c, map[string]interface{}{"URI": c.Request.RequestURI})
return

View File

@@ -9,9 +9,13 @@ import (
// This file is define some business error
const (
BAD_REQUEST = "Bad Request"
DATA_NOT_FOUND = "Data not Found"
STATUS_DATA_NOT_FOUND = 210
BAD_REQUEST = "Bad Request"
DATA_NOT_FOUND = "Data not Found"
STATUS_DATA_NOT_FOUND = 210
FORBIDDENT = "FORBIDDENT"
PAGE_NOT_FOUND = "404 page not found"
STATUS_OPERATION_FAILED = 410
OPERATION_FAILED = "operation failed"
)
func RequestError(c *gin.Context, code int, data interface{}) {
@@ -31,3 +35,14 @@ func ServerErr(c *gin.Context, msg interface{}) {
func DataNotFound(c *gin.Context, data interface{}) {
utils.Success(c, http.StatusOK, STATUS_DATA_NOT_FOUND, DATA_NOT_FOUND, data)
}
func Forbidden(c *gin.Context) {
utils.Err(c, http.StatusForbidden, http.StatusForbidden, FORBIDDENT)
}
func UrlNotFound(c *gin.Context) {
c.String(http.StatusNotFound, PAGE_NOT_FOUND)
}
func OperationFailed(c *gin.Context) {
c.String(STATUS_OPERATION_FAILED, OPERATION_FAILED)
}

234
handler/management.go Normal file
View File

@@ -0,0 +1,234 @@
package handler
import (
"nCovTrack-Backend/global"
"nCovTrack-Backend/service/management"
"nCovTrack-Backend/utils"
"strconv"
"github.com/gin-gonic/gin"
)
//PullContactHandler pull contact from faker
// @Tags Management
// @Produce json
// @Summary pull contact from faker
// @Success 200 {object} utils.GinResponse
// @Router /management/faker/{patientId} [get]
// @Param Token header string true "Token"
// @Param patientId path int true "Patient Id"
func PullContactHandler(c *gin.Context) {
claims := utils.ClaimsFromHeader(c)
if claims.Role != global.ROLE_ID_MAP["ADMIN"] {
Forbidden(c)
return
}
patientId, _ := strconv.Atoi(c.Param("patientId"))
rowsAffected := management.PullFromFaker(claims, patientId)
if rowsAffected == -1 {
DataNotFound(c, map[string]interface{}{"patientId": patientId})
return
}
utils.Succ(c, map[string]interface{}{"pullAmount": rowsAffected})
}
//GetObservationHandler get observation's info
// @Tags Management
// @Produce json
// @Summary get observation's info
// @Success 200 {object} utils.GinResponse{data=models.BackObservation}
// @Router /management/observation/{id} [get]
// @Param token header string true "token"
// @Param id path int true "id"
func GetObservationHandler(c *gin.Context) {
claims := utils.ClaimsFromHeader(c)
allowRows := []int{global.ROLE_ID_MAP["ADMIN"], global.ROLE_ID_MAP["WORKER"], global.ROLE_ID_MAP["VOLUNTEER"]}
if !utils.Contains(allowRows, claims.Role) {
Forbidden(c)
return
}
id, _ := strconv.Atoi(c.Param("id"))
utils.Succ(c, management.GetObservation(id))
}
//ListObservationsHandler list observations by query
// @Tags Management
// @Produce json
// @Summary list observations by query
// @Success 200 {object} utils.GinResponse{data=[]models.ListObservation}
// @Router /management/observation/ [get]
// @Param Token header string true "token"
// @Param queries query models.BackObservation true "queries"
func ListObservationsHandler(c *gin.Context) {
claims := utils.ClaimsFromHeader(c)
allowRows := []int{global.ROLE_ID_MAP["ADMIN"], global.ROLE_ID_MAP["WORKER"], global.ROLE_ID_MAP["VOLUNTEER"]}
if !utils.Contains(allowRows, claims.Role) {
Forbidden(c)
return
}
jsonMap := bindQuery(c)
utils.Succ(c, management.ListObservation(jsonMap))
}
//TreeifyObservationHandler get transform chain by user
// @Tags Management
// @Produce json
// @Summary get transform chain by user
// @Success 200 {object} utils.GinResponse{data=models.TreeObservation}
// @Router /management/observation/tree/{id}/{direction} [get]
// @Param Token header string true "token"
// @Param id path int true "id"
// @Param direction path string true "direction" Enums(up, down, all)
func TreeifyObservationHandler(c *gin.Context) {
claims := utils.ClaimsFromHeader(c)
allowRows := []int{global.ROLE_ID_MAP["ADMIN"], global.ROLE_ID_MAP["WORKER"], global.ROLE_ID_MAP["VOLUNTEER"]}
if !utils.Contains(allowRows, claims.Role) {
Forbidden(c)
return
}
id, _ := strconv.Atoi(c.Param("id"))
direction := c.Param("direction")
tree := management.TreeifyObservations(id, direction)
utils.Succ(c, tree)
}
//InsertObservationHandler insert observation
// @Tags Management
// @Accept json
// @Produce json
// @Summary insert observation
// @Success 200 {object} utils.GinResponse
// @Router /management/observation/ [post]
// @Param Observation body models.BackObservation true "observation"
// @Param Token header string true "token"
func InsertObservationHandler(c *gin.Context) {
claims := utils.ClaimsFromHeader(c)
allowRows := []int{global.ROLE_ID_MAP["ADMIN"], global.ROLE_ID_MAP["WORKER"]}
if !utils.Contains(allowRows, claims.Role) {
Forbidden(c)
return
}
observaion := bindJson(c)
delete(observaion, "id")
ok := management.InsertObservation(claims, observaion)
if ok {
utils.Succ(c, nil)
} else {
OperationFailed(c)
}
}
//UpdateObservationHandler update observation
// @Tags Management
// @Accept json
// @Produce json
// @Summary update observation
// @Success 200 {object} utils.GinResponse
// @Router /management/observation/ [put]
// @Param Observation body models.BackObservation true "observation"
// @Param Token header string true "token"
func UpdateObservationHandler(c *gin.Context) {
claims := utils.ClaimsFromHeader(c)
allowRows := []int{global.ROLE_ID_MAP["ADMIN"], global.ROLE_ID_MAP["WORKER"]}
if !utils.Contains(allowRows, claims.Role) {
Forbidden(c)
return
}
observation := bindJson(c)
ok := management.UpdateObservation(claims, observation)
if ok {
utils.Succ(c, nil)
} else {
OperationFailed(c)
}
}
//ListLocationHandler list locations
// @Tags Management
// @Produce json
// @Summary list locations
// @Success 200 {object} utils.GinResponse{data=models.BackLocation}
// @Router /management/location/ [get]
// @Param queries query models.BackLocation true "queries"
// @Param Token header string true "token"
func ListLocationHandler(c *gin.Context) {
claims := utils.ClaimsFromHeader(c)
allowRows := []int{global.ROLE_ID_MAP["ADMIN"], global.ROLE_ID_MAP["WORKER"], global.ROLE_ID_MAP["VOLUNTEER"]}
if !utils.Contains(allowRows, claims.Role) {
Forbidden(c)
return
}
queryMap := bindQuery(c)
utils.Succ(c, management.ListLocation(queryMap))
}
//InsertLocationHandler insert location
// @Tags Management
// @Accept json
// @Produce json
// @Summary insert location
// @Success 200 {object} utils.GinResponse
// @Router /management/location/ [post]
// @Param Location body models.BackLocation true "location"
// @Param Token header string true "token"
func InsertLocationHandler(c *gin.Context) {
claims := utils.ClaimsFromHeader(c)
if claims.Role != global.ROLE_ID_MAP["ADMIN"] {
Forbidden(c)
return
}
locationMap := bindJson(c)
ok := management.InsertLocation(claims, locationMap)
if ok {
utils.Succ(c, nil)
} else {
OperationFailed(c)
}
}
//DeleteLocationHandler delete location
// @Tags Management
// @Produce json
// @Summary delete location
// @Success 200 {object} utils.GinResponse
// @Router /management/location/{id} [delete]
// @Param Token header string true "token"
// @Param Id path int true "Id"
func DeleteLocationHandler(c *gin.Context) {
claims := utils.ClaimsFromHeader(c)
if claims.Role != global.ROLE_ID_MAP["ADMIN"] {
Forbidden(c)
return
}
id, _ := strconv.Atoi(c.Param("id"))
ok := management.DeleteLocation(id)
if ok {
utils.Succ(c, nil)
} else {
OperationFailed(c)
}
}
//InsertPcrHandler insert pcr record
// @Tags Management
// @Produce json
// @Summary insert pcr record
// @Success 200 {object} utils.GinResponse
// @Router /management/pcr/ [post]
// @Param Pcr body models.BackPcr true "Pcr"
// @Param Token header string true "token"
func InsertPcrHandler(c *gin.Context) {
claims := utils.ClaimsFromHeader(c)
allowRows := []int{global.ROLE_ID_MAP["ADMIN"], global.ROLE_ID_MAP["WORKER"]}
if !utils.Contains(allowRows, claims.Role) {
Forbidden(c)
return
}
jsonMap := bindJson(c)
ok := management.InsertPcr(claims, jsonMap)
if ok {
utils.Succ(c, nil)
} else {
OperationFailed(c)
}
}

65
handler/notify.go Normal file
View File

@@ -0,0 +1,65 @@
package handler
import (
"nCovTrack-Backend/models"
"nCovTrack-Backend/service/notify"
"nCovTrack-Backend/utils"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
//InsertNotificationHandler for test
func InsertNotificationHandler(c *gin.Context) {
sendNotify := models.SendInfo{Region: []string{"江苏 徐州"}, Channel: []int{1, 0}, Notification: models.BackNotification{Time: time.Now(), Kind: "测试", Content: "Test"}}
notify.SendNotify(sendNotify)
}
//CountNotificationHandler count the notification
// @Tags Notification
// @Produce json
// @Summary count the notification
// @Success 200 {object} utils.GinResponse
// @Router /notify/count/ [get]
// @Param Token header string true "token"
func CountNotificationHandler(c *gin.Context) {
claims := utils.ClaimsFromHeader(c)
count := notify.QueryNotificationLen(claims.Region)
utils.Succ(c, map[string]interface{}{"count": count})
}
//ListNotificationHandler list the notifications in the range
// @Tags Notification
// @Produce json
// @Summary list the notification in the range
// @Success 200 {object} utils.GinResponse{data=[]models.BackNotification}
// @Router /notify/{start}/{end} [get]
// @Param Token header string true "token"
// @Param start path int true "start"
// @Param end path int true "end"
func ListNotificationHandler(c *gin.Context) {
claims := utils.ClaimsFromHeader(c)
start, _ := strconv.Atoi(c.Param("start"))
end, _ := strconv.Atoi(c.Param("end"))
notifications := notify.ListNotifycation(claims.Region, start, end)
utils.Succ(c, notifications)
}
//DeleteNotificationHandler delete the notify by index
// @Tags Notification
// @Produce json
// @Summary delete the notify by index, if -1 delete all
// @Success 200 {object} utils.GinResponse
// @Router /notify/{index} [delete]
// @Param Token header string true "token"
// @Param index path int true "index"
func DeleteNotificationHandler(c *gin.Context) {
claims := utils.ClaimsFromHeader(c)
index, _ := strconv.Atoi(c.Param("index"))
if index == -1 {
notify.CleanNotification(claims.Region)
}
notify.DeleteNotification(claims.Region, index)
utils.Succ(c, nil)
}

View File

@@ -17,7 +17,7 @@ import (
// @Param sort path string false "data sorted by" Enums(today, total, now, default)
func ProvinceDataHandler(c *gin.Context) {
sort := c.Param("sort")
data := service.GetAllProvienceData(sort)
data := service.GetAllProvinceData(sort)
utils.Succ(c, data)
}
@@ -57,3 +57,14 @@ func ChinaDataHandler(c *gin.Context) {
data := service.GetChinaNCovStatistic()
utils.Succ(c, data)
}
// ChinaTrendHandler Get china data
// @Tags Statistics
// @Produce json
// @Summary china trend
// @Success 200 {object} utils.GinResponse{data=models.ChinaTrend}
// @Router /statistics/china/trend [get]
func ChinaTrendHandler(c *gin.Context) {
data := service.GetChinaTrend()
utils.Succ(c, data)
}

View File

@@ -1,11 +1,13 @@
package handler
import (
"github.com/gin-gonic/gin"
"nCovTrack-Backend/global"
"nCovTrack-Backend/models"
"nCovTrack-Backend/service/user"
"nCovTrack-Backend/utils"
"regexp"
"github.com/gin-gonic/gin"
)
//UserRegisterHandler user register
@@ -13,7 +15,7 @@ import (
// @Accept json
// @Produce json
// @Summary user register account
// @Success 200 {object} utils.GinResponse{}
// @Success 200 {object} utils.GinResponse{data=models.BackUser}
// @Router /user/register [post]
// @Param json body models.UserRegister true "json"
func UserRegisterHandler(c *gin.Context) {
@@ -22,11 +24,13 @@ func UserRegisterHandler(c *gin.Context) {
return
}
registered := user.NoDuplicatePhoneOrEmail(jsonMap["phone"].(string), jsonMap["email"].(string))
if registered {
if !registered {
utils.Success(c, 200, 200, "Registered", nil)
return
}
colMap := models.MapJ2c[models.BackUser](jsonMap, true)
user.Register(colMap)
utils.Succ(c, nil)
}
//UserApproveHandler admin approve account
@@ -39,12 +43,16 @@ func UserRegisterHandler(c *gin.Context) {
// @Param Token header string true "token"
// @Param json body models.UserApprove true "json"
func UserApproveHandler(c *gin.Context) {
//TODO: auth user is admin or not
claims := utils.ClaimsFromHeader(c)
if claims.Role != global.ROLE_ID_MAP["ADMIN"] {
Forbidden(c)
return
}
jsonMap := bindJsonStruct[models.UserApprove](c)
if jsonMap == nil {
return
}
if !user.ApproveRegister(jsonMap["email"].(string), jsonMap["pass"].(bool)) {
if !user.ApproveRegister(claims, jsonMap["email"].(string), jsonMap["pass"].(bool)) {
RequestErr(c, "approve failed")
return
}
@@ -66,7 +74,9 @@ func UserLoginHandler(c *gin.Context) {
}
token := user.Login(jsonMap)
if token == "" {
// TODO: change to request error
// Login failed reasons as follow:
// 1. account or password incorrect
// 2. account apply not pass
utils.Succ(c, map[string]interface{}{"msg": "failed"})
return
}
@@ -79,10 +89,25 @@ func UserLoginHandler(c *gin.Context) {
// @Produce json
// @Summary list register infos, which is to be approved
// @Success 200 {object} utils.GinResponse{}
// @Router /user/registers [get]
// @Router /user/registers/{approved} [get]
// @Param Token header string true "token"
// @Param approved path string true "string enums" Enums(approved, notapproved)
func ListRegisterUserHandler(c *gin.Context) {
registers := user.ListRegister()
approved := c.Param("approved")
claims := utils.ClaimsFromHeader(c)
if claims.Role != global.ROLE_ID_MAP["ADMIN"] {
Forbidden(c)
return
}
var registers *[]map[string]interface{}
if approved == "notapproved" {
registers = user.ListRegister(claims)
} else if approved == "approved" {
registers = user.ListApprovedRegister(claims)
} else {
UrlNotFound(c)
return
}
utils.Succ(c, registers)
}
@@ -91,7 +116,7 @@ func ListRegisterUserHandler(c *gin.Context) {
// @Produce json
// @Summary send verify code
// @Success 200 {object} utils.GinResponse{}
// @Router /user/{code} [get]
// @Router /user/code/{email} [get]
// @Param email path string true "email"
func SendEmailCodeHandler(c *gin.Context) {
email := c.Param("email")

View File

@@ -2,6 +2,7 @@ package handler
import (
"encoding/json"
"github.com/gin-gonic/gin"
)
@@ -46,3 +47,12 @@ func bindJsonStruct[T any](c *gin.Context) map[string]interface{} {
}
return jsonMap
}
func bindQuery(c *gin.Context) map[string]interface{} {
jsonMap := map[string]interface{}{}
queries := c.Request.URL.Query()
for k := range queries {
jsonMap[k] = c.Query(k)
}
return jsonMap
}

20
initialize/bos.go Normal file
View File

@@ -0,0 +1,20 @@
package initialize
import (
"github.com/baidubce/bce-sdk-go/services/bos"
"nCovTrack-Backend/global"
)
func initBos() {
clientConfig := bos.BosClientConfiguration{
global.ServerSettings.Bos.AccessKey,
global.ServerSettings.Bos.SecretKey,
global.ServerSettings.Bos.Domain,
false,
}
bosClient, err := bos.NewClientWithConfig(&clientConfig)
if err != nil {
panic(err)
}
global.BosClient = bosClient
}

View File

@@ -2,11 +2,9 @@ package initialize
import (
"fmt"
"nCovTrack-Backend/config"
"nCovTrack-Backend/global"
"github.com/fatih/color"
"github.com/spf13/viper"
"nCovTrack-Backend/global"
)
func initConfig() {
@@ -16,7 +14,7 @@ func initConfig() {
if err := v.ReadInConfig(); err != nil {
panic(err)
}
serverConfig := config.ServerConfig{}
serverConfig := global.ServerConfig{}
if err := v.Unmarshal(&serverConfig); err != nil {
panic(err)
}

View File

@@ -10,5 +10,6 @@ func initCron() {
c := cron.New()
//c.AddFunc("@every 10s", func() { global.Redis.Set("OK", time.Now().String(), time.Duration(10*time.Hour)) })
c.AddFunc("@every 10m", statistics.CacheNCov)
c.AddFunc("@every 8h", statistics.CacheNCovTrend)
c.Start()
}

View File

@@ -8,7 +8,10 @@ func Initialize() *gin.Engine {
initHttpClient()
initMySQL()
initRedis()
initBos()
initCron()
go initProducer()
go initConsumer()
g := initRouter()
initSwagger()
return g

70
initialize/kafka.go Normal file
View File

@@ -0,0 +1,70 @@
package initialize
import (
"nCovTrack-Backend/global"
"nCovTrack-Backend/service/notify"
"time"
"github.com/Shopify/sarama"
)
func initProducer() {
config := sarama.NewConfig()
config.Producer.RequiredAcks = sarama.WaitForAll
config.Producer.Partitioner = sarama.NewRandomPartitioner
config.Producer.Return.Successes = true
config.Producer.Return.Errors = true
producer, err := sarama.NewSyncProducer(global.ServerSettings.Kafka.Servers, config)
if err != nil {
panic("kafka init failed")
}
// defer producer.Close()
for {
select {
case value := <-global.KafkaProducerChan:
producer.SendMessage(&sarama.ProducerMessage{Topic: global.ServerSettings.Kafka.Topic, Key: nil, Value: sarama.ByteEncoder(value)})
}
}
}
func initConsumer() {
config := sarama.NewConfig()
config.Consumer.Offsets.CommitInterval = 1 * time.Second
client, err := sarama.NewClient(global.ServerSettings.Kafka.Servers, config)
if err != nil {
panic("kafka init failed")
}
defer client.Close()
offsetManager, err := sarama.NewOffsetManagerFromClient("", client)
if err != nil {
panic("kafka init failed")
}
defer offsetManager.Close()
partitionOffsetManager, err := offsetManager.ManagePartition(global.ServerSettings.Kafka.Topic, int32(global.ServerSettings.Kafka.Partition))
if err != nil {
panic("kafka init failed")
}
defer partitionOffsetManager.Close()
offsetNewest, _ := partitionOffsetManager.NextOffset()
consumer, err := sarama.NewConsumer(global.ServerSettings.Kafka.Servers, config)
if err != nil {
panic("kafka init failed")
}
defer consumer.Close()
partitionConsumer, err := consumer.ConsumePartition(global.ServerSettings.Kafka.Topic, int32(global.ServerSettings.Kafka.Partition), offsetNewest)
if err != nil {
panic("kafka init failed")
}
defer partitionConsumer.Close()
for {
select {
case msg := <-partitionConsumer.Messages():
ConsumeFunc(msg.Value)
}
}
}
func ConsumeFunc(msg []byte) {
notify.HandleKafkaNotify(msg)
}

View File

@@ -9,6 +9,6 @@ import (
)
func initSwagger() {
docs.SwaggerInfo_swagger.BasePath = "/api" + global.ServerSettings.UrlPrefix
docs.SwaggerInfo.BasePath = "/api" + global.ServerSettings.UrlPrefix
global.RootRouter.GET("/swagger/*any", swagger.WrapHandler(swaggerFile.Handler))
}

View File

@@ -3,11 +3,20 @@ package main
import (
"nCovTrack-Backend/global"
"nCovTrack-Backend/initialize"
"os"
"os/signal"
"syscall"
)
// @title nCov Tracker
// @version 1.0
func main() {
signals := make(chan os.Signal)
signal.Notify(signals, os.Interrupt, syscall.SIGTERM)
go func() {
<-signals
os.Exit(1)
}()
gin := initialize.Initialize()
gin.Run(global.GetListenOn())
}

View File

@@ -1,14 +1,16 @@
package middleware
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
"encoding/json"
"nCovTrack-Backend/global"
"nCovTrack-Backend/models"
"nCovTrack-Backend/utils"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
)
const UNAUTH_MSG = "unauthorized"
@@ -24,9 +26,15 @@ func Auth() gin.HandlerFunc {
}
// Write the field of token to request header
claims := utils.ParseClaims(oldToken[0])
c.Request.Header.Set("role", fmt.Sprint(claims["role"]))
c.Request.Header.Set("email", claims["email"].(string))
c.Request.Header.Set("id", fmt.Sprint(claims["id"]))
tokenClaims := models.TokenClaims{
ID: int(claims["id"].(float64)),
Username: claims["username"].(string),
Email: claims["email"].(string),
Role: int(claims["role"].(float64)),
Region: claims["region"].(string),
}
claimsByte, _ := json.Marshal(tokenClaims)
c.Request.Header.Add("claims", string(claimsByte))
// renew token, and judge the token's iat is expired or not
renewToken := utils.RenewToken(oldToken[0])

View File

@@ -13,7 +13,7 @@ func Cors() gin.HandlerFunc {
c.Header("Access-Control-Allow-Origin", origin)
c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id")
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS,DELETE,PUT")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, X-Token, Access-Control-Allow-Headers, Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")
if method == "OPTIONS" {

View File

@@ -6,11 +6,11 @@ import (
// BackArticle article struct
type BackArticle struct {
ID int `gorm:"primaryKey;column:id" json:"-"` // 文章id
ID int `gorm:"primaryKey;column:id" json:"id"` // 文章id
CreateTime time.Time `gorm:"column:create_time" json:"createTime"` // 文章新建时间
CreateUser string `gorm:"column:create_user" json:"createUser"` // 文章创建者id
CreateUser int `gorm:"column:create_user" json:"createUser"` // 文章创建者id
ModifyTime time.Time `gorm:"column:modify_time" json:"modifyTime"` // 文章最后更新时间
ModifyUser string `gorm:"column:modify_user" json:"modifyUser"` // 文章最后更新者id
ModifyUser int `gorm:"column:modify_user" json:"modifyUser"` // 文章最后更新者id
Title string `gorm:"column:title" json:"title"` // 文章标题
Tags string `gorm:"column:tags" json:"tags"` // 文章Tag
Resume string `gorm:"column:resume" json:"resume"` // 文章简述
@@ -20,16 +20,17 @@ type BackArticle struct {
IsDelete int8 `gorm:"column:is_delete" json:"isDelete"` // 删除标志
}
type ListArticle struct {
ID int `json:"id"`
Username string `json:"username"`
CreateTime time.Time `json:"createTime"`
ModifyTime time.Time `json:"modifyTime"`
Title string `json:"title"`
Tags string `json:"tags"`
Resume string `json:"resume"`
Cover string `json:"cover"`
}
func init() {
initJcMap[BackArticle]()
}
//func ArticleMapJ2c(jsonMap map[string]interface{}, ignoreNil bool) map[string]interface{} {
// colMap := make(map[string]interface{})
// for k, v := range jsonMap {
// if colKey := colKey != "" && (!ignoreNil && v == nil) {
// colMap[colKey] = v
// }
// }
// return colMap
//}

1
models/kafka.go Normal file
View File

@@ -0,0 +1 @@
package models

177
models/management.go Normal file
View File

@@ -0,0 +1,177 @@
package models
import (
"fmt"
"time"
)
type BackObservation struct {
ID int `gorm:"primaryKey;column:id" json:"id"` // ID
Name string `gorm:"column:name" json:"name"` // 姓名
Age int `gorm:"column:age" json:"age"` // 年龄
Sex int `gorm:"column:sex" json:"sex"` // 性别
Phone string `gorm:"column:phone" json:"phone"` // 手机号码
Identification string `gorm:"column:identification" json:"identification"` // 身份证号
ContactPerson int `gorm:"column:contact_person" json:"contactPerson"` // 接触者id
Region string `gorm:"column:region" json:"region"` // 受观察者所在区域
Address string `gorm:"column:address" json:"address"` // 受观察者所在具体地点
HealthSituation int `gorm:"column:health_situation" json:"healthSituation"` // 被观察者的疫情状况0- 其他1-患者2-密接3-次密接
HealthChangeTime time.Time `gorm:"column:health_change_time" json:"healthChangeTime"` // 患者健康状况转化时间
MeasureSituation int `gorm:"column:measure_situation" json:"measureSituation"` // 受观察者被采取措施状况 0-未采取措施1-解除风险2-正在治疗3-集中隔离4-居家隔离
Trajectory string `gorm:"column:trajectory" json:"trajectory"` // 行程轨迹
CreateUser int `gorm:"column:create_user" json:"createUser"` // 创建者
CreateTime time.Time `gorm:"column:create_time" json:"createTime"` // 创建时间
ModifyUser int `gorm:"column:modify_user" json:"modifyUser"` // 修改者
ModifyTime time.Time `gorm:"column:modify_time" json:"modifyTime"` // 修改时间
IsDelete int `gorm:"column:is_delete" json:"isDelete"` // 删除标志
}
type BackLocation struct {
ID int `gorm:"primaryKey;column:id" json:"id"` // ID
Name string `gorm:"column:name" json:"name"` // 地点名
Region string `gorm:"column:region" json:"region"` // 地点所在地区
Address string `gorm:"column:address" json:"address"` // 地点的精确地址
PrincipalName string `gorm:"column:principal_name" json:"principalName"` // 负责人姓名
PrincipalPhone string `gorm:"column:principal_phone" json:"principalPhone"` // 负责人电话
CreateUser int `gorm:"column:create_user" json:"createUser"` // 创建者
CreateTime time.Time `gorm:"column:create_time" json:"createTime"` // 创建时间
ModifyUser int `gorm:"column:modify_user" json:"modifyUser"` // 修改者
ModifyTime time.Time `gorm:"column:modify_time" json:"modifyTime"` // 修改时间
IsDelete int `gorm:"column:is_delete" json:"isDelete"` // 删除标志
}
type BackPcr struct {
ID int `gorm:"primaryKey;column:id" json:"id"` // ID
Observation int `gorm:"column:observation" json:"observation"` // 观察对象ID
DetectTime time.Time `gorm:"column:detect_time" json:"detectTime"` // 核酸检测时间
DetectResult int `gorm:"column:detect_result" json:"detectResult"` // 核酸检测结果0-未检测1-阴性2-阳性
CreateUser int `gorm:"column:create_user" json:"createUser"` // 创建者
CreateTime time.Time `gorm:"column:create_time" json:"createTime"` // 创建时间
ModifyUser int `gorm:"column:modify_user" json:"modifyUser"` // 修改者
ModifyTime time.Time `gorm:"column:modify_time" json:"modifyTime"` // 修改时间
IsDelete int `gorm:"column:is_delete" json:"isDelete"` // 删除标志
}
type BackSituationRecord struct {
ID int `gorm:"primaryKey;column:id" json:"id"` // ID
Observation int `gorm:"column:observation" json:"observation"` // 观察对象
Record string `gorm:"column:record" json:"record"` // 状态转化记录
CreateUser int `gorm:"column:create_user" json:"createUser"` // 创建者
CreateTime time.Time `gorm:"column:create_time" json:"createTime"` // 创建时间
ModifyUser int `gorm:"column:modify_user" json:"modifyUser"` // 修改者
ModifyTime time.Time `gorm:"column:modify_time" json:"modifyTime"` // 修改时间
IsDelete int `gorm:"column:is_delete" json:"isDelete"` // 删除标志
}
type ListObservation struct {
ID int `json:"id"`
Name string `json:"name"` // 姓名
Age int `json:"age"` // 年龄
Sex int `json:"sex"` // 性别
Phone string `json:"phone"` // 手机号码
Identification string `json:"identification"` // 身份证号
ContactPerson int `json:"contactPerson"` // 接触者id
Region string `json:"region"` // 受观察者所在区域
Address string `json:"address"` // 受观察者所在具体地点
HealthSituation int `json:"healthSituation"` // 被观察者的疫情状况0- 其他1-患者2-密接3-次密接
HealthChangeTime time.Time `json:"healthChangeTime"` // 患者健康状况转化时间
MeasureSituation int `json:"measureSituation"` // 受观察者被采取措施状况 0-未采取措施1-解除风险2-正在治疗3-集中隔离4-居家隔离
Trajectory string `json:"trajectory"` // 行程轨迹
CreateUser int `json:"createUser"` // 创建者
CreateTime time.Time `json:"createTime"` // 创建时间
ModifyUser int `json:"modifyUser"` // 修改者
ModifyTime time.Time `json:"modifyTime"` // 修改时间
PcrTime time.Time `json:"pcrTime"` // 核酸时间
PcrResult int `json:"pcrResult"` // 核酸结果0-未检测1-阴性2-阳性
RecordTime time.Time `json:"recordTime"` // 状态转换时间
Record string `json:"record"` // 状态转换内容
}
type TreeObservation struct {
ID int `json:"id"`
Name string `json:"name"` // 姓名
Age int `json:"age"` // 年龄
Sex int `json:"sex"` // 性别
Phone string `json:"phone"` // 手机号码
Identification string `json:"identification"` // 身份证号
ContactPerson int `json:"contactPerson"` // 接触者id
Region string `json:"region"` // 受观察者所在区域
Address string `json:"address"` // 受观察者所在具体地点
HealthSituation int `json:"healthSituation"` // 被观察者的疫情状况0- 其他1-患者2-密接3-次密接
HealthChangeTime time.Time `json:"healthChangeTime"` // 患者健康状况转化时间
MeasureSituation int `json:"measureSituation"` // 受观察者被采取措施状况 0-未采取措施1-解除风险2-正在治疗3-集中隔离4-居家隔离
Trajectory string `json:"trajectory"` // 行程轨迹
CreateUser int `json:"createUser"` // 创建者
CreateTime time.Time `json:"createTime"` // 创建时间
ModifyUser int `json:"modifyUser"` // 修改者
ModifyTime time.Time `json:"modifyTime"` // 修改时间
Children []*TreeObservation `json:"children"` // 子结点
}
type QueryObservation struct {
BackObservation
PcrRecord []BackPcr `json:"pcrRecord"`
SituationRecord []BackSituationRecord `json:"situationRecord"`
}
func init() {
initJcMap[BackObservation]()
initJcMap[BackLocation]()
initJcMap[BackPcr]()
initJcMap[BackSituationRecord]()
}
type FakerDate time.Time
const (
timeFormat = "2006-01-02"
)
func (t *FakerDate) UnmarshalJSON(data []byte) (err error) {
newTime, err := time.ParseInLocation(`"`+timeFormat+`"`, string(data), time.Local)
*t = FakerDate(newTime)
return
}
func (t FakerDate) MarshalJSON() ([]byte, error) {
timeStr := fmt.Sprintf("\"%s\"", time.Time(t).Format(timeFormat))
return []byte(timeStr), nil
}
func (t FakerDate) String() string {
return time.Time(t).Format(timeFormat)
}
type HotelContactRequest struct {
Name string `json:"name"`
Age int `json:"age,string"`
Sex int `json:"sex,string"`
Phone string `json:"phone"`
Address string `json:"address"`
HotelCode string `json:"hotel_code"`
HotelName string `json:"hotel_name"`
LocateCityId string `json:"locate_city_id"`
Identification string `json:"identification"`
InData FakerDate `json:"in_data"`
OutData FakerDate `json:"out_data"`
}
type RailwayContactRequest struct {
Name string `json:"name"`
Age int `json:"age,string"`
Sex int `json:"sex,string"`
Phone string `json:"phone"`
Address string `json:"address"`
Train string `json:"train"`
Launch FakerDate `json:"launch"`
Identification string `json:"identification"`
}
type PatientRequest struct {
Name string `json:"name"`
Age int `json:"age,string"`
Sex int `json:"sex,string"`
Phone string `json:"phone"`
Address string `json:"address"`
Identification string `json:"identification"`
}

15
models/notify.go Normal file
View File

@@ -0,0 +1,15 @@
package models
import "time"
type BackNotification struct {
Time time.Time `json:"time"`
Kind string `json:"kind"`
Content string `json:"content"`
}
type SendInfo struct {
Region []string `json:"region"`
Channel []int `json:"channel"`
Notification BackNotification `json:"notification"`
}

View File

@@ -2,6 +2,7 @@ package models
type AreaInfo struct {
Name string `json:"name"`
Parent string `json:"parent"`
Today AreaToday `json:"today"`
Total AreaTotal `json:"total"`
Children []AreaInfo `json:"children"`
@@ -58,3 +59,42 @@ type ChinaData struct {
ChinaAdd ChinaAdd `json:"chinaAdd"`
ChinaTotal ChinaTotal `json:"chinaTotal"`
}
type ChinaDayAdd struct {
DeadRate string `json:"deadRate"`
HealRate string `json:"healRate"`
Date string `json:"date"`
Year string `json:"y"`
Confirm int `json:"confirm"`
Suspect int `json:"suspect"`
Dead int `json:"dead"`
Infect int `json:"infect"`
Heal int `json:"heal"`
ImportedCase int `json:"importedCase"`
Localinfectionadd int `json:"localinfectionadd"`
LocalConfirmadd int `json:"localConfirmadd"`
}
type ChinaDay struct {
DeadRate string `json:"deadRate"`
NoInfect int `json:"noInfect"`
LocalConfirm int `json:"localConfirm"`
ImportedCase int `json:"importedCase"`
Date string `json:"date"`
LocalConfirmH5 int `json:"localConfirmH5"`
Suspect int `json:"suspect"`
Dead int `json:"dead"`
Heal int `json:"heal"`
Year string `json:"y"`
Confirm int `json:"confirm"`
NowConfirm int `json:"nowConfirm"`
HealRate string `json:"healRate"`
NowSevere int `json:"nowSevere"`
NoInfectH5 int `json:"noInfectH5"`
LocalAccConfirm int `json:"local_acc_confirm"`
}
type ChinaTrend struct {
ChinaDayList []ChinaDay `json:"ChinaDayList"`
ChinaDayAddList []ChinaDayAdd `json:"chinaDayAddList"`
}

View File

@@ -3,7 +3,7 @@ package models
import "time"
type BackUser struct {
ID int `gorm:"primaryKey;column:id" json:"-"` // 用户ID
ID int `gorm:"primaryKey;column:id" json:"id"` // 用户ID
Username string `gorm:"column:username" json:"username"` // 用户真实姓名
Password string `gorm:"column:password" json:"password"` // 用户密码
Role int `gorm:"column:role" json:"role"` // 用户角色
@@ -14,6 +14,7 @@ type BackUser struct {
Approver int `gorm:"column:approver" json:"approver"` // 注册审核人ID
ModifyTime time.Time `gorm:"column:modify_time" json:"modifyTime"`
IsDelete int8 `gorm:"column:is_delete" json:"isDelete"` // 删除标志
Region string `gorm:"column:region" json:"region"` // 用户所属地域
}
type UserLogin struct {
@@ -27,6 +28,8 @@ type UserRegister struct {
Email string `json:"email"`
Phone string `json:"phone"`
Aptitude string `json:"aptitude"`
Region string `json:"region"`
Role int `json:"role"`
}
type UserChangePwd struct {
@@ -40,6 +43,14 @@ type UserApprove struct {
Pass bool `json:"pass"`
}
type TokenClaims struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Role int `json:"role"`
Region string `json:"region"`
}
func init() {
initJcMap[BackUser]()
}

View File

@@ -2,11 +2,13 @@ package models
import (
"fmt"
"gorm.io/gorm"
"nCovTrack-Backend/global"
"reflect"
"regexp"
"strconv"
"time"
"gorm.io/gorm"
)
var colNameReg, _ = regexp.Compile(".*column:(.*);?")
@@ -60,17 +62,60 @@ func MapJ2c[T any](jsonMap map[string]interface{}, ignoreNil bool) (colMap map[s
return colMap
}
func MapsJ2c[T any](jsonMaps []map[string]interface{}, ignoreNil bool) (colMaps []map[string]interface{}) {
for _, jsonMap := range jsonMaps {
colMap := MapJ2c[T](jsonMap, ignoreNil)
colMaps = append(colMaps, colMap)
}
return colMaps
}
func MapC2j[T any](colMap map[string]interface{}, ignoreNil bool) (jsonMap map[string]interface{}) {
tName := reflect.TypeOf(new(T)).Elem().Name()
tC2jMap := c2jMap[tName]
if tC2jMap == nil {
panic(tName + " is not init registered int j2cMap")
}
jsonMap = make(map[string]interface{})
for k, v := range colMap {
//TODO 无法转换
if jsonKey := tC2jMap[k]; jsonKey != "" && (!ignoreNil || v != nil) {
jsonMap[jsonKey] = v
}
}
return jsonMap
}
func MapsC2j[T any](jsonMaps []map[string]interface{}, ignoreNil bool) (colMaps []map[string]interface{}) {
for _, jsonMap := range jsonMaps {
colMap := MapC2j[T](jsonMap, ignoreNil)
colMaps = append(colMaps, colMap)
}
return colMaps
}
// BeforeSave need to set some field while insert or update
func BeforeSave(colMap map[string]interface{}, user int) {
if colMap["id"] == nil {
if colMap["id"] == nil || int(colMap["id"].(float64)) == 0 {
colMap["create_time"] = time.Now()
if user != -1 {
colMap["create_user"] = user
} else {
colMap["create_user"] = 0
}
}
colMap["modify_time"] = time.Now()
if user != -1 {
colMap["modify_user"] = user
} else {
colMap["modify_user"] = 0
}
}
func BeforeBatchSave(colMaps *[]map[string]interface{}, user int) {
for _, colMap := range *colMaps {
BeforeSave(colMap, user)
}
}
@@ -81,7 +126,7 @@ func BeforeSave(colMap map[string]interface{}, user int) {
func Upsert[T any](colMap map[string]interface{}) (ok bool, rowsAffected int64) {
var tx *gorm.DB
if colMap["id"] == nil {
if colMap["id"] == nil || getMapId(colMap["id"]) == 0 {
tx = global.Db.Model(new(T)).Create(colMap)
} else {
tx = global.Db.Model(new(T)).Where("id = ?", colMap["id"]).Updates(colMap)
@@ -93,13 +138,44 @@ func Upsert[T any](colMap map[string]interface{}) (ok bool, rowsAffected int64)
return true, tx.RowsAffected
}
func BatchInsert[T any](colMaps []map[string]interface{}) (ok bool, rowsAffected int64) {
tx := global.Db.Model(new(T)).Create(&colMaps)
if tx.Error != nil {
fmt.Println(tx.Error)
return false, 0
}
return true, tx.RowsAffected
}
func Update[T any](queryMap []map[string]interface{}, updateMap map[string]interface{}) (ok bool, rowsAffected int64) {
tx := global.Db.Model(new(T))
for _, e := range queryMap {
e[IS_DELETE] = 0
tx = tx.Or(e)
}
return UpdateByOrm(tx, updateMap)
}
func UpdateByOrm(tx *gorm.DB, updateMap map[string]interface{}) (ok bool, rowsAffected int64) {
tx.Updates(updateMap)
if tx.Error != nil {
fmt.Println(tx.Error)
return false, 0
}
return true, tx.RowsAffected
}
// DeleteById will delete by id, not delete the record from database, only set the field "is_delete" as 1
func DeleteById[T any](id int) (ok bool, rowsAffected int64) {
tx := global.Db.Model(new(T)).Where("id = ?", id).Update("is_delete", 1)
if tx.Error != nil {
return false, 0
}
return true, rowsAffected
return true, tx.RowsAffected
}
func DropById[T any](id int) {
global.Db.Model(new(T)).Delete("id = ?", id)
}
func List[T any](queryMap []map[string]interface{}) *[]map[string]interface{} {
@@ -179,3 +255,17 @@ func CountByOrm(tx *gorm.DB) int64 {
tx.Count(&count)
return count
}
func getMapId(id interface{}) int {
switch id.(type) {
case int:
return id.(int)
case string:
id, _ := strconv.Atoi(id.(string))
return id
case float64:
return int(id.(float64))
default:
return -1
}
}

View File

@@ -8,16 +8,17 @@ import (
func articlePrivateRouter(router *gin.RouterGroup) {
articleRouter := router.Group("/article")
{
articleRouter.POST("", handler.SaveArticleHandler)
articleRouter.DELETE("/:id", handler.DeleteArticleHandler)
articleRouter.POST("/:id/publish", handler.PublishArticleHandler)
articleRouter.GET("/list/:published", handler.ListArticlesByUser)
}
}
func articlePublicRouter(router *gin.RouterGroup) {
articleRouter := router.Group("/article")
{
articleRouter.POST("", handler.SaveArticleHandler)
articleRouter.GET("/list", handler.GetAllArticlesHandler)
articleRouter.GET("/list", handler.ListPublishedArticlesHandler)
articleRouter.GET("/:id", handler.GetArticleHandler)
}
}

23
router/management.go Normal file
View File

@@ -0,0 +1,23 @@
package router
import (
"nCovTrack-Backend/handler"
"github.com/gin-gonic/gin"
)
func managementPrivateRouter(router *gin.RouterGroup) {
managementRouter := router.Group("/management")
{
managementRouter.GET("/faker/:patientId", handler.PullContactHandler)
managementRouter.GET("/observation", handler.ListObservationsHandler)
managementRouter.POST("/observation", handler.InsertObservationHandler)
managementRouter.GET("/observation/:id", handler.GetObservationHandler)
managementRouter.GET("/observation/tree/:id/:direction", handler.TreeifyObservationHandler)
managementRouter.PUT("/observation", handler.UpdateObservationHandler)
managementRouter.POST("/location/", handler.InsertLocationHandler)
managementRouter.GET("/location/", handler.ListLocationHandler)
managementRouter.DELETE("/location/", handler.DeleteLocationHandler)
managementRouter.POST("/pcr/", handler.InsertPcrHandler)
}
}

17
router/notify.go Normal file
View File

@@ -0,0 +1,17 @@
package router
import (
"nCovTrack-Backend/handler"
"github.com/gin-gonic/gin"
)
func notifyPrivateRouter(router *gin.RouterGroup) {
notifyRouter := router.Group("/notify")
{
notifyRouter.GET("/count", handler.CountNotificationHandler)
notifyRouter.POST("/", handler.InsertNotificationHandler)
notifyRouter.GET("/:start/:end", handler.ListNotificationHandler)
notifyRouter.DELETE("/:index", handler.DeleteNotificationHandler)
}
}

View File

@@ -21,5 +21,7 @@ func BusiRouter() {
{
articlePrivateRouter(privateRouter)
userPrivateRouter(privateRouter)
notifyPrivateRouter(privateRouter)
managementPrivateRouter(privateRouter)
}
}

View File

@@ -8,10 +8,11 @@ import (
func statisticRouter(router *gin.RouterGroup) {
statisticsRouter := router.Group("/statistics")
{
statisticsRouter.GET("/provience/:sort", handler.ProvinceDataHandler)
statisticsRouter.GET("/province/:sort", handler.ProvinceDataHandler)
statisticsRouter.GET("/city/:sort", handler.CityDataHandler)
statisticsRouter.GET("/country/child", handler.CountryDataHandler)
statisticsRouter.GET("/country", handler.CountryDataHandler)
statisticsRouter.GET("/china", handler.ChinaDataHandler)
statisticsRouter.GET("/china/trend", handler.ChinaTrendHandler)
}
}

View File

@@ -19,6 +19,6 @@ func userPrivateRouter(router *gin.RouterGroup) {
userRouter := router.Group("/user")
{
userRouter.POST("/approve", handler.UserApproveHandler)
userRouter.GET("/registers", handler.ListRegisterUserHandler)
userRouter.GET("/registers/:approved", handler.ListRegisterUserHandler)
}
}

View File

@@ -1,31 +1,46 @@
package article
import (
"nCovTrack-Backend/global"
"nCovTrack-Backend/models"
"strconv"
)
//ListPublishedArticles list the articles published, use to show the articles to all people
func ListPublishedArticles() *[]map[string]interface{} {
article := models.ListField[models.BackArticle]([]map[string]interface{}{{"is_publish": 0}}, true, "content")
if *article == nil {
article = &[]map[string]interface{}{}
}
return article
func ListPublishedArticles() *[]models.ListArticle {
return listArticles(1, 0)
}
//ListAllArticles list all articles, will show the articles not published of the user
// TODO: need only show the user's not published article
func ListAllArticles() *[]map[string]interface{} {
article := models.ListField[models.BackArticle]([]map[string]interface{}{{}}, true, "content")
if *article == nil {
article = &[]map[string]interface{}{}
//ListPublishedArticlesByUser list the user's published articles
func ListPublishedArticlesByUser(id int) *[]models.ListArticle {
return listArticles(1, id)
}
//ListNotPublishedArticlesByUser list the user's not publish articles
func ListNotPublishedArticlesByUser(id int) *[]models.ListArticle {
return listArticles(0, id)
}
//listArticles complex function need to directly use gorm
func listArticles(isPublish int, createUser int) *[]models.ListArticle {
queryStr := "back_article.is_delete = 0 AND is_publish = " + strconv.Itoa(isPublish)
if createUser != 0 {
queryStr += " AND create_user = " + strconv.Itoa(createUser)
}
return article
var res []models.ListArticle
global.Db.Table("back_article").
Select("back_user.username, back_article.*").
Joins("join back_user on back_article.create_user=back_user.id").
Where(queryStr).Find(&res)
if res == nil {
res = []models.ListArticle{}
}
return &res
}
//SaveArticle save the articles
func SaveArticle(article map[string]interface{}) (ok bool) {
models.BeforeSave(article, -1)
func SaveArticle(claims models.TokenClaims, article map[string]interface{}) (ok bool) {
models.BeforeSave(article, claims.ID)
ok, rows := models.Upsert[models.BackArticle](article)
return ok && rows != 0
}

111
service/management/faker.go Normal file
View File

@@ -0,0 +1,111 @@
package management
import (
"encoding/json"
"fmt"
"nCovTrack-Backend/global"
"nCovTrack-Backend/models"
"nCovTrack-Backend/utils"
"strings"
"time"
)
//fakerGetRequest Get data from faker
func fakerGetRequest(uri string) string {
resStr := utils.GetWhioutHeader(global.FACKER_HOST + uri)
var res utils.GinResponse
_ = json.Unmarshal([]byte(resStr), &res)
dataStr, _ := json.Marshal(res.Data)
return string(dataStr)
}
//queryHotelContacts Hotel contacts
func queryHotelContacts(identification string) []models.HotelContactRequest {
dataStr := fakerGetRequest("query/contacts/hotel/" + identification)
var data []models.HotelContactRequest
err := json.Unmarshal([]byte(dataStr), &data)
if err != nil {
panic(err)
}
return data
}
//queryRailwayContacts Railway contacts
func queryRailwayContacts(identification string) []models.RailwayContactRequest {
dataStr := fakerGetRequest("query/contacts/railway/" + identification)
var data []models.RailwayContactRequest
err := json.Unmarshal([]byte(dataStr), &data)
if err != nil {
panic(err)
}
return data
}
//queryPatients Patients
func QueryPatients(identification string) []models.PatientRequest {
dataStr := fakerGetRequest("query/contacts/railway/" + identification)
var data []models.PatientRequest
err := json.Unmarshal([]byte(dataStr), &data)
if err != nil {
panic(err)
}
return data
}
//queryContacts Resolve the diffrent of hotel and railway request
func queryContacts(identification string) []models.BackObservation {
hotelContacts := queryHotelContacts(identification)
railwayContacts := queryRailwayContacts(identification)
observations := append(fakerContacts2Observations(hotelContacts), fakerContacts2Observations(railwayContacts)...)
return observations
}
//splitFakerAddress Extract the region
func splitFakerAddress(fakerAddress string) string {
addresses := strings.Split(fakerAddress, " ")
return addresses[1] + " " + addresses[2]
}
//fakerContacts2Observations Convvert structs
func fakerContacts2Observations(contacts any) []models.BackObservation {
var observations []models.BackObservation
switch contacts.(type) {
case []models.HotelContactRequest:
for _, contact := range contacts.([]models.HotelContactRequest) {
tranjectory := fmt.Sprintf("在%s-%s期间与患者同期留宿%s", contact.InData.String(), contact.OutData.String(), contact.HotelName)
observation := &models.BackObservation{
Name: contact.Name,
Age: contact.Age,
Sex: contact.Sex,
Phone: contact.Phone,
Identification: contact.Identification,
Region: splitFakerAddress(contact.Address),
Address: "",
HealthSituation: global.HEALTH_SITUATION_ID_MAP["CONTACT"],
HealthChangeTime: time.Now(),
MeasureSituation: global.MEASURE_SITUATION_ID_MAP["NOMEASURE"],
Trajectory: tranjectory,
}
observations = append(observations, *observation)
}
case []models.RailwayContactRequest:
for _, contact := range contacts.([]models.RailwayContactRequest) {
tranjectory := fmt.Sprintf("在%s与患者同期乘坐%s", contact.Launch.String(), contact.Train)
observation := &models.BackObservation{
Name: contact.Name,
Age: contact.Age,
Sex: contact.Sex,
Phone: contact.Phone,
Identification: contact.Identification,
Region: splitFakerAddress(contact.Address),
Address: "",
HealthSituation: global.HEALTH_SITUATION_ID_MAP["CONTACT"],
HealthChangeTime: time.Now(),
MeasureSituation: global.MEASURE_SITUATION_ID_MAP["NOMEASURE"],
Trajectory: tranjectory,
}
observations = append(observations, *observation)
}
}
return observations
}

View File

@@ -0,0 +1,25 @@
package management
import (
"nCovTrack-Backend/models"
)
//InsertLocation Insert locations
func InsertLocation(claims models.TokenClaims, locationJMap map[string]interface{}) bool {
locationCMap := models.MapJ2c[models.BackLocation](locationJMap, true)
models.BeforeSave(locationCMap, claims.ID)
ok, rowsAffected := models.Upsert[models.BackLocation](locationCMap)
return (ok && rowsAffected != 0)
}
//ListLocation List locations
func ListLocation(queryJMap map[string]any) *[]map[string]any {
queryCMap := models.MapJ2c[models.BackLocation](queryJMap, true)
return models.ListField[models.BackLocation]([]map[string]any{queryCMap}, true, "is_delete")
}
//DeleteLocation Delete locations
func DeleteLocation(id int) bool {
ok, rowsAffected := models.DeleteById[models.BackLocation](id)
return (ok && rowsAffected != 0)
}

View File

@@ -0,0 +1,389 @@
package management
import (
"fmt"
"nCovTrack-Backend/global"
"nCovTrack-Backend/models"
"nCovTrack-Backend/service/notify"
"nCovTrack-Backend/utils"
"time"
)
//PullFromFaker Pull observations from faker system
func PullFromFaker(claims models.TokenClaims, patientId int) int64 {
// Get patient's identification by id
queryMap := []map[string]interface{}{{"id": patientId}}
patient := models.GetField[models.BackObservation](queryMap, false, "identification")
if patient == nil || patient["identification"] == nil {
return -1
}
// Pull contacts form faker
observations := queryContacts(patient["identification"].(string))
// Set ContactPerson
for i := range observations {
observations[i].ContactPerson = patientId
}
// Insert into db
var observationsJMap []map[string]interface{}
utils.Strcut2Map(observations, &observationsJMap)
observationsCMap := models.MapsJ2c[models.BackObservation](observationsJMap, false)
models.BeforeBatchSave(&observationsCMap, claims.ID)
_, rowsAffected := models.BatchInsert[models.BackObservation](observationsCMap)
// Generate situation record and send notify to the region
if rowsAffected != 0 {
pullSituationRecord(claims, observations)
pullNotify(observations)
}
return rowsAffected
}
//ListObservation list observations with query
func ListObservation(jsonMap map[string]interface{}) []models.ListObservation {
colMap := models.MapJ2c[models.BackObservation](jsonMap, true)
queryMap := []map[string]interface{}{colMap}
return listObservation(queryMap)
}
//GetObservation get observation by id
func GetObservation(id int) map[string]interface{} {
// Obseration
observationCMap := models.GetField[models.BackObservation]([]map[string]interface{}{{"id": id}}, true, "is_delete")
observationJMap := models.MapC2j[models.BackObservation](observationCMap, true)
// Pcr record
pcrRecordCMap := models.ListField[models.BackPcr]([]map[string]interface{}{{"observation": id}}, true, "is_delete")
pcrRecordJMap := models.MapsC2j[models.BackPcr](*pcrRecordCMap, true)
// Situation record
situationRecordCMap := models.ListField[models.BackSituationRecord]([]map[string]interface{}{{"observation": id}}, true, "is_delete")
situationRecordJMap := models.MapsC2j[models.BackSituationRecord](*situationRecordCMap, true)
// Assemble
observationJMap["pcrRecord"] = pcrRecordJMap
observationJMap["situationRecord"] = situationRecordJMap
return observationJMap
}
//InsertObservation add observation
func InsertObservation(claims models.TokenClaims, jsonMap map[string]interface{}) bool {
colMap := models.MapJ2c[models.BackObservation](jsonMap, true)
models.BeforeSave(colMap, claims.ID)
ok, rowsAffected := models.Upsert[models.BackObservation](colMap)
queryMap := []map[string]interface{}{{"identification": jsonMap["identification"]}}
// Get result after insert, assemble record will use it.
newObservation := models.GetField[models.BackObservation](queryMap, true)
recordOk := insertSituationRecord(claims, map[string]interface{}{}, newObservation)
return (ok && recordOk && rowsAffected != 0)
}
func UpdateObservation(claims models.TokenClaims, jsonMap map[string]any) bool {
// Pre actions
colMap := models.MapJ2c[models.BackObservation](jsonMap, true)
models.BeforeSave(colMap, claims.ID)
queryMap := []map[string]interface{}{{"id": jsonMap["id"]}}
// Get old record
oldMap := models.Get[models.BackObservation](queryMap)
if oldMap == nil || len(oldMap) == 0 {
return false
}
// Deal with region update
if colMap["region"] != nil && oldMap["region"].(string) != colMap["region"].(string) {
updateObservationRegion(colMap["region"].(string))
}
// Deal with situation update
if colMap["health_situation"] != nil && int(colMap["health_situation"].(float64)) != oldMap["health_situation"].(int) ||
colMap["measure_situation"] != nil && int(colMap["measure_situation"].(float64)) != oldMap["measure_situation"].(int) {
updateSituation(claims, oldMap, colMap)
}
ok, rowsAffected := models.Update[models.BackObservation](queryMap, colMap)
return ok && rowsAffected != 0
}
//TreeifyObservations get the transmission chains of observations
// Param direction: up, down, all
func TreeifyObservations(observationId int, direction string) *models.TreeObservation {
// Get arrays form back observation
var observations []models.BackObservation
if direction != "down" {
observations = append(observations, treeifyObservations(observationId, true)...)
}
if direction != "up" {
observations = append(observations, treeifyObservations(observationId, false)...)
}
// Treeify
observationMap := map[int]*models.TreeObservation{}
for _, observation := range observations {
observationMap[observation.ID] = &models.TreeObservation{
ID: observation.ID,
Name: observation.Name,
Age: observation.Age,
Sex: observation.Sex,
Phone: observation.Phone,
Identification: observation.Identification,
ContactPerson: observation.ContactPerson,
Region: observation.Region,
Address: observation.Address,
HealthSituation: observation.HealthSituation,
HealthChangeTime: observation.HealthChangeTime,
CreateUser: observation.CreateUser,
CreateTime: observation.CreateTime,
ModifyUser: observation.ModifyUser,
ModifyTime: observation.ModifyTime,
}
}
var rootKey int
for k := range observationMap {
parentId := observationMap[k].ContactPerson
parent := observationMap[parentId]
if parent == nil {
rootKey = k
continue
}
parent.Children = append(parent.Children, observationMap[k])
}
return observationMap[rootKey]
}
//treeifyObservations query tree data from db
// Param direction up: true, down: false
func treeifyObservations(observationId int, direction bool) []models.BackObservation {
var res []models.BackObservation
var directionCond string
if direction {
directionCond = "p.id = cte.contact_person"
} else {
directionCond = "cte.id = p.contact_person"
}
querySql := fmt.Sprintf(`
WITH RECURSIVE cte (id, name, age, sex, phone, identification, contact_person, region, address, health_situation, health_change_time, trajectory, create_user, modify_user, create_time, modify_time) AS (
SELECT id, name, age, sex, phone, identification, contact_person, region, address, health_situation, health_change_time, trajectory, create_user, modify_user, create_time, modify_time
FROM back_observation
WHERE contact_person = ?
UNION ALL
SELECT p.id, p.name, p.age, p.sex, p.phone, p.identification, p.contact_person, p.region, p.address, p.health_situation, p.health_change_time, p.trajectory, p.create_user, p.modify_user, p.create_time, p.modify_time
FROM back_observation p
INNER JOIN cte ON %s
)
SELECT * FROM cte
`, directionCond)
global.Db.Model(models.BackObservation{}).Raw(querySql, observationId).Scan(&res)
return res
}
//listObservation the queryMap need with table name
// Param queryMap the colMap of models.BackObservation
func listObservation(queryMap []map[string]interface{}) []models.ListObservation {
var observations []models.ListObservation
tx := global.Db.Model(new((models.BackObservation))).
Select("back_observation.*, back_pcr.detect_time AS pcr_time, detect_result AS pcr_result," +
"back_situation_record.create_time AS record_time, back_situation_record.record AS record").
Joins("LEFT JOIN back_pcr ON back_observation.id = back_pcr.observation").
Joins("LEFT JOIN back_situation_record ON back_observation.id = back_situation_record.observation")
for _, query := range queryMap {
query["is_delete"] = 0
// Add the table prefix
fullQuery := map[string]interface{}{}
for k := range query {
fullQuery["back_observation."+k] = query[k]
}
tx = tx.Or(fullQuery)
}
tx.Find(&observations)
return observations
}
//pullSituationRecord push situation record after pull observations
func pullSituationRecord(claims models.TokenClaims, observations []models.BackObservation) {
// Query observations' id
var identifications []string
for _, observation := range observations {
identifications = append(identifications, observation.Identification)
}
queryMap := []map[string]interface{}{{"identification": identifications}}
observationMaps := models.ListField[models.BackObservation](queryMap, false, "id", "identification")
// Assemble records
var records []models.BackSituationRecord
recordContent := record___20()
for _, observationMap := range *observationMaps {
record := models.BackSituationRecord{Observation: observationMap["id"].(int), Record: recordContent}
records = append(records, record)
}
// Insert into db
var recordsJMaps []map[string]interface{}
utils.Strcut2Map(records, &recordsJMaps)
recordsCMaps := models.MapsJ2c[models.BackSituationRecord](recordsJMaps, true)
models.BeforeBatchSave(&recordsCMaps, claims.ID)
models.BatchInsert[models.BackSituationRecord](recordsCMaps)
}
//pullNotify send notify after pull observations
func pullNotify(observations []models.BackObservation) {
var regions []string
for _, observation := range observations {
regions = append(regions, observation.Region)
}
regions = utils.Distinct(regions)
notification := models.BackNotification{Time: time.Now(), Kind: "疫情", Content: "出现了新的接触者,请及时登录系统进行处理"}
sendInfo := models.SendInfo{Region: regions, Channel: []int{0, 1}, Notification: notification}
notify.SendNotify(sendInfo)
}
//updateObservationRegion send notify while update observation's region
func updateObservationRegion(region string) {
notification := models.BackNotification{Time: time.Now(), Kind: "疫情", Content: "本地有新的接触者,请及时登录系统进行处理"}
sendInfo := models.SendInfo{Region: []string{region}, Channel: []int{0, 1}, Notification: notification}
notify.SendNotify(sendInfo)
}
//updateSituation make some special resolve while situation update
func updateSituation(claims models.TokenClaims, oldObservationMap, newObservationMap map[string]interface{}) {
if newObservationMap["health_situation"] != nil {
newHealthSituation := int(newObservationMap["health_situation"].(float64))
oldHealthSituation := oldObservationMap["health_situation"].(int)
// Update health situation change time and measure_situation
if newHealthSituation != oldHealthSituation {
newObservationMap["health_change_time"] = time.Now()
newObservationMap["measure_situation"] = 0
}
// Deal with contact to patient
if oldHealthSituation == 2 && newHealthSituation == 1 {
// Query sub contacts
queryMap := []map[string]interface{}{{"contact_person": oldObservationMap["id"].(int)}}
subContacts := models.ListField[models.BackObservation](queryMap, false, "id", "region")
// Get reginos and assemble situation record
var regions []string
var subContactRecords []models.BackSituationRecord
for _, subContact := range *subContacts {
regions = append(regions, subContact["region"].(string))
subContactRecord := models.BackSituationRecord{
Observation: int(newObservationMap["id"].(float64)),
Record: record___20(),
}
subContactRecords = append(subContactRecords, subContactRecord)
}
// Update sub contact
updateMap := map[string]interface{}{
"health_situation": 2,
"health_change_time": newObservationMap["health_change_time"],
"measure_situation": 0,
}
models.Update[models.BackObservation](queryMap, updateMap)
// Insert subContacts' record
var recordJMaps []map[string]interface{}
utils.Strcut2Map(subContactRecords, &recordJMaps)
recordCMap := models.MapsJ2c[models.BackSituationRecord](recordJMaps, true)
models.BeforeBatchSave(&recordCMap, claims.ID)
models.BatchInsert[models.BackSituationRecord](recordCMap)
// Send nofity
regions = utils.Distinct(regions)
notification := models.BackNotification{
Time: newObservationMap["health_change_time"].(time.Time),
Kind: "疫情",
Content: "由于密接者转为患者,次密接者状态需要修改",
}
sendInfo := models.SendInfo{
Region: regions,
Channel: []int{0, 1},
Notification: notification,
}
notify.SendNotify(sendInfo)
}
insertSituationRecord(claims, oldObservationMap, newObservationMap)
}
}
//insertSituationRecord insert situation record
func insertSituationRecord(claims models.TokenClaims, oldObservation, newObservation map[string]interface{}) bool {
var oldSituation, newSituation string
if oldObservation["id"] == nil {
// insert observation
oldSituation = "00"
} else {
// update observation
oldSituation = fmt.Sprintf("%d%d", oldObservation["health_situation"], oldObservation["measure_situation"])
}
if newObservation["health_situation"] == nil || newObservation["measure_situation"] == nil {
// Situation not update
return true
}
newSituation = fmt.Sprintf("%d%d", newObservation["health_situation"], newObservation["measure_situation"])
// Situation not update
if oldSituation == newSituation {
return true
}
// Set record content accrodding the situation convertions
var recordContent string
situationConvert := oldSituation + "_" + newSituation
switch situationConvert {
case "00_10":
recordContent = record00_10()
case "00_30":
recordContent = record00_30()
case "00_12":
recordContent = record00_12(newObservation["region"].(string), newObservation["address"].(string))
case "10_12":
recordContent = record10_12(newObservation["region"].(string), newObservation["address"].(string))
case "00_20", "33_20", "34_20":
recordContent = record___20()
case "12_01", "23_01", "24_01", "33_01", "34_01":
recordContent = record___01()
case "20_23", "30_33", "24_23", "34_33":
recordContent = record____3(newObservation["region"].(string), newObservation["address"].(string))
case "20_24", "30_34", "23_24", "33_34":
recordContent = record____4(newObservation["region"].(string), newObservation["address"].(string))
case "23_10", "24_10", "33_10", "34_10":
recordContent = record___10()
default:
if oldSituation != "00" {
models.Upsert[models.BackObservation](oldObservation)
} else {
models.DropById[models.BackObservation](newObservation["id"].(int))
}
return false
}
record := models.BackSituationRecord{Observation: newObservation["id"].(int), Record: recordContent}
var recordJMap map[string]interface{}
utils.Strcut2Map(record, &recordJMap)
recordCMap := models.MapJ2c[models.BackSituationRecord](recordJMap, true)
models.BeforeSave(recordCMap, claims.ID)
models.Upsert[models.BackSituationRecord](recordCMap)
return true
}
func record00_10() string {
date := utils.FormatDate(time.Now())
return date + " 确诊为患者"
}
func record00_12(region, address string) string {
date := utils.FormatDate(time.Now())
return fmt.Sprintf("%s 于 %s %s 确诊为患者", date, region, address)
}
func record___20() string {
date := utils.FormatDate(time.Now())
return date + " 转为密接"
}
func record00_30() string {
date := utils.FormatDate(time.Now())
return date + " 转为次密接"
}
func record___01() string {
date := utils.FormatDate(time.Now())
return fmt.Sprintf("%s 解除风险", date)
}
func record10_12(region, address string) string {
date := utils.FormatDate(time.Now())
return fmt.Sprintf("%s 转至医院:%s %s", date, region, address)
}
func record___10() string {
date := utils.FormatDate(time.Now())
return date + " 确诊为患者"
}
func record____3(region, address string) string {
date := utils.FormatDate(time.Now())
return fmt.Sprintf("%s 集中隔离至:%s %s", date, region, address)
}
func record____4(region, address string) string {
date := utils.FormatDate(time.Now())
return fmt.Sprintf("%s 进行居家隔离:%s %s", date, region, address)
}

10
service/management/pcr.go Normal file
View File

@@ -0,0 +1,10 @@
package management
import "nCovTrack-Backend/models"
func InsertPcr(claims models.TokenClaims, jsonMap map[string]interface{}) bool {
colMap := models.MapJ2c[models.BackPcr](jsonMap, true)
models.BeforeSave(colMap, claims.ID)
ok, rowsAffected := models.Upsert[models.BackPcr](colMap)
return ok && rowsAffected != 0
}

89
service/notify/notify.go Normal file
View File

@@ -0,0 +1,89 @@
package notify
import (
"encoding/json"
"nCovTrack-Backend/global"
"nCovTrack-Backend/models"
"nCovTrack-Backend/utils"
)
type NotifyFunc func([]string, models.BackNotification)
const (
NOTIFY_KEY = "notification_"
NOTIFY_DEL_VALUE = "notify_del"
)
var (
NOTIFY_CHANNEL_FUNC_ARR = []NotifyFunc{notifyNotification, emailNotification}
)
//emailNotification send email
func emailNotification(regions []string, notification models.BackNotification) {
queryMap := []map[string]interface{}{{"region": regions, "role": global.ROLE_ID_MAP["ADMIN"]}}
observations := models.ListField[models.BackUser](queryMap, false, "email")
var emails []string
for _, observation := range *observations {
emails = append(emails, observation["email"].(string))
}
utils.SendEmail(notification.Kind+"通知邮件", notification.Content, emails...)
}
//notifyNotification add to notify system
func notifyNotification(regions []string, notification models.BackNotification) {
valueByte, _ := json.Marshal(notification)
pipe := global.Redis.TxPipeline()
for _, region := range regions {
pipe.RPush(NOTIFY_KEY+region, valueByte)
}
pipe.Exec()
}
//DeleteNotification delete notify by id
func DeleteNotification(region string, index int) {
key := NOTIFY_KEY + region
pipe := global.Redis.TxPipeline()
pipe.LSet(key, int64(index), NOTIFY_DEL_VALUE)
pipe.LRem(key, 2, NOTIFY_DEL_VALUE)
pipe.Exec()
}
//CleanNotification delete all notify
func CleanNotification(region string) {
key := NOTIFY_KEY + region
global.Redis.Del(key)
}
//QueryNotificationLen query the count notification
func QueryNotificationLen(region string) int {
key := NOTIFY_KEY + region
return int(global.Redis.LLen(key).Val())
}
//ListNotifycation list the notifications ny range
func ListNotifycation(region string, start, end int) []models.BackNotification {
key := NOTIFY_KEY + region
var notifications []models.BackNotification
notificationStrArr := global.Redis.LRange(key, int64(start), int64(end)).Val()
for _, notificationStr := range notificationStrArr {
var notification models.BackNotification
json.Unmarshal([]byte(notificationStr), &notification)
notifications = append(notifications, notification)
}
return notifications
}
//SendNotify send notify to kafka
func SendNotify(send models.SendInfo) {
sendInfo, _ := json.Marshal(send)
global.KafkaProducerChan <- sendInfo
}
//HandleKafkaNotify handle kafka info
func HandleKafkaNotify(sendInfoByte []byte) {
var sendInfo models.SendInfo
json.Unmarshal(sendInfoByte, &sendInfo)
for _, channel := range sendInfo.Channel {
NOTIFY_CHANNEL_FUNC_ARR[channel](sendInfo.Region, sendInfo.Notification)
}
}

View File

@@ -12,26 +12,28 @@ import (
)
const (
rds_NCOV_STATISTIC_KEY = "nCovStatistic"
rds_CHINA_TOTAL_KEY = "chinaTotal"
rds_CHINA_ADD_KEY = "chinaAdd"
rds_COUNTRY_LEVEL_KEY = "countryLevel"
rds_COUNTRY_LEVEL_CHILD_KEY = "countryLevelChild"
rds_PROVIENCE_LEVEL_CHILD_KEY = "provienceLevelChild"
rds_PROVIENCE_LEVEL_NOW_CONFIRM_KEY = "provienceLevelNowConfirm"
rds_PROVIENCE_LEVEL_TODAY_CONFIRM_KEY = "provienceLevelTodayConfirm"
rds_PROVIENCE_LEVEL_TOTAL_CONFIRM_KEY = "provienceLevelTotalConfirm"
rds_CITY_LEVEL_CHILD_KEY = "cityLevelChild"
rds_CITY_LEVEL_NOW_CONFIRM_KEY = "cityLevelNowConfirm"
rds_CITY_LEVEL_TODAY_CONFIRM_KEY = "cityLevelTodayConfirm"
rds_CITY_LEVEL_TOTAL_CONFIRM_KEY = "cityLevelTotalConfirm"
rds_LAST_UPDATE_TIME = "statisticsLastUpdateTime"
rds_LAST_CACHE_TIME = "statisticsLastCacheTime"
rds_NCOV_STATISTIC_KEY = "nCovStatistic"
rds_CHINA_TOTAL_KEY = "chinaTotal"
rds_CHINA_ADD_KEY = "chinaAdd"
rds_COUNTRY_LEVEL_KEY = "countryLevel"
rds_COUNTRY_LEVEL_CHILD_KEY = "countryLevelChild"
rds_PROVINCE_LEVEL_CHILD_KEY = "provinceLevelChild"
rds_PROVINCE_LEVEL_NOW_CONFIRM_KEY = "provinceLevelNowConfirm"
rds_PROVINCE_LEVEL_TODAY_CONFIRM_KEY = "provinceLevelTodayConfirm"
rds_PROVINCE_LEVEL_TOTAL_CONFIRM_KEY = "provinceLevelTotalConfirm"
rds_CITY_LEVEL_CHILD_KEY = "cityLevelChild"
rds_CITY_LEVEL_NOW_CONFIRM_KEY = "cityLevelNowConfirm"
rds_CITY_LEVEL_TODAY_CONFIRM_KEY = "cityLevelTodayConfirm"
rds_CITY_LEVEL_TOTAL_CONFIRM_KEY = "cityLevelTotalConfirm"
rds_LAST_UPDATE_TIME = "statisticsLastUpdateTime"
rds_LAST_CACHE_TIME = "statisticsLastCacheTime"
rds_CHINA_DAY_ADD_LIST_KEY = "chinaDayAdd"
rds_CHINA_DAY_LIST_KEY = "chinaDay"
SORT_TODAY_CONFIRM = "today"
SORT_TOTAL_CONFIRM = "total"
SORT_NOW_CONFIRM = "now"
D
json_FOREIGN_COUNTRY = "境外"
json_FOREIGN_CITY = "外地"
json_TO_BE_CONFIRM = "待确认"
@@ -45,16 +47,31 @@ type AreaSlice []models.AreaInfo
func cacheNCovStatistics() {
resp := utils.GetWhioutHeader(global.CHINA_NCOV_STATISTIC_URL)
var nCovRes map[string]string
var nCovRes map[string]interface{}
json.Unmarshal([]byte(resp), &nCovRes)
var nCovResData map[string]interface{}
json.Unmarshal([]byte(nCovRes["data"]), &nCovResData)
nCovResData := (nCovRes["data"].(map[string]interface{}))["diseaseh5Shelf"].(map[string]interface{})
if !needToRecache(nCovResData) {
return
}
cacheChinaInfo(nCovResData)
cacheLevelInfo(nCovResData)
cacheLastUpdateTime(nCovResData)
resp = utils.GetWhioutHeader(global.CHINA_NCOV_STATISTIC_TREND_URL)
}
func CacheNCovTrend() {
resp := utils.GetWhioutHeader(global.CHINA_NCOV_STATISTIC_TREND_URL)
var nCovRes map[string]interface{}
json.Unmarshal([]byte(resp), &nCovRes)
nCovResData := nCovRes["data"].(map[string]interface{})
cacheChinaTrend(nCovResData)
}
func cacheChinaTrend(data map[string]interface{}) {
chinaDayAdd, _ := json.Marshal(data["chinaDayAddList"])
chinaDay, _ := json.Marshal(data["chinaDayList"])
global.Redis.Set(rds_CHINA_DAY_ADD_LIST_KEY, chinaDayAdd, 0)
global.Redis.Set(rds_CHINA_DAY_LIST_KEY, chinaDay, 0)
}
func cacheChinaInfo(data map[string]interface{}) {
@@ -81,8 +98,8 @@ func cacheLevelInfo(data map[string]interface{}) {
// Get Every Level's Info
var countryLevels []models.AreaInfo
json.Unmarshal(areaTree, &countryLevels)
provienceLevels := children(countryLevels)
cityLevels := children(provienceLevels)
provinceLevels := children(countryLevels)
cityLevels := children(provinceLevels)
// Country Level Area Info With Child
cacheList(rds_COUNTRY_LEVEL_CHILD_KEY, areaInfoToJson(countryLevels)...)
@@ -90,34 +107,34 @@ func cacheLevelInfo(data map[string]interface{}) {
areaInfoChildNil(&countryLevels)
cacheList(rds_COUNTRY_LEVEL_KEY, areaInfoToJson(countryLevels)...)
// Provience Level Area Info With Child
cacheList(rds_PROVIENCE_LEVEL_CHILD_KEY, areaInfoToJson(provienceLevels)...)
areaInfoChildNil(&provienceLevels)
// Province Level Area Info With Child
cacheList(rds_PROVINCE_LEVEL_CHILD_KEY, areaInfoToJson(provinceLevels)...)
areaInfoChildNil(&provinceLevels)
// City Level Area Info With Child
cacheList(rds_CITY_LEVEL_CHILD_KEY, areaInfoToJson(cityLevels)...)
areaInfoChildNil(&provienceLevels)
areaInfoChildNil(&provinceLevels)
// Provience Level Area Info Sorted by Now Confirm
provienceLevelsSlice := AreaSlice(provienceLevels)
sort.Sort(provienceLevelsSlice)
cacheList(rds_PROVIENCE_LEVEL_NOW_CONFIRM_KEY, areaInfoToJson(provienceLevelsSlice)...)
// Province Level Area Info Sorted by Now Confirm
provinceLevelsSlice := AreaSlice(provinceLevels)
sort.Sort(provinceLevelsSlice)
cacheList(rds_PROVINCE_LEVEL_NOW_CONFIRM_KEY, areaInfoToJson(provinceLevelsSlice)...)
// City Level Area Info Sorted By Now Confirm
cityLevelsSlice := AreaSlice(cityLevels)
sort.Sort(cityLevelsSlice)
cacheList(rds_CITY_LEVEL_NOW_CONFIRM_KEY, areaInfoToJson(cityLevelsSlice)...)
sortBy = SORT_TODAY_CONFIRM
// Provience Level Area Info Sorted by Today Confirm
sort.Sort(provienceLevelsSlice)
cacheList(rds_PROVIENCE_LEVEL_TODAY_CONFIRM_KEY, areaInfoToJson(provienceLevelsSlice)...)
// Province Level Area Info Sorted by Today Confirm
sort.Sort(provinceLevelsSlice)
cacheList(rds_PROVINCE_LEVEL_TODAY_CONFIRM_KEY, areaInfoToJson(provinceLevelsSlice)...)
// City Level Area Info Sorted by Today Confirm
sort.Sort(cityLevelsSlice)
cacheList(rds_CITY_LEVEL_TODAY_CONFIRM_KEY, areaInfoToJson(cityLevelsSlice)...)
sortBy = SORT_TOTAL_CONFIRM
// Provience Level Area Info Sorted by Total Confirm
sort.Sort(provienceLevelsSlice)
cacheList(rds_PROVIENCE_LEVEL_TOTAL_CONFIRM_KEY, areaInfoToJson(provienceLevelsSlice)...)
// Province Level Area Info Sorted by Total Confirm
sort.Sort(provinceLevelsSlice)
cacheList(rds_PROVINCE_LEVEL_TOTAL_CONFIRM_KEY, areaInfoToJson(provinceLevelsSlice)...)
// City Level Area Info Sorted by Total Confirm
sort.Sort(cityLevelsSlice)
cacheList(rds_CITY_LEVEL_TOTAL_CONFIRM_KEY, areaInfoToJson(cityLevelsSlice)...)
@@ -137,6 +154,7 @@ func children(parents []models.AreaInfo) []models.AreaInfo {
}
for _, item := range parent.Children {
name := item.Name
item.Parent = parent.Name
if !strings.Contains(name, json_FOREIGN_CITY) && !strings.Contains(name, json_FOREIGN_COUNTRY) && !strings.Contains(name, json_TO_BE_CONFIRM) {
areaInfos = append(areaInfos, item)
}

View File

@@ -7,18 +7,18 @@ import (
"strings"
)
func GetAllProvienceData(sort string) []interface{} {
func GetAllProvinceData(sort string) []interface{} {
checkCache()
if sort == SORT_TODAY_CONFIRM {
return getEntireRedisList(rds_PROVIENCE_LEVEL_TODAY_CONFIRM_KEY)
return getEntireRedisList(rds_PROVINCE_LEVEL_TODAY_CONFIRM_KEY)
}
if sort == SORT_TOTAL_CONFIRM {
return getEntireRedisList(rds_PROVIENCE_LEVEL_TOTAL_CONFIRM_KEY)
return getEntireRedisList(rds_PROVINCE_LEVEL_TOTAL_CONFIRM_KEY)
}
if sort == SORT_NOW_CONFIRM {
return getEntireRedisList(rds_PROVIENCE_LEVEL_NOW_CONFIRM_KEY)
return getEntireRedisList(rds_PROVINCE_LEVEL_NOW_CONFIRM_KEY)
}
return getEntireRedisList(rds_PROVIENCE_LEVEL_CHILD_KEY)
return getEntireRedisList(rds_PROVINCE_LEVEL_CHILD_KEY)
}
func GetAllCityData(sort string) []interface{} {
@@ -36,6 +36,7 @@ func GetAllCityData(sort string) []interface{} {
}
func GetCountryData(child bool) []interface{} {
checkCache()
if child {
return getEntireRedisList(rds_COUNTRY_LEVEL_CHILD_KEY)
}
@@ -43,12 +44,39 @@ func GetCountryData(child bool) []interface{} {
}
func GetChinaNCovStatistic() models.ChinaData {
checkCache()
data := models.ChinaData{}
json.Unmarshal([]byte(global.Redis.Get(rds_CHINA_ADD_KEY).Val()), &data.ChinaAdd)
json.Unmarshal([]byte(global.Redis.Get(rds_CHINA_TOTAL_KEY).Val()), &data.ChinaTotal)
return data
}
func GetChinaDayAdd() []models.ChinaDayAdd {
chinaDayAddStr := global.Redis.Get(rds_CHINA_DAY_ADD_LIST_KEY).Val()
if chinaDayAddStr == "" {
CacheNCovTrend()
chinaDayAddStr = global.Redis.Get(rds_CHINA_DAY_ADD_LIST_KEY).Val()
}
var chinaDayAddList []models.ChinaDayAdd
json.Unmarshal([]byte(chinaDayAddStr), &chinaDayAddList)
return chinaDayAddList
}
func GetChinaDay() []models.ChinaDay {
chinaDayStr := global.Redis.Get(rds_CHINA_DAY_LIST_KEY).Val()
if chinaDayStr == "" {
CacheNCovTrend()
chinaDayStr = global.Redis.Get(rds_CHINA_DAY_LIST_KEY).Val()
}
var chinaDayList []models.ChinaDay
json.Unmarshal([]byte(chinaDayStr), &chinaDayList)
return chinaDayList
}
func GetChinaTrend() models.ChinaTrend {
return models.ChinaTrend{ChinaDayAddList: GetChinaDayAdd(), ChinaDayList: GetChinaDay()}
}
func getEntireRedisList(key string) []interface{} {
var data []interface{}
dataStrArr := global.Redis.LRange(key, 0, -1).Val()

View File

@@ -1,15 +1,16 @@
package user
import (
"encoding/json"
"fmt"
"github.com/golang-jwt/jwt/v4"
"github.com/google/uuid"
"nCovTrack-Backend/global"
"nCovTrack-Backend/models"
"nCovTrack-Backend/service/notify"
"nCovTrack-Backend/utils"
"strings"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/google/uuid"
)
const (
@@ -29,64 +30,88 @@ func Login(user map[string]interface{}) (token string) {
if userInfo == nil {
return ""
}
if userInfo["approver"].(int) <= 0 {
return ""
}
if !utils.PasswordCompare(user["password"].(string), userInfo["password"].(string)) {
return ""
}
claims := jwt.MapClaims{
"id": userInfo["id"],
"username": userInfo["username"],
"role": userInfo["role"],
"email": userInfo["email"],
"region": userInfo["region"],
"role": userInfo["role"],
}
return utils.GenerateToken(claims)
}
// Register user register, user can use account after approved
func Register(user map[string]interface{}) {
func Register(user map[string]interface{}) bool {
user["password"] = utils.PasswordEncrypt(user["password"].(string))
userStr, _ := json.Marshal(user)
// insert into redis, wait for approve
cmd := global.Redis.HMSet(global.REGISTER_REDIS_KEY, map[string]interface{}{user["email"].(string): userStr})
if cmd.Err() != nil {
panic(cmd.Err())
user["approver"] = 0
colMap := models.MapJ2c[models.BackUser](user, false)
ok, rowsAffected := models.Upsert[models.BackUser](colMap)
var sendRegion string
if int(user["role"].(float64)) == global.ROLE_ID_MAP["ADMIN"] {
sendRegion = strings.Split(user["region"].(string), " ")[0]
} else {
sendRegion = user["region"].(string)
}
notification := models.BackNotification{Time: time.Now(), Kind: "审批", Content: "有新的注册待审批"}
sendInfo := models.SendInfo{Region: []string{sendRegion}, Channel: []int{0}, Notification: notification}
notify.SendNotify(sendInfo)
if !ok || rowsAffected == 0 {
return false
}
return true
}
// ListRegister list the registers in the redis to be approved
func ListRegister() *[]map[string]interface{} {
applyStrMap := global.Redis.HGetAll(global.REGISTER_REDIS_KEY).Val()
var applies []map[string]interface{}
for _, v := range applyStrMap {
var apply map[string]interface{}
_ = json.Unmarshal([]byte(v), &apply)
applies = append(applies, apply)
func ListRegister(claims models.TokenClaims) *[]map[string]interface{} {
registers := []map[string]interface{}{}
tx := global.Db.Model(new(models.BackUser)).Omit("password")
if claims.Region == "" {
// do nothing
} else if !strings.Contains(claims.Region, " ") {
// Province Admin approve city admin
tx.Where("approver = 0 AND is_delete = 0 AND region LIKE ? AND role = ?", claims.Region+" %", global.ROLE_ID_MAP["ADMIN"])
registers = *models.ListByOrm(tx)
} else {
// City Admin approve workers and volunteers
tx.Where("approver = 0 AND is_delete = 0 AND region = ? AND role in ?", claims.Region, []int{global.ROLE_ID_MAP["WORKER"], global.ROLE_ID_MAP["VOLUNTEER"]})
registers = *models.ListByOrm(tx)
}
if applies == nil {
applies = []map[string]interface{}{}
}
return &applies
return &registers
}
// ListApprovedRegister list registers approved by the admin
func ListApprovedRegister(claims models.TokenClaims) *[]map[string]interface{} {
approvedRegisters := []map[string]interface{}{}
tx := global.Db.Model(new(models.BackUser)).Omit("password").Where("approver in ? and is_delete = 0", []int{claims.ID, -claims.ID})
approvedRegisters = *models.ListByOrm(tx)
return &approvedRegisters
}
// ApproveRegister approve a register
func ApproveRegister(email string, pass bool) bool {
if !pass {
rowsAffected := global.Redis.HDel(global.REGISTER_REDIS_KEY, email).Val()
return rowsAffected != 0
func ApproveRegister(claims models.TokenClaims, email string, pass bool) bool {
queryMap := []map[string]interface{}{{"email": email}}
var approver int
// Approver field status:
// = 0: not approved
// > 0: approver id, and registe successful
// < 0: approver id, and registe failed
if pass {
approver = claims.ID
} else {
approver = -claims.ID
}
// if pass, will get the register info from redis, and the insert into mysql, this mean user is register success
applyStr := global.Redis.HGet(global.REGISTER_REDIS_KEY, email).Val()
rowsAffected := global.Redis.HDel(global.REGISTER_REDIS_KEY, email).Val()
if rowsAffected == 0 {
updateMap := map[string]interface{}{"approver": approver, "modify_time": time.Now()}
ok, rowsAffected := models.Update[models.BackUser](queryMap, updateMap)
if !ok || rowsAffected == 0 {
return false
}
var apply map[string]interface{}
_ = json.Unmarshal([]byte(applyStr), &apply)
if !NoDuplicatePhoneOrEmail(apply["phone"].(string), apply["email"].(string)) {
return false
}
colMap := models.MapJ2c[models.BackUser](apply, true)
ok, rowsAffected := models.Upsert[models.BackUser](colMap)
return ok && rowsAffected != 0
return true
}
// ChangePassword user change password, or user forgot password
@@ -97,11 +122,12 @@ func ChangePassword(changePwd map[string]interface{}) bool {
}
newPassword := utils.PasswordEncrypt(changePwd["newPassword"].(string))
colMap := map[string]interface{}{
"id": 1,
"id": 1.0,
"password": newPassword,
}
models.BeforeSave(colMap, -1)
delete(colMap, "id")
delete(colMap, "modify_user")
rowAffected := global.Db.Model(models.BackUser{}).Where("email = ?", changePwd["email"]).Updates(colMap).RowsAffected
if rowAffected == 0 {
return false

View File

@@ -27,3 +27,14 @@ email:
port: 587
account: fallen-angle@foxmail.com
password: hxrisxltxsjvieec
bos:
accessKey: 90dbff87c5aa4bdbb0d7a29e130b2808
secretKey: e53a672a10294abc8ecabe1ef92625b1
domain: bj.bcebos.com
kafka:
servers:
- myhost.fallen-angle.com:9092
topic: ncov_track
partition: 0

15
utils/bos.go Normal file
View File

@@ -0,0 +1,15 @@
package utils
import "nCovTrack-Backend/global"
func UploadFile() string {
etag, err := global.BosClient.PutObjectFromFile("ncovtrack", "test.jpg", "/home/fallen-angle/Pictures/Anime/pic-w-000003.jpg", nil)
if err != nil {
return ""
}
return etag
}
func DownloadLink() string {
return global.BosClient.BasicGeneratePresignedUrl("ncovtrack", "test.jpg", 1800)
}

View File

@@ -8,7 +8,6 @@ import (
)
func SendEmail(subject string, text string, to ...string) bool {
//TODO: add logs
e := email.Email{
From: "nCovTrack Server<1853633282@qq.com>",
To: to,
@@ -20,6 +19,7 @@ func SendEmail(subject string, text string, to ...string) bool {
smtp.PlainAuth("", global.ServerSettings.Email.Account, global.ServerSettings.Email.Password, global.ServerSettings.Email.Host),
)
if err != nil {
fmt.Println("Send failed")
return false
}
return true

View File

@@ -31,7 +31,11 @@ func GetWithHeader(url string, header map[string]string) string {
cost := time.Since(startTime)
var logParams []interface{}
logParams = append(logParams,
"reqest", req,
"reqest", map[string]interface{}{
"url": req.URL,
"method": req.Method,
"header": req.Header,
},
"cost", cost.String(),
)
if err != nil {

10
utils/json.go Normal file
View File

@@ -0,0 +1,10 @@
package utils
import (
"encoding/json"
)
func Strcut2Map[S any, T *map[string]interface{} | *[]map[string]interface{}](source S, target T) {
jsonByte, _ := json.Marshal(source)
json.Unmarshal(jsonByte, target)
}

View File

@@ -1,10 +1,12 @@
package utils
import (
"fmt"
"encoding/json"
"nCovTrack-Backend/global"
"nCovTrack-Backend/models"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
)
@@ -56,7 +58,7 @@ func RenewToken(tokenStr string) string {
if expireDuration.Hours() > float64(global.ServerSettings.Jwt.RenewAheadDays*24) {
return tokenStr
}
fmt.Println(expireDuration)
//fmt.Println(expireDuration)
claims["exp"] = time.Now().Add(global.TOKEN_EXPIRE_DAYS * 24 * time.Hour).Unix()
token = jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
@@ -81,3 +83,9 @@ func ParseClaims(tokenStr string) jwt.MapClaims {
}
return token.Claims.(jwt.MapClaims)
}
func ClaimsFromHeader(c *gin.Context) models.TokenClaims {
var claims models.TokenClaims
json.Unmarshal([]byte(c.Request.Header.Get("claims")), &claims)
return claims
}

View File

@@ -15,3 +15,24 @@ func Map[T any, V any](arr []T, fun func(item T) V) []V {
}
return res
}
func Distinct[T comparable](arr []T) []T {
set := map[T]interface{}{}
for _, item := range arr {
set[item] = nil
}
var res []T
for k := range set {
res = append(res, k)
}
return res
}
func Contains[T comparable](arr []T, item T) bool {
for _, a := range arr {
if a == item {
return true
}
}
return false
}

View File

@@ -1,40 +0,0 @@
package utils
type void struct{}
// This is set with generic
type Set[T comparable] struct {
setMap map[T]void
}
func NewSet[T comparable](eles ...T) *Set[T] {
set := &Set[T]{setMap: make(map[T]void)}
set.AddAll(eles...)
return set
}
func (set *Set[T]) Add(ele T) *Set[T] {
set.setMap[ele] = void{}
return set
}
func (set *Set[T]) AddAll(eles ...T) *Set[T] {
for _, ele := range eles {
set.Add(ele)
}
return set
}
func (set *Set[T]) Delete(ele T) *Set[T] {
delete(set.setMap, ele)
return set
}
func (set *Set[T]) ToSlice() []T {
var s []T
for k, _ := range set.setMap {
s = append(s, k)
}
return s
}