1- // Package main initializes and starts a URL shortening service using Echo framework,
2- // connecting to a MySQL database and providing endpoints for shortening and resolving URLs
1+ // Package main implements the HTTP server for the URL shortening service.
32package main
43
54import (
@@ -15,13 +14,32 @@ import (
1514 "github.com/labstack/echo/middleware"
1615)
1716
17+ // ensure UTC in JSON responses for determinism across environments
18+ func utcInfo (in * uss.ShortCodeInfo ) * uss.ShortCodeInfo {
19+ if in == nil {
20+ return nil
21+ }
22+ cp := * in
23+ cp .EndTime = cp .EndTime .UTC ()
24+ cp .UpdatedAt = cp .UpdatedAt .UTC ()
25+ return & cp
26+ }
27+ func utcInfos (in []uss.ShortCodeInfo ) []uss.ShortCodeInfo {
28+ out := make ([]uss.ShortCodeInfo , len (in ))
29+ for i := range in {
30+ out [i ] = in [i ]
31+ out [i ].EndTime = in [i ].EndTime .UTC ()
32+ out [i ].UpdatedAt = in [i ].UpdatedAt .UTC ()
33+ }
34+ return out
35+ }
36+
1837func main () {
1938 time .Sleep (2 * time .Second )
2039 appConfig , err := godotenv .Read ()
2140 if err != nil {
2241 log .Printf ("Error reading .env file %s" , err .Error ())
2342 os .Exit (1 )
24-
2543 }
2644
2745 uss .MetaStore = & uss.Store {}
@@ -36,26 +54,20 @@ func main() {
3654func StartHTTPServer () {
3755 e := echo .New ()
3856 e .Use (middleware .LoggerWithConfig (middleware.LoggerConfig {
39- Format : `${remote_ip} [${time_rfc3339}] "${method} ${uri} HTTP/1.0" ${status} ${latency_human} ${bytes_out} ${error} "${user_agent}"` + "\n " ,
40- Skipper : func (c echo.Context ) bool {
41- return c .Request ().RequestURI == "/healthcheck"
42- },
57+ Format : `${remote_ip} [${time_rfc3339}] "${method} ${uri} HTTP/1.0" ${status} ${latency_human} ${bytes_out} ${error} "${user_agent}"` + "\n " ,
58+ Skipper : func (c echo.Context ) bool { return c .Request ().RequestURI == "/healthcheck" },
4359 }))
4460 e .Use (middleware .Recover ())
45- e .GET ("/" , func (c echo.Context ) error {
46- return c .String (http .StatusOK , "Hello, World!" )
47- })
48- e .GET ("/healthcheck" , func (c echo.Context ) error {
49- return c .String (http .StatusOK , "good!" )
50- })
61+
62+ e .GET ("/" , func (c echo.Context ) error { return c .String (http .StatusOK , "Hello, World!" ) })
63+ e .GET ("/healthcheck" , func (c echo.Context ) error { return c .String (http .StatusOK , "good!" ) })
5164
5265 e .GET ("/resolve/:code" , func (c echo.Context ) error {
5366 code := c .Param ("code" )
5467 info := uss .MetaStore .FindByShortCode (code )
5568 if info != nil {
56- return c .JSON (http .StatusOK , info )
69+ return c .JSON (http .StatusOK , utcInfo ( info ) )
5770 }
58-
5971 return c .String (http .StatusNotFound , "Not Found." )
6072 })
6173
@@ -64,19 +76,128 @@ func StartHTTPServer() {
6476 if err := c .Bind (req ); err != nil {
6577 return err
6678 }
67-
6879 req .ShortCode = uss .GenerateShortLink (req .URL )
69- err := uss .MetaStore .Persist (req )
70- if err != nil {
80+ if err := uss .MetaStore .Persist (req ); err != nil {
7181 return c .String (http .StatusInternalServerError , fmt .Sprintf ("Failed Persisiting Entity with Error %s" , err .Error ()))
7282 }
73-
7483 req .UpdatedAt = req .UpdatedAt .Truncate (time .Second )
75- return c .JSON (http .StatusOK , req )
84+ return c .JSON (http .StatusOK , utcInfo (req ))
85+ })
86+
87+ // Original "seed" kept (now sets CreatedBy too)
88+ e .POST ("/seed" , func (c echo.Context ) error {
89+ end := time .Date (9999 , 1 , 1 , 0 , 0 , 0 , 0 , time .UTC )
90+ info := & uss.ShortCodeInfo {
91+ ShortCode : "dt-sentinel-9999-01-01T00:00:00Z" ,
92+ URL : "https://example.com/sentinel-start" ,
93+ EndTime : end ,
94+ CreatedBy : "keploy.io/dates" ,
95+ }
96+ if err := uss .MetaStore .UpsertByShortCode (info ); err != nil {
97+ return c .String (http .StatusInternalServerError , err .Error ())
98+ }
99+ return c .JSON (http .StatusOK , utcInfo (info ))
100+ })
101+
102+ // seed a set of edge-case datetimes
103+ e .POST ("/seed/dates" , func (c echo.Context ) error {
104+ nowMicro := uss .ToMicroUTC (time .Now ())
105+
106+ // Labels become ShortCode so they’re easy to fetch directly.
107+ payload := []* uss.ShortCodeInfo {
108+ {
109+ ShortCode : "dt-sentinel-9999-01-01T00:00:00Z" ,
110+ URL : "https://example.com/sentinel-start" ,
111+ EndTime : uss .SentinelStart ,
112+ CreatedBy : "keploy.io/dates" ,
113+ },
114+ {
115+ ShortCode : "dt-max-9999-12-31T23:59:59.999999Z" ,
116+ URL : "https://example.com/sentinel-max" ,
117+ EndTime : uss .SentinelMax ,
118+ CreatedBy : "keploy.io/dates" ,
119+ },
120+ {
121+ ShortCode : "dt-min-1000-01-01T00:00:00Z" ,
122+ URL : "https://example.com/min-valid" ,
123+ EndTime : time .Date (1000 , 1 , 1 , 0 , 0 , 0 , 0 , time .UTC ),
124+ CreatedBy : "keploy.io/dates" ,
125+ },
126+ {
127+ ShortCode : "dt-epoch-1970-01-01T00:00:00Z" ,
128+ URL : "https://example.com/epoch" ,
129+ EndTime : time .Date (1970 , 1 , 1 , 0 , 0 , 0 , 0 , time .UTC ),
130+ CreatedBy : "keploy.io/dates" ,
131+ },
132+ {
133+ ShortCode : "dt-leap-2020-02-29T12:34:56Z" ,
134+ URL : "https://example.com/leap" ,
135+ EndTime : time .Date (2020 , 2 , 29 , 12 , 34 , 56 , 0 , time .UTC ),
136+ CreatedBy : "keploy.io/dates" ,
137+ },
138+ {
139+ ShortCode : "dt-offset-2023-07-01T18:30:00+05:30" ,
140+ URL : "https://example.com/offset" ,
141+ EndTime : time .Date (2023 , 7 , 1 , 18 , 30 , 0 , 0 , time .FixedZone ("IST" , 5 * 3600 + 30 * 60 )),
142+ CreatedBy : "keploy.io/dates" ,
143+ },
144+ {
145+ ShortCode : "dt-now-trunc" ,
146+ URL : "https://example.com/now" ,
147+ EndTime : nowMicro ,
148+ CreatedBy : "keploy.io/dates" ,
149+ },
150+ }
151+
152+ if err := uss .MetaStore .UpsertMany (payload ); err != nil {
153+ return c .String (http .StatusInternalServerError , err .Error ())
154+ }
155+ // Return what we wrote (UTC’d for JSON determinism)
156+ resp := make ([]uss.ShortCodeInfo , 0 , len (payload ))
157+ for _ , p := range payload {
158+ if got := uss .MetaStore .FindByShortCode (p .ShortCode ); got != nil {
159+ resp = append (resp , * utcInfo (got ))
160+ }
161+ }
162+ return c .JSON (http .StatusOK , resp )
163+ })
164+
165+ // exact EndTime query (RFC3339/RFC3339Nano/MySQL-like)
166+ e .GET ("/query/by-endtime" , func (c echo.Context ) error {
167+ ts := c .QueryParam ("ts" )
168+ if ts == "" {
169+ return c .String (http .StatusBadRequest , "query param 'ts' required (RFC3339 or MySQL datetime)" )
170+ }
171+ t , err := uss .ParseFlexible (ts )
172+ if err != nil {
173+ return c .String (http .StatusBadRequest , fmt .Sprintf ("parse error: %v" , err ))
174+ }
175+ infos := uss .MetaStore .FindByEndTime (t )
176+ return c .JSON (http .StatusOK , utcInfos (infos ))
177+ })
178+
179+ // fetch both sentinels
180+ e .GET ("/query/sentinels" , func (c echo.Context ) error {
181+ infos := uss .MetaStore .FindSentinels ()
182+ return c .JSON (http .StatusOK , utcInfos (infos ))
183+ })
184+
185+ // fetch any seeded date rows
186+ e .GET ("/query/dates" , func (c echo.Context ) error {
187+ infos := uss .MetaStore .FindSeededDates ()
188+ return c .JSON (http .StatusOK , utcInfos (infos ))
189+ })
190+
191+ // fetch by label (stored in ShortCode)
192+ e .GET ("/query/label/:label" , func (c echo.Context ) error {
193+ label := c .Param ("label" )
194+ info := uss .MetaStore .FindByShortCode (label )
195+ if info == nil {
196+ return c .String (http .StatusNotFound , "Not Found." )
197+ }
198+ return c .JSON (http .StatusOK , utcInfo (info ))
76199 })
77200
78- // automatically add routers for net/http/pprof e.g. /debug/pprof, /debug/pprof/heap, etc.
79- // go get github.com/hiko1129/echo-pprof
80- //echopprof.Wrap(e)
201+ // e.g., echopprof.Wrap(e)
81202 e .Logger .Fatal (e .Start (":9090" ))
82203}
0 commit comments