diff --git a/cmd/webserver.go b/cmd/webserver.go index f104304a..15fb59b4 100644 --- a/cmd/webserver.go +++ b/cmd/webserver.go @@ -137,6 +137,12 @@ func startWebServer(c *cli.Context) error { router.GET("/healthz.json", bindApi(api.Healths.HealthStatusHandler)) + proxyRoute := router.Group("/proxy") + proxyRoute.Use(bindMiddleware(middlewares.JWTAuthorizationByQueryString)) + { + proxyRoute.GET("/openstreetmap/tile/:zoomLevel/:coordinateX/:fileName", bindProxy(api.MapImages.OpenStreetMapTileImageProxyHandler)) + } + apiRoute := router.Group("/api") apiRoute.Use(bindMiddleware(middlewares.RequestId(config))) @@ -288,3 +294,16 @@ func bindCsv(fn core.DataHandlerFunc) gin.HandlerFunc { } } } + +func bindProxy(fn core.ProxyHandlerFunc) gin.HandlerFunc { + return func(ginCtx *gin.Context) { + c := core.WrapContext(ginCtx) + proxy, err := fn(c) + + if err != nil { + utils.PrintDataErrorResult(c, "text/text", err) + } else { + proxy.ServeHTTP(c.Writer, c.Request) + } + } +} diff --git a/package-lock.json b/package-lock.json index 5df74482..f588aa8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "framework7-icons": "^5.0.5", "framework7-vue": "^8.0.3", "js-cookie": "^3.0.1", + "leaflet": "^1.9.3", "line-awesome": "^1.3.0", "moment": "^2.29.4", "moment-timezone": "^0.5.43", @@ -5200,6 +5201,11 @@ "node": ">=0.10.0" } }, + "node_modules/leaflet": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.3.tgz", + "integrity": "sha512-iB2cR9vAkDOu5l3HAay2obcUHZ7xwUBBjph8+PGtmW/2lYhbLizWtG7nTeYht36WfOslixQF9D/uSIzhZgGMfQ==" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", diff --git a/package.json b/package.json index 57a607ef..dd7e7e3c 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "framework7-icons": "^5.0.5", "framework7-vue": "^8.0.3", "js-cookie": "^3.0.1", + "leaflet": "^1.9.3", "line-awesome": "^1.3.0", "moment": "^2.29.4", "moment-timezone": "^0.5.43", diff --git a/pkg/api/map_image_proxies.go b/pkg/api/map_image_proxies.go new file mode 100644 index 00000000..0c31feb2 --- /dev/null +++ b/pkg/api/map_image_proxies.go @@ -0,0 +1,41 @@ +package api + +import ( + "fmt" + "net/http" + "net/http/httputil" + "net/url" + + "github.com/mayswind/ezbookkeeping/pkg/core" + "github.com/mayswind/ezbookkeeping/pkg/errs" +) + +const openStreetMapTileImageUrlFormat = "https://tile.openstreetmap.org/%s/%s/%s" // https://tile.openstreetmap.org/{z}/{x}/{y}.png + +// MapImageProxy represents map image proxy +type MapImageProxy struct { +} + +// Initialize a map image proxy singleton instance +var ( + MapImages = &MapImageProxy{} +) + +// OpenStreetMapTileImageProxyHandler returns open street map tile image +func (p *MapImageProxy) OpenStreetMapTileImageProxyHandler(c *core.Context) (*httputil.ReverseProxy, *errs.Error) { + director := func(req *http.Request) { + zoomLevel := c.Param("zoomLevel") + coordinateX := c.Param("coordinateX") + fileName := c.Param("fileName") + + imageRawUrl := fmt.Sprintf(openStreetMapTileImageUrlFormat, zoomLevel, coordinateX, fileName) + imageUrl, _ := url.Parse(imageRawUrl) + + req.Header.Del("Authorization") + req.URL = imageUrl + req.RequestURI = req.URL.RequestURI() + req.Host = imageUrl.Host + } + + return &httputil.ReverseProxy{Director: director}, nil +} diff --git a/pkg/core/handler.go b/pkg/core/handler.go index e6804c04..9a88140a 100644 --- a/pkg/core/handler.go +++ b/pkg/core/handler.go @@ -1,6 +1,10 @@ package core -import "github.com/mayswind/ezbookkeeping/pkg/errs" +import ( + "net/http/httputil" + + "github.com/mayswind/ezbookkeeping/pkg/errs" +) // MiddlewareHandlerFunc represents the middleware handler function type MiddlewareHandlerFunc func(*Context) @@ -10,3 +14,6 @@ type ApiHandlerFunc func(*Context) (interface{}, *errs.Error) // DataHandlerFunc represents the handler function that returns byte array type DataHandlerFunc func(*Context) ([]byte, string, *errs.Error) + +// ProxyHandlerFunc represents the reverse proxy handler function +type ProxyHandlerFunc func(*Context) (*httputil.ReverseProxy, *errs.Error) diff --git a/pkg/middlewares/authorization.go b/pkg/middlewares/authorization.go index 6d5e429f..6161bc95 100644 --- a/pkg/middlewares/authorization.go +++ b/pkg/middlewares/authorization.go @@ -37,6 +37,21 @@ func JWTAuthorization(c *core.Context) { c.Next() } +// JWTAuthorizationByQueryString verifies whether current request is valid by jwt token +func JWTAuthorizationByQueryString(c *core.Context) { + token, exists := c.GetQuery(tokenQueryStringParam) + + if !exists { + log.WarnfWithRequestId(c, "[authorization.JWTAuthorizationByQueryString] no token provided") + utils.PrintJsonErrorResult(c, errs.ErrUnauthorizedAccess) + return + } + + c.Request.Header.Set("Authorization", token) + + JWTAuthorization(c) +} + // JWTTwoFactorAuthorization verifies whether current request is valid by 2fa passcode func JWTTwoFactorAuthorization(c *core.Context) { claims, err := getTokenClaims(c) diff --git a/src/components/mobile/MapSheet.vue b/src/components/mobile/MapSheet.vue new file mode 100644 index 00000000..f788518e --- /dev/null +++ b/src/components/mobile/MapSheet.vue @@ -0,0 +1,116 @@ + + + diff --git a/src/lib/services.js b/src/lib/services.js index bad91bab..8b445a7a 100644 --- a/src/lib/services.js +++ b/src/lib/services.js @@ -389,4 +389,9 @@ export default { ignoreError: !!ignoreError }); }, + generateOpenStreetMapTileImageUrl: () => { + const token = userState.getToken(); + + return 'proxy/openstreetmap/tile/{z}/{x}/{y}.png?token=' + token; + }, }; diff --git a/src/locales/en.js b/src/locales/en.js index bf87ee50..36716f81 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -703,6 +703,8 @@ export default { 'Back': 'Back', 'Load More': 'Load More', 'No data': 'No data', + 'Zoom in': 'Zoom in', + 'Zoom out': 'Zoom out', 'Change Language': 'Change Language', 'Date is too early': 'Date is too early', 'Unlock Application': 'Unlock Application', @@ -833,6 +835,7 @@ export default { 'Geographic Location': 'Geographic Location', 'No Location': 'No Location', 'Getting Location...': 'Getting Location...', + 'Show on the map': 'Show on the map', 'Update Geographic Location': 'Update Geographic Location', 'Clear Geographic Location': 'Clear Geographic Location', 'Unable to get current position': 'Unable to get current position', diff --git a/src/locales/zh_Hans.js b/src/locales/zh_Hans.js index 1a78a470..bda8f3c2 100644 --- a/src/locales/zh_Hans.js +++ b/src/locales/zh_Hans.js @@ -703,6 +703,8 @@ export default { 'Back': '返回', 'Load More': '加载更多', 'No data': '没有数据', + 'Zoom in': '放大', + 'Zoom out': '缩小', 'Change Language': '修改语言', 'Date is too early': '日期过早', 'Unlock Application': '解锁应用', @@ -833,6 +835,7 @@ export default { 'Geographic Location': '地理位置', 'No Location': '没有位置', 'Getting Location...': '正在获取位置...', + 'Show on the map': '在地图上显示', 'Update Geographic Location': '更新地理位置', 'Clear Geographic Location': '清除地理位置', 'Unable to get current position': '无法获取当前地理位置', diff --git a/src/mobile-main.js b/src/mobile-main.js index ff8daaef..ff75c29c 100644 --- a/src/mobile-main.js +++ b/src/mobile-main.js @@ -73,6 +73,9 @@ import 'line-awesome/dist/line-awesome/css/line-awesome.css'; import VueDatePicker from '@vuepic/vue-datepicker'; import '@vuepic/vue-datepicker/dist/main.css'; +import * as Leaflet from 'leaflet/dist/leaflet-src.esm.js'; +import 'leaflet/dist/leaflet.css'; + import api from './consts/api.js'; import datetime from './consts/datetime.js'; import currency from './consts/currency.js'; @@ -135,6 +138,7 @@ import IconSelectionSheet from './components/mobile/IconSelectionSheet.vue'; import ColorSelectionSheet from './components/mobile/ColorSelectionSheet.vue'; import InformationSheet from './components/mobile/InformationSheet.vue'; import NumberPadSheet from './components/mobile/NumberPadSheet.vue'; +import MapSheet from './components/mobile/MapSheet.vue'; import TransactionTagSelectionSheet from './components/mobile/TransactionTagSelectionSheet.vue'; import TextareaAutoSize from "./directives/mobile/textareaAutoSize.js"; @@ -254,6 +258,7 @@ app.component('IconSelectionSheet', IconSelectionSheet); app.component('ColorSelectionSheet', ColorSelectionSheet); app.component('InformationSheet', InformationSheet); app.component('NumberPadSheet', NumberPadSheet); +app.component('MapSheet', MapSheet); app.component('TransactionTagSelectionSheet', TransactionTagSelectionSheet); app.directive('TextareaAutoSize', TextareaAutoSize); @@ -299,6 +304,10 @@ app.config.globalProperties.$locale = { getDisplayCurrency: (value, currencyCode, notConvertValue) => getDisplayCurrency(value, currencyCode, notConvertValue, i18n.global.t), initLocale: initLocale }; +app.config.globalProperties.$map = { + leaflet: Leaflet, + generateOpenStreetMapTileImageUrl: services.generateOpenStreetMapTileImageUrl +}; app.config.globalProperties.$tIf = (text, isTranslate) => transateIf(text, isTranslate, i18n.global.t); app.config.globalProperties.$alert = (message, confirmCallback) => showAlert(message, confirmCallback, i18n.global.t); diff --git a/src/views/mobile/transactions/EditPage.vue b/src/views/mobile/transactions/EditPage.vue index 58e7ed02..99454ce3 100644 --- a/src/views/mobile/transactions/EditPage.vue +++ b/src/views/mobile/transactions/EditPage.vue @@ -38,12 +38,12 @@ -