Pre graphql
This commit is contained in:
		
							parent
							
								
									dc56899b8b
								
							
						
					
					
						commit
						ecf981495e
					
				
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							| @ -3,6 +3,7 @@ module reichard.io/imagini | |||||||
| go 1.15 | go 1.15 | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
|  | 	github.com/codeon/govips v0.0.0-20200329201227-415341c0ce33 // indirect | ||||||
| 	github.com/dgrijalva/jwt-go v3.2.0+incompatible | 	github.com/dgrijalva/jwt-go v3.2.0+incompatible | ||||||
| 	github.com/disintegration/imaging v1.6.2 // indirect | 	github.com/disintegration/imaging v1.6.2 // indirect | ||||||
| 	github.com/dsoprea/go-exif/v3 v3.0.0-20201216222538-db167117f483 | 	github.com/dsoprea/go-exif/v3 v3.0.0-20201216222538-db167117f483 | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							| @ -13,6 +13,8 @@ github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+Wji | |||||||
| github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | ||||||
| github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | ||||||
| github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= | ||||||
|  | github.com/codeon/govips v0.0.0-20200329201227-415341c0ce33 h1:/wmSAm0UZlQ/NmiwUlThjUUHe2cD36rjM61px1X6ccM= | ||||||
|  | github.com/codeon/govips v0.0.0-20200329201227-415341c0ce33/go.mod h1:kXwwWC7hMGnPrV6bw8gFi2k0ZLTiYTNMM+G3D9cUBt0= | ||||||
| github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= | ||||||
| github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= | ||||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ import ( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (api *API) loginHandler(w http.ResponseWriter, r *http.Request) { | func (api *API) loginHandler(w http.ResponseWriter, r *http.Request) { | ||||||
|  |     w.Header().Set("Access-Control-Allow-Origin", "*") | ||||||
|     if r.Method != http.MethodPost { |     if r.Method != http.MethodPost { | ||||||
|         errorJSON(w, "Method is not supported.", http.StatusMethodNotAllowed) |         errorJSON(w, "Method is not supported.", http.StatusMethodNotAllowed) | ||||||
|         return |         return | ||||||
| @ -77,72 +78,21 @@ func (api *API) logoutHandler(w http.ResponseWriter, r *http.Request) { | |||||||
|     successJSON(w, "Logout success.", http.StatusOK) |     successJSON(w, "Logout success.", http.StatusOK) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (api *API) refreshLoginHandler(w http.ResponseWriter, r *http.Request) { |  | ||||||
|     refreshCookie, err := r.Cookie("RefreshToken") |  | ||||||
|     if err != nil { |  | ||||||
|         log.Warn("[middleware] Cookie not found") |  | ||||||
|         w.WriteHeader(http.StatusUnauthorized) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Validate Refresh Token |  | ||||||
|     refreshToken, ok := api.Auth.ValidateJWTRefreshToken(refreshCookie.Value) |  | ||||||
|     if !ok { |  | ||||||
|         http.SetCookie(w, &http.Cookie{Name: "AccessToken", Expires: time.Unix(0, 0)}) |  | ||||||
|         http.SetCookie(w, &http.Cookie{Name: "RefreshToken", Expires: time.Unix(0, 0)}) |  | ||||||
|         errorJSON(w, "Invalid credentials.", http.StatusUnauthorized) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Acquire User & Device (Trusted) |  | ||||||
|     did, ok := refreshToken.Get("did") |  | ||||||
|     if !ok { |  | ||||||
|         errorJSON(w, "Invalid credentials.", http.StatusUnauthorized) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
|     uid, ok := refreshToken.Get(jwt.SubjectKey) |  | ||||||
|     if !ok { |  | ||||||
|         errorJSON(w, "Invalid credentials.", http.StatusUnauthorized) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
|     deviceID, err := uuid.Parse(fmt.Sprintf("%v", did)) |  | ||||||
|     if err != nil { |  | ||||||
|         errorJSON(w, "Invalid credentials.", http.StatusUnauthorized) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
|     userID, err := uuid.Parse(fmt.Sprintf("%v", uid)) |  | ||||||
|     if err != nil { |  | ||||||
|         errorJSON(w, "Invalid credentials.", http.StatusUnauthorized) |  | ||||||
|         return |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Device Skeleton |  | ||||||
|     user := models.User{Base: models.Base{UUID: userID}} |  | ||||||
|     device := models.Device{Base: models.Base{UUID: deviceID}} |  | ||||||
| 
 |  | ||||||
|     // Update token |  | ||||||
|     accessToken, err := api.Auth.CreateJWTAccessToken(user, device) |  | ||||||
|     accessCookie := http.Cookie{Name: "AccessToken", Value: accessToken} |  | ||||||
|     http.SetCookie(w, &accessCookie) |  | ||||||
| 
 |  | ||||||
|     // Response success |  | ||||||
|     successJSON(w, "Refresh success.", http.StatusOK) |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * This will find or create the requested device based on ID and User. |  * This will find or create the requested device based on ID and User. | ||||||
|  **/ |  **/ | ||||||
| func (api *API) upsertRequestedDevice(user models.User, r *http.Request) (models.Device, error) { | func (api *API) upsertRequestedDevice(user models.User, r *http.Request) (models.Device, error) { | ||||||
|     requestedDevice := deriveRequestedDevice(r) |     requestedDevice := deriveRequestedDevice(r) | ||||||
|     requestedDevice.Type = deriveDeviceType(r) |     requestedDevice.Type = deriveDeviceType(r) | ||||||
|     requestedDevice.User = user |     requestedDevice.UserUUID = user.UUID | ||||||
| 
 | 
 | ||||||
|     if requestedDevice.UUID == uuid.Nil { |     if requestedDevice.UUID == uuid.Nil { | ||||||
|         createdDevice, err := api.DB.CreateDevice(requestedDevice) |         err := api.DB.CreateDevice(&requestedDevice) | ||||||
|  |         createdDevice, err := api.DB.Device(&requestedDevice) | ||||||
|         return createdDevice, err |         return createdDevice, err | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     foundDevice, err := api.DB.Device(models.Device{ |     foundDevice, err := api.DB.Device(&models.Device{ | ||||||
|         Base: models.Base{ UUID: requestedDevice.UUID }, |         Base: models.Base{ UUID: requestedDevice.UUID }, | ||||||
|         User: user, |         User: user, | ||||||
|     }) |     }) | ||||||
| @ -213,6 +163,60 @@ func deriveRequestedDevice(r *http.Request) models.Device { | |||||||
|     return deviceSkeleton |     return deviceSkeleton | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (api *API) refreshAccessToken(w http.ResponseWriter, r *http.Request) (jwt.Token, error) { | ||||||
|  |     refreshCookie, err := r.Cookie("RefreshToken") | ||||||
|  |     if err != nil { | ||||||
|  |         log.Warn("[middleware] RefreshToken not found") | ||||||
|  |         return nil, err | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Validate Refresh Token | ||||||
|  |     refreshToken, err := api.Auth.ValidateJWTRefreshToken(refreshCookie.Value) | ||||||
|  |     if err != nil { | ||||||
|  |         http.SetCookie(w, &http.Cookie{Name: "AccessToken", Expires: time.Unix(0, 0)}) | ||||||
|  |         http.SetCookie(w, &http.Cookie{Name: "RefreshToken", Expires: time.Unix(0, 0)}) | ||||||
|  |         return nil, err | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Acquire User & Device (Trusted) | ||||||
|  |     did, ok := refreshToken.Get("did") | ||||||
|  |     if !ok { | ||||||
|  |         return nil, err | ||||||
|  |     } | ||||||
|  |     uid, ok := refreshToken.Get(jwt.SubjectKey) | ||||||
|  |     if !ok { | ||||||
|  |         return nil, err | ||||||
|  |     } | ||||||
|  |     deviceUUID, err := uuid.Parse(fmt.Sprintf("%v", did)) | ||||||
|  |     if err != nil { | ||||||
|  |         return nil, err | ||||||
|  |     } | ||||||
|  |     userUUID, err := uuid.Parse(fmt.Sprintf("%v", uid)) | ||||||
|  |     if err != nil { | ||||||
|  |         return nil, err | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Device & User Skeleton | ||||||
|  |     user := models.User{Base: models.Base{UUID: userUUID}} | ||||||
|  |     device := models.Device{Base: models.Base{UUID: deviceUUID}} | ||||||
|  | 
 | ||||||
|  |     // Update token | ||||||
|  |     accessTokenString, err := api.Auth.CreateJWTAccessToken(user, device) | ||||||
|  |     if err != nil { | ||||||
|  |         return nil, err | ||||||
|  |     } | ||||||
|  |     accessCookie := http.Cookie{Name: "AccessToken", Value: accessTokenString} | ||||||
|  |     http.SetCookie(w, &accessCookie) | ||||||
|  | 
 | ||||||
|  |     // TODO: Update Refresh Key & Token | ||||||
|  | 
 | ||||||
|  |     // Convert to jwt.Token | ||||||
|  |     accessTokenBytes := []byte(accessTokenString) | ||||||
|  |     accessToken, err := jwt.ParseBytes(accessTokenBytes) | ||||||
|  | 
 | ||||||
|  |     return accessToken, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func trimQuotes(s string) string { | func trimQuotes(s string) string { | ||||||
|     if len(s) >= 2 { |     if len(s) >= 2 { | ||||||
|         if s[0] == '"' && s[len(s)-1] == '"' { |         if s[0] == '"' && s[len(s)-1] == '"' { | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| package api | package api | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  |     "os" | ||||||
|     "path" |     "path" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| ) | ) | ||||||
| @ -17,6 +18,17 @@ func (api *API) mediaHandler(w http.ResponseWriter, r *http.Request) { | |||||||
|         return |         return | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // Acquire Width & Height Parameters | ||||||
|  |     query := r.URL.Query() | ||||||
|  |     width := query["width"] | ||||||
|  |     height := query["height"] | ||||||
|  |     _ = width | ||||||
|  |     _ = height | ||||||
|  | 
 | ||||||
|  |     // TODO: Caching & Resizing | ||||||
|  |     //  - If both, force resize with new scale | ||||||
|  |     //  - If one, scale resize proportionally | ||||||
|  | 
 | ||||||
|     // Pull out UUIDs |     // Pull out UUIDs | ||||||
|     reqInfo := r.Context().Value("uuids").(map[string]string) |     reqInfo := r.Context().Value("uuids").(map[string]string) | ||||||
|     uid := reqInfo["uid"] |     uid := reqInfo["uid"] | ||||||
| @ -26,5 +38,13 @@ func (api *API) mediaHandler(w http.ResponseWriter, r *http.Request) { | |||||||
|     folderPath := path.Join("/" + api.Config.DataPath + "/media/" + uid) |     folderPath := path.Join("/" + api.Config.DataPath + "/media/" + uid) | ||||||
|     mediaPath := path.Join(folderPath + "/" + fileName) |     mediaPath := path.Join(folderPath + "/" + fileName) | ||||||
| 
 | 
 | ||||||
|  |     // Check if File Exists | ||||||
|  |     _, err := os.Stat(mediaPath) | ||||||
|  |     if os.IsNotExist(err) { | ||||||
|  |         // TODO: Different HTTP Response Code? | ||||||
|  |         w.WriteHeader(http.StatusMethodNotAllowed) | ||||||
|  |         return | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     http.ServeFile(w, r, mediaPath) |     http.ServeFile(w, r, mediaPath) | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,10 +3,16 @@ package api | |||||||
| import ( | import ( | ||||||
| 	"io" | 	"io" | ||||||
| 	"os" | 	"os" | ||||||
|     "time" | 	"fmt" | ||||||
| 	"path" | 	"path" | ||||||
|  | 	"time" | ||||||
|  | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |     "errors" | ||||||
|  | 	"net/url" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"encoding/json" | ||||||
|  | 
 | ||||||
| 	"github.com/google/uuid" | 	"github.com/google/uuid" | ||||||
| 	"github.com/dsoprea/go-exif/v3" | 	"github.com/dsoprea/go-exif/v3" | ||||||
| 	log "github.com/sirupsen/logrus" | 	log "github.com/sirupsen/logrus" | ||||||
| @ -16,7 +22,6 @@ import ( | |||||||
| 	"reichard.io/imagini/internal/models" | 	"reichard.io/imagini/internal/models" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| // GET | // GET | ||||||
| //  - /api/v1/MediaItems/<GUID> | //  - /api/v1/MediaItems/<GUID> | ||||||
| //      - JSON Struct | //      - JSON Struct | ||||||
| @ -34,14 +39,88 @@ func (api *API) mediaItemsHandler(w http.ResponseWriter, r *http.Request) { | |||||||
|         // DELETE |         // DELETE | ||||||
|     } else if r.Method == http.MethodGet { |     } else if r.Method == http.MethodGet { | ||||||
|         // GET |         // GET | ||||||
|  |         api.mediaItemGETHandler(w, r) | ||||||
|     } else { |     } else { | ||||||
|         errorJSON(w, "Method is not supported.", http.StatusMethodNotAllowed) |         errorJSON(w, "Method is not supported.", http.StatusMethodNotAllowed) | ||||||
|         return |         return | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Paging: | ||||||
|  | //  - Regular Pagination: | ||||||
|  | //      - /api/v1/MediaItems?page[limit]=50&page=2 | ||||||
|  | //  - Meta Count Only | ||||||
|  | //      - /api/v1/MediaItems?page[limit]=0 | ||||||
|  | 
 | ||||||
|  | // Sorting: | ||||||
|  | //  - Ascending Sort: | ||||||
|  | //      - /api/v1/MediaItems?sort=created_at | ||||||
|  | //  - Descending Sort: | ||||||
|  | //      - /api/v1/MediaItems?sort=-created_at | ||||||
|  | 
 | ||||||
|  | // Filters: | ||||||
|  | //  - Greater Than / Less Than (created_at, updated_at, exif_date) | ||||||
|  | //      - /api/v1/MediaItems?filter[created_at]>=2020-01-01&filter[created_at]<=2021-01-01 | ||||||
|  | //  - Long / Lat Range (latitude, longitude) | ||||||
|  | //      - /api/v1/MediaItems?filter[latitude]>=71.1827&filter[latitude]<=72.0000&filter[longitude]>=100.000&filter[longitude]<=101.0000 | ||||||
|  | //  - Image / Video (media_type) | ||||||
|  | //      - /api/v1/MediaItems?filter[media_type]=Image | ||||||
|  | //  - Tags (tags) | ||||||
|  | //      - /api/v1/MediaItems?filter[tags]=id1,id2,id3 | ||||||
|  | //  - Albums (albums) | ||||||
|  | //      - /api/v1/MediaItems?filter[albums]=id1 | ||||||
|  | func (api *API) mediaItemGETHandler(w http.ResponseWriter, r *http.Request) { | ||||||
|  |     if err := r.ParseForm(); err != nil { | ||||||
|  |         // Handle error | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     testObj := models.MediaItem{} | ||||||
|  |     json.NewDecoder().Decode(&testObj) | ||||||
|  |     fmt.Printf("Result: %+v\n", testObj) | ||||||
|  | 
 | ||||||
|  |     // allParams, err := json.Marshal(r.Form) | ||||||
|  |     // if err != nil { | ||||||
|  |     //     // Handle error | ||||||
|  |     // } | ||||||
|  | 
 | ||||||
|  |     // filter := &models.MediaItem{} | ||||||
|  |     // if err = json.Unmarshal(allParams, filter); err != nil { | ||||||
|  |     //     // Handle error | ||||||
|  |     //     fmt.Printf("Fuck: %s\n", err) | ||||||
|  |     // } | ||||||
|  | 
 | ||||||
|  |     // fmt.Printf("Result: %+v\n", filter) | ||||||
|  | 
 | ||||||
|  |     // err = normalizeForm(r.Form, models.MediaItem{}) | ||||||
|  |     // if err != nil { | ||||||
|  |     //     fmt.Printf("Error: %s\n", err) | ||||||
|  |     // } | ||||||
|  | 
 | ||||||
|  |     // var testItems []models.MediaItem | ||||||
|  |     // api.DB.QueryBuilder(&testItems, allParams) | ||||||
|  | 
 | ||||||
|  |     // fmt.Printf("\n\nItems: %+v", testItems) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     // Pull out UUIDs | ||||||
|  |     reqInfo := r.Context().Value("uuids").(map[string]string) | ||||||
|  |     uid := reqInfo["uid"] | ||||||
|  |     userUUID, _ := uuid.Parse(uid) | ||||||
|  | 
 | ||||||
|  |     // TODO: Can apply multiple filters based on query parameters | ||||||
|  |     mediaItemFilter := &models.MediaItem{UserUUID: userUUID} | ||||||
|  |     mediaItemFilter.UserUUID = userUUID | ||||||
|  | 
 | ||||||
|  |     mediaItems, count, _ := api.DB.MediaItems(mediaItemFilter) | ||||||
|  |     response := &models.APIResponse{ | ||||||
|  |         Data: &mediaItems, | ||||||
|  |         Meta: &models.APIMeta{Count: count}, | ||||||
|  |     } | ||||||
|  |     responseJSON(w, &response, http.StatusOK) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (api *API) mediaItemPOSTHandler(w http.ResponseWriter, r *http.Request) { | func (api *API) mediaItemPOSTHandler(w http.ResponseWriter, r *http.Request) { | ||||||
|     // 64MB limit (TODO: Change this) |     // 64MB limit (TODO: Change this - video) | ||||||
|     r.ParseMultipartForm(64 << 20) |     r.ParseMultipartForm(64 << 20) | ||||||
| 
 | 
 | ||||||
|     // Open form file |     // Open form file | ||||||
| @ -116,13 +195,13 @@ func (api *API) mediaItemPOSTHandler(w http.ResponseWriter, r *http.Request) { | |||||||
| 
 | 
 | ||||||
|     // Add Additional MediaItem Fields |     // Add Additional MediaItem Fields | ||||||
|     mediaItem.Base.UUID = mediaItemUUID |     mediaItem.Base.UUID = mediaItemUUID | ||||||
|     mediaItem.User.UUID, err = uuid.Parse(uid) |     mediaItem.UserUUID, err = uuid.Parse(uid) | ||||||
|     mediaItem.MediaType = mediaType |     mediaItem.MediaType = mediaType | ||||||
|     mediaItem.FileName = fileName |     mediaItem.FileName = fileName | ||||||
|     mediaItem.OrigName = multipartFileHeader.Filename |     mediaItem.OrigName = multipartFileHeader.Filename | ||||||
| 
 | 
 | ||||||
|     // Create MediaItem in DB |     // Create MediaItem in DB | ||||||
|     _, err = api.DB.CreateMediaItem(mediaItem) |     err = api.DB.CreateMediaItem(mediaItem) | ||||||
|     if err != nil { |     if err != nil { | ||||||
|         errorJSON(w, "Upload failed.", http.StatusInternalServerError) |         errorJSON(w, "Upload failed.", http.StatusInternalServerError) | ||||||
|         return |         return | ||||||
| @ -131,14 +210,14 @@ func (api *API) mediaItemPOSTHandler(w http.ResponseWriter, r *http.Request) { | |||||||
|     successJSON(w, "Upload succeeded.", http.StatusCreated) |     successJSON(w, "Upload succeeded.", http.StatusCreated) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func mediaItemFromEXIFData(filePath string) (models.MediaItem, error) { | func mediaItemFromEXIFData(filePath string) (*models.MediaItem, error) { | ||||||
|     rawExif, err := exif.SearchFileAndExtractExif(filePath) |     rawExif, err := exif.SearchFileAndExtractExif(filePath) | ||||||
|     entries, _, err := exif.GetFlatExifData(rawExif, nil) |     entries, _, err := exif.GetFlatExifData(rawExif, nil) | ||||||
| 
 | 
 | ||||||
|     decLong := float32(1) |     decLong := float32(1) | ||||||
|     decLat := float32(1) |     decLat := float32(1) | ||||||
| 
 | 
 | ||||||
|     var mediaItem models.MediaItem |     mediaItem := &models.MediaItem{} | ||||||
|     for _, v := range entries { |     for _, v := range entries { | ||||||
|         if v.TagName == "DateTimeOriginal" { |         if v.TagName == "DateTimeOriginal" { | ||||||
|             formattedTime, _ := time.Parse("2006:01:02 15:04:05", v.Formatted) |             formattedTime, _ := time.Parse("2006:01:02 15:04:05", v.Formatted) | ||||||
| @ -164,8 +243,8 @@ func mediaItemFromEXIFData(filePath string) (models.MediaItem, error) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     mediaItem.Latitude = decLat |     mediaItem.Latitude = &decLat | ||||||
|     mediaItem.Longitude = decLong |     mediaItem.Longitude = &decLong | ||||||
| 
 | 
 | ||||||
|     return mediaItem, err |     return mediaItem, err | ||||||
| } | } | ||||||
| @ -173,3 +252,76 @@ func mediaItemFromEXIFData(filePath string) (models.MediaItem, error) { | |||||||
| func deriveDecimalCoordinate(degrees, minutes uint32, seconds float32) float32 { | func deriveDecimalCoordinate(degrees, minutes uint32, seconds float32) float32 { | ||||||
|     return float32(degrees) + (float32(minutes) / 60) + (seconds / 3600) |     return float32(degrees) + (float32(minutes) / 60) + (seconds / 3600) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // { | ||||||
|  | //  filters: [ | ||||||
|  | //      { field: "", operator: ""}, | ||||||
|  | //      { field: "", operator: ""}, | ||||||
|  | //      { field: "", operator: ""}, | ||||||
|  | //  ], | ||||||
|  | //  sort: "" | ||||||
|  | //  page: {} | ||||||
|  | // } | ||||||
|  | func normalizeForm(form url.Values, typeObj interface{}) error { | ||||||
|  |     allowedFields := models.JSONFields(typeObj) | ||||||
|  | 
 | ||||||
|  |     for key, val := range form { | ||||||
|  |         key = strings.ToLower(key) | ||||||
|  | 
 | ||||||
|  |         re := regexp.MustCompile(`^(filter|page)\[(\w*)]($|>|<)$`) | ||||||
|  |         matches := re.FindStringSubmatch(key) | ||||||
|  | 
 | ||||||
|  |         if len(matches) == 4 { | ||||||
|  |             cmd := strings.ToLower(matches[1]) | ||||||
|  |             field := strings.ToLower(matches[2]) | ||||||
|  |             operator := strings.ToLower(matches[3]) | ||||||
|  | 
 | ||||||
|  |             if cmd == "page" && field == "limit" { | ||||||
|  |                 fmt.Printf("cmd: %s field: %s op: %s\n", cmd, field, operator) | ||||||
|  |                 continue | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Validate field | ||||||
|  |             _, ok := allowedFields[field] | ||||||
|  |             if !ok { | ||||||
|  |                 return errors.New("Invalid field.") | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Val assertions | ||||||
|  |             tempObj := make(map[string]string) | ||||||
|  |             tempObj[field] = val[0] | ||||||
|  | 
 | ||||||
|  |             mi, err := json.Marshal(tempObj) | ||||||
|  |             if err != nil { | ||||||
|  |                 // Handle error | ||||||
|  |                 fmt.Printf("1 Type Assertion Failed For Field: [%s] with value: [%s]\n", field, val) | ||||||
|  |             } | ||||||
|  |             fmt.Printf("String JSON: %s", string(mi)) | ||||||
|  |             refObj := &models.MediaItem{} | ||||||
|  |             if err = json.Unmarshal(mi, refObj); err != nil { | ||||||
|  |                 // Handle error | ||||||
|  |                 fmt.Printf("2 Type Assertion Failed For Field: [%s] with value: [%s]\n", field, val[0]) | ||||||
|  |                 fmt.Println(err) | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             fmt.Printf("Result: %+v\n", refObj) | ||||||
|  | 
 | ||||||
|  |             fmt.Printf("cmd: %s field: %s op: %s\n", cmd, field, operator) | ||||||
|  |         } else if key == "sort" { | ||||||
|  |             field := strings.ToLower(val[0]) | ||||||
|  | 
 | ||||||
|  |             // Validate field | ||||||
|  |             _, ok := allowedFields[field] | ||||||
|  |             if !ok { | ||||||
|  |                 return errors.New("Invalid field.") | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // TODO: Validate val | ||||||
|  | 
 | ||||||
|  |             fmt.Printf("cmd: %s\n", key) | ||||||
|  |         } else { | ||||||
|  |             return errors.New("Invalid parameter(s)") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return nil | ||||||
|  | } | ||||||
|  | |||||||
| @ -22,18 +22,33 @@ func multipleMiddleware(h http.HandlerFunc, m ...Middleware) http.HandlerFunc { | |||||||
| 
 | 
 | ||||||
| func (api *API) authMiddleware(next http.Handler) http.HandlerFunc { | func (api *API) authMiddleware(next http.Handler) http.HandlerFunc { | ||||||
|     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |     return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 
 | ||||||
|         // Acquire Token |         // Acquire Token | ||||||
|         accessCookie, err := r.Cookie("AccessToken") |         accessCookie, err := r.Cookie("AccessToken") | ||||||
|         if err != nil { |         if err != nil { | ||||||
|             log.Warn("[middleware] AccessToken not found") |             log.Warn("[middleware] AccessToken not found") | ||||||
|             w.WriteHeader(http.StatusUnauthorized) |             errorJSON(w, "Invalid token.", http.StatusUnauthorized) | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Validate JWT Tokens |         // Validate JWT Tokens | ||||||
|         accessToken, accessOK := api.Auth.ValidateJWTAccessToken(accessCookie.Value) |         accessToken, err := api.Auth.ValidateJWTAccessToken(accessCookie.Value) | ||||||
|  | 
 | ||||||
|  |         if err != nil && err.Error() == "exp not satisfied" { | ||||||
|  |             log.Info("[middleware] Refreshing AccessToken") | ||||||
|  |             accessToken, err = api.refreshAccessToken(w, r) | ||||||
|  |             if err != nil { | ||||||
|  |                 log.Warn("[middleware] Refreshing AccessToken failed: ", err) | ||||||
|  |                 errorJSON(w, "Invalid token.", http.StatusUnauthorized) | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|  |             log.Info("[middleware] AccessToken Refreshed") | ||||||
|  |         } else if err != nil { | ||||||
|  |             log.Warn("[middleware] AccessToken failed to validate") | ||||||
|  |             errorJSON(w, "Invalid token.", http.StatusUnauthorized) | ||||||
|  |             return | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         if accessOK { |  | ||||||
|         // Acquire UserID and DeviceID |         // Acquire UserID and DeviceID | ||||||
|         reqInfo := make(map[string]string) |         reqInfo := make(map[string]string) | ||||||
|         uid, _ := accessToken.Get("sub") |         uid, _ := accessToken.Get("sub") | ||||||
| @ -46,9 +61,6 @@ func (api *API) authMiddleware(next http.Handler) http.HandlerFunc { | |||||||
|         sr := r.WithContext(ctx) |         sr := r.WithContext(ctx) | ||||||
| 
 | 
 | ||||||
|         next.ServeHTTP(w, sr) |         next.ServeHTTP(w, sr) | ||||||
|         } else { |  | ||||||
|             w.WriteHeader(http.StatusUnauthorized) |  | ||||||
|         } |  | ||||||
|     }) |     }) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -3,6 +3,8 @@ package api | |||||||
| import ( | import ( | ||||||
|     "encoding/json" |     "encoding/json" | ||||||
|     "net/http" |     "net/http" | ||||||
|  | 
 | ||||||
|  |     "reichard.io/imagini/internal/models" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (api *API) registerRoutes() { | func (api *API) registerRoutes() { | ||||||
| @ -45,17 +47,21 @@ func (api *API) registerRoutes() { | |||||||
| 
 | 
 | ||||||
|     api.Router.HandleFunc("/api/v1/Logout", api.logoutHandler) |     api.Router.HandleFunc("/api/v1/Logout", api.logoutHandler) | ||||||
|     api.Router.HandleFunc("/api/v1/Login",  api.loginHandler) |     api.Router.HandleFunc("/api/v1/Login",  api.loginHandler) | ||||||
|     api.Router.HandleFunc("/api/v1/RefreshLogin",  api.refreshLoginHandler) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| // https://stackoverflow.com/a/59764037 | // https://stackoverflow.com/a/59764037 | ||||||
| func errorJSON(w http.ResponseWriter, err string, code int) { | func errorJSON(w http.ResponseWriter, err string, code int) { | ||||||
|  |     errStruct := &models.APIResponse{Error: &models.APIError{Message: err, Code: int64(code)}} | ||||||
|  |     responseJSON(w, errStruct, code) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func responseJSON(w http.ResponseWriter, msg interface{}, code int) { | ||||||
|     w.Header().Set("Content-Type", "application/json; charset=utf-8") |     w.Header().Set("Content-Type", "application/json; charset=utf-8") | ||||||
|     w.Header().Set("X-Content-Type-Options", "nosniff") |     w.Header().Set("X-Content-Type-Options", "nosniff") | ||||||
|     w.WriteHeader(code) |     w.WriteHeader(code) | ||||||
|     json.NewEncoder(w).Encode(map[string]interface{}{"error": err}) |     json.NewEncoder(w).Encode(msg) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func successJSON(w http.ResponseWriter, msg string, code int) { | func successJSON(w http.ResponseWriter, msg string, code int) { | ||||||
|  | |||||||
| @ -35,9 +35,9 @@ func NewMgr(db *db.DBManager, c *config.Config) *AuthManager { | |||||||
| 
 | 
 | ||||||
| func (auth *AuthManager) AuthenticateUser(creds models.APICredentials) (bool, models.User) { | func (auth *AuthManager) AuthenticateUser(creds models.APICredentials) (bool, models.User) { | ||||||
|     // By Username |     // By Username | ||||||
|     foundUser, err := auth.DB.User(models.User{Username: creds.User}) |     foundUser, err := auth.DB.User(&models.User{Username: creds.User}) | ||||||
|     if errors.Is(err, gorm.ErrRecordNotFound) { |     if errors.Is(err, gorm.ErrRecordNotFound) { | ||||||
|         foundUser, err = auth.DB.User(models.User{Email: creds.User}) |         foundUser, err = auth.DB.User(&models.User{Email: creds.User}) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Error Checking |     // Error Checking | ||||||
| @ -67,22 +67,22 @@ func (auth *AuthManager) getRole(user models.User) string { | |||||||
|     return "User" |     return "User" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (auth *AuthManager) ValidateJWTRefreshToken(refreshJWT string) (jwt.Token, bool) { | func (auth *AuthManager) ValidateJWTRefreshToken(refreshJWT string) (jwt.Token, error) { | ||||||
|     byteRefreshJWT := []byte(refreshJWT) |     byteRefreshJWT := []byte(refreshJWT) | ||||||
| 
 | 
 | ||||||
|     // Acquire Relevant Device |     // Acquire Relevant Device | ||||||
|     unverifiedToken, err := jwt.ParseBytes(byteRefreshJWT) |     unverifiedToken, err := jwt.ParseBytes(byteRefreshJWT) | ||||||
|     did, ok := unverifiedToken.Get("did") |     did, ok := unverifiedToken.Get("did") | ||||||
|     if !ok { |     if !ok { | ||||||
|         return nil, false |         return nil, errors.New("did does not exist") | ||||||
|     } |     } | ||||||
|     deviceID, err := uuid.Parse(fmt.Sprintf("%v", did)) |     deviceID, err := uuid.Parse(fmt.Sprintf("%v", did)) | ||||||
|     if err != nil { |     if err != nil { | ||||||
|         return nil, false |         return nil, errors.New("did does not parse") | ||||||
|     } |     } | ||||||
|     device, err := auth.DB.Device(models.Device{Base: models.Base{UUID: deviceID}}) |     device, err := auth.DB.Device(&models.Device{Base: models.Base{UUID: deviceID}}) | ||||||
|     if err != nil { |     if err != nil { | ||||||
|         return nil, false |         return nil, err | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Verify & Validate Token |     // Verify & Validate Token | ||||||
| @ -92,22 +92,23 @@ func (auth *AuthManager) ValidateJWTRefreshToken(refreshJWT string) (jwt.Token, | |||||||
|     ) |     ) | ||||||
|     if err != nil { |     if err != nil { | ||||||
|         fmt.Println("failed to parse payload: ", err) |         fmt.Println("failed to parse payload: ", err) | ||||||
|         return nil, false |         return nil, err | ||||||
|     } |     } | ||||||
|     return verifiedToken, true |     return verifiedToken, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (auth *AuthManager) ValidateJWTAccessToken(accessJWT string) (jwt.Token, bool) { | func (auth *AuthManager) ValidateJWTAccessToken(accessJWT string) (jwt.Token, error) { | ||||||
|     byteAccessJWT := []byte(accessJWT) |     byteAccessJWT := []byte(accessJWT) | ||||||
|     verifiedToken, err := jwt.ParseBytes(byteAccessJWT, |     verifiedToken, err := jwt.ParseBytes(byteAccessJWT, | ||||||
|         jwt.WithValidate(true), |         jwt.WithValidate(true), | ||||||
|         jwt.WithVerify(jwa.HS256, []byte(auth.Config.JWTSecret)), |         jwt.WithVerify(jwa.HS256, []byte(auth.Config.JWTSecret)), | ||||||
|     ) |     ) | ||||||
|  | 
 | ||||||
|     if err != nil { |     if err != nil { | ||||||
|         fmt.Println("failed to parse payload: ", err) |         return nil, err | ||||||
|         return nil, false |  | ||||||
|     } |     } | ||||||
|     return verifiedToken, true | 
 | ||||||
|  |     return verifiedToken, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (auth *AuthManager) CreateJWTRefreshToken(user models.User, device models.Device) (string, error) { | func (auth *AuthManager) CreateJWTRefreshToken(user models.User, device models.Device) (string, error) { | ||||||
|  | |||||||
| @ -1,7 +1,9 @@ | |||||||
| package db | package db | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  |     "fmt" | ||||||
|     "path" |     "path" | ||||||
|  |     "errors" | ||||||
| 
 | 
 | ||||||
|     "gorm.io/gorm" |     "gorm.io/gorm" | ||||||
|     // "gorm.io/gorm/logger" |     // "gorm.io/gorm/logger" | ||||||
| @ -52,7 +54,7 @@ func NewMgr(c *config.Config) *DBManager { | |||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) bootstrapDatabase() { | func (dbm *DBManager) bootstrapDatabase() { | ||||||
|     log.Info("[query] Bootstrapping database.") |     log.Info("[query] Bootstrapping database.") | ||||||
|     _, err := dbm.CreateUser(models.User{ |     err := dbm.CreateUser(&models.User{ | ||||||
|         Username: "admin", |         Username: "admin", | ||||||
|         Password: "admin", |         Password: "admin", | ||||||
|         AuthType: "Local", |         AuthType: "Local", | ||||||
| @ -62,3 +64,46 @@ func (dbm *DBManager) bootstrapDatabase() { | |||||||
|         log.Fatal("[query] Unable to bootstrap database.") |         log.Fatal("[query] Unable to bootstrap database.") | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func (dbm *DBManager) QueryBuilder(dest interface{}, params []byte) (int64, error) { | ||||||
|  |     // TODO: | ||||||
|  |     //  - Where Filters | ||||||
|  |     //  - Sort Filters | ||||||
|  |     //  - Paging Filters | ||||||
|  | 
 | ||||||
|  |     objType := fmt.Sprintf("%T", dest) | ||||||
|  |     if objType == "*[]models.MediaItem" { | ||||||
|  |         // TODO: Validate MediaItem Type | ||||||
|  |     } else { | ||||||
|  |         // Return Error | ||||||
|  |         return 0, errors.New("Invalid type") | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     var count int64 | ||||||
|  |     err := dbm.db.Find(dest).Count(&count).Error; | ||||||
|  |     return count, err | ||||||
|  | 
 | ||||||
|  |     // Paging: | ||||||
|  |     //  - Regular Pagination: | ||||||
|  |     //      - /api/v1/MediaItems?page[limit]=50&page=2 | ||||||
|  |     //  - Meta Count Only | ||||||
|  |     //      - /api/v1/MediaItems?page[limit]=0 | ||||||
|  | 
 | ||||||
|  |     // Sorting: | ||||||
|  |     //  - Ascending Sort: | ||||||
|  |     //      - /api/v1/MediaItems?sort=created_at | ||||||
|  |     //  - Descending Sort: | ||||||
|  |     //      - /api/v1/MediaItems?sort=-created_at | ||||||
|  | 
 | ||||||
|  |     // Filters: | ||||||
|  |     //  - Greater Than / Less Than (created_at, updated_at, exif_date) | ||||||
|  |     //      - /api/v1/MediaItems?filter[created_at]>=2020-01-01&filter[created_at]<=2021-01-01 | ||||||
|  |     //  - Long / Lat Range (latitude, longitude) | ||||||
|  |     //      - /api/v1/MediaItems?filter[latitude]>=71.1827&filter[latitude]<=72.0000&filter[longitude]>=100.000&filter[longitude]<=101.0000 | ||||||
|  |     //  - Image / Video (media_type) | ||||||
|  |     //      - /api/v1/MediaItems?filter[media_type]=Image | ||||||
|  |     //  - Tags (tags) | ||||||
|  |     //      - /api/v1/MediaItems?filter[tags]=id1,id2,id3 | ||||||
|  |     //  - Albums (albums) | ||||||
|  |     //      - /api/v1/MediaItems?filter[albums]=id1 | ||||||
|  | } | ||||||
|  | |||||||
| @ -7,24 +7,24 @@ import ( | |||||||
|     "reichard.io/imagini/internal/models" |     "reichard.io/imagini/internal/models" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) CreateDevice (device models.Device) (models.Device, error) { | func (dbm *DBManager) CreateDevice (device *models.Device) error { | ||||||
|     log.Info("[db] Creating device: ", device.Name) |     log.Info("[db] Creating device: ", device.Name) | ||||||
|     device.RefreshKey = uuid.New().String() |     device.RefreshKey = uuid.New().String() | ||||||
|     err := dbm.db.Create(&device).Error |     err := dbm.db.Create(&device).Error | ||||||
|     return device, err |     return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) Device (device models.Device) (models.Device, error) { | func (dbm *DBManager) Device (device *models.Device) (models.Device, error) { | ||||||
|     var foundDevice models.Device |     var foundDevice models.Device | ||||||
|     var count int64 |     var count int64 | ||||||
|     err := dbm.db.Where(&device).First(&foundDevice).Count(&count).Error |     err := dbm.db.Where(&device).First(&foundDevice).Count(&count).Error | ||||||
|     return foundDevice, err |     return foundDevice, err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) DeleteDevice (user models.Device) error { | func (dbm *DBManager) DeleteDevice (user *models.Device) error { | ||||||
|     return nil |     return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) UpdateRefreshToken (device models.Device, refreshToken string) error { | func (dbm *DBManager) UpdateRefreshToken (device *models.Device, refreshToken string) error { | ||||||
|     return nil |     return nil | ||||||
| } | } | ||||||
|  | |||||||
| @ -6,30 +6,16 @@ import ( | |||||||
|     "reichard.io/imagini/internal/models" |     "reichard.io/imagini/internal/models" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) CreateMediaItem (mediaItem models.MediaItem) (models.MediaItem, error) { | func (dbm *DBManager) CreateMediaItem (mediaItem *models.MediaItem) error { | ||||||
|     log.Info("[db] Creating media item: ", mediaItem.FileName) |     log.Info("[db] Creating media item: ", mediaItem.FileName) | ||||||
|     err := dbm.db.Create(&mediaItem).Error |     err := dbm.db.Create(&mediaItem).Error | ||||||
|     return mediaItem, err |     return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) MediaItemsFromAlbum(user models.User, album models.Album) ([]models.MediaItem, error) { | func (dbm *DBManager) MediaItems(mediaItemFilter *models.MediaItem) ([]models.MediaItem, int64, error) { | ||||||
|     var mediaItems []models.MediaItem |     var mediaItems []models.MediaItem | ||||||
|     // db.Table("media_albums"). |     var count int64 | ||||||
|     //     Select("media_item.*"). |  | ||||||
|     //     Joins("INNER JOIN media_items ON media_albums.ID = media_items.Albums"). |  | ||||||
|     //     Where("media_albums.album_id = ? AND media_items.User = ?", albumID, userID). |  | ||||||
| 
 | 
 | ||||||
| 
 |     err := dbm.db.Where(&mediaItemFilter).Find(&mediaItems).Count(&count).Error; | ||||||
|     err := dbm.db. |     return mediaItems, count, err | ||||||
|         //Where("album = ? AND user = ?", albumID, userID). |  | ||||||
|         Find(&mediaItems).Error |  | ||||||
|     return mediaItems, err |  | ||||||
| 
 |  | ||||||
|     // db.Raw(` |  | ||||||
|     //     SELECT |  | ||||||
|     //         MediaItems.* |  | ||||||
|     //     FROM |  | ||||||
|     //         MediaAlbums |  | ||||||
|     //     INNER JOIN MediaItems ON MediaAlbums.mediaID = MediaItems.mediaID |  | ||||||
|     //     WHERE MediaAlbums.albumID = ? AND MediaItems.userID = ?`, albumID, userID) |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,19 +7,19 @@ import ( | |||||||
|     "reichard.io/imagini/internal/models" |     "reichard.io/imagini/internal/models" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) CreateUser(user models.User) (models.User, error) { | func (dbm *DBManager) CreateUser(user *models.User) error { | ||||||
|     log.Info("[db] Creating user: ", user.Username) |     log.Info("[db] Creating user: ", user.Username) | ||||||
|     hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost) |     hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost) | ||||||
|     if err != nil { |     if err != nil { | ||||||
|         log.Error(err) |         log.Error(err) | ||||||
|         return user, err |         return err | ||||||
|     } |     } | ||||||
|     user.Password = string(hashedPassword) |     user.Password = string(hashedPassword) | ||||||
|     err = dbm.db.Create(&user).Error |     err = dbm.db.Create(&user).Error | ||||||
|     return user, err |     return err | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (dbm *DBManager) User (user models.User) (models.User, error) { | func (dbm *DBManager) User (user *models.User) (models.User, error) { | ||||||
|     var foundUser models.User |     var foundUser models.User | ||||||
|     var count int64 |     var count int64 | ||||||
|     err := dbm.db.Where(&user).First(&foundUser).Count(&count).Error |     err := dbm.db.Where(&user).First(&foundUser).Count(&count).Error | ||||||
|  | |||||||
| @ -5,18 +5,20 @@ type APICredentials struct { | |||||||
|     Password    string  `json:"password"` |     Password    string  `json:"password"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | type APIData interface{} | ||||||
|  | 
 | ||||||
| type APIMeta struct { | type APIMeta struct { | ||||||
|     Count   int     `json:"count"` |     Count   int64   `json:"count"` | ||||||
|     Page    int     `json:"page"` |     Page    int64   `json:"page"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type APIError struct { | type APIError struct { | ||||||
|     Message string  `json:"message"` |     Message string  `json:"message"` | ||||||
|     Code    int     `json:"code"` |     Code    int64   `json:"code"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type APIResponse struct { | type APIResponse struct { | ||||||
|     Data    []interface{}   `json:"data"` |     Data    APIData     `json:"data,omitempty"` | ||||||
|     Meta    APIMeta         `json:"meta"` |     Meta    *APIMeta     `json:"meta,omitempty"` | ||||||
|     Error   APIError        `json:"error"` |     Error   *APIError    `json:"error,omitempty"` | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,16 +2,17 @@ package models | |||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|     "time" |     "time" | ||||||
|  |     "strings" | ||||||
|  |     "reflect" | ||||||
|     "gorm.io/gorm" |     "gorm.io/gorm" | ||||||
|     "github.com/google/uuid" |     "github.com/google/uuid" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Base contains common columns for all tables. |  | ||||||
| type Base struct { | type Base struct { | ||||||
|     UUID      uuid.UUID         `json:"uuid"        gorm:"type:uuid;primarykey"` |     UUID      uuid.UUID         `json:"uuid"        gorm:"type:uuid;primarykey"` | ||||||
|     CreatedAt time.Time         `json:"created_at"` |     CreatedAt time.Time         `json:"created_at"` | ||||||
|     UpdatedAt time.Time         `json:"updated_at"` |     UpdatedAt time.Time         `json:"updated_at"` | ||||||
|     DeletedAt gorm.DeletedAt    `json:"deleted_at"  gorm:"index"` |     DeletedAt gorm.DeletedAt    `json:"-"           gorm:"index"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (base *Base) BeforeCreate(tx *gorm.DB) (err error) { | func (base *Base) BeforeCreate(tx *gorm.DB) (err error) { | ||||||
| @ -28,7 +29,8 @@ type ServerSetting struct { | |||||||
| 
 | 
 | ||||||
| type Device struct { | type Device struct { | ||||||
|     Base |     Base | ||||||
|     User         User    `json:"user"   gorm:"ForeignKey:UUID;not null"` // User UUID |     UserUUID     uuid.UUID `json:"-"      gorm:"not null"` | ||||||
|  |     User         User      `json:"user"   gorm:"ForeignKey:UUID;References:UserUUID;not null"` // User | ||||||
|     Name         string    `json:"name"   gorm:"not null"`                                     // Name of Device |     Name         string    `json:"name"   gorm:"not null"`                                     // Name of Device | ||||||
|     Type         string    `json:"type"   gorm:"not null"`                                     // Android, iOS, Chrome, FireFox, Edge |     Type         string    `json:"type"   gorm:"not null"`                                     // Android, iOS, Chrome, FireFox, Edge | ||||||
|     RefreshKey   string    `json:"-"`                                                          // Device Specific Refresh Key |     RefreshKey   string    `json:"-"`                                                          // Device Specific Refresh Key | ||||||
| @ -47,10 +49,11 @@ type User struct { | |||||||
| 
 | 
 | ||||||
| type MediaItem struct { | type MediaItem struct { | ||||||
|     Base |     Base | ||||||
|     User        User        `json:"user"        gorm:"ForeignKey:UUID;not null"` // User UUID |     UserUUID    uuid.UUID   `json:"-"           gorm:"not null"` | ||||||
|  |     User        User        `json:"-"           gorm:"ForeignKey:UUID;References:UserUUID;not null"` // User | ||||||
|     EXIFDate    time.Time   `json:"exif_date"`                                                       // EXIF Date |     EXIFDate    time.Time   `json:"exif_date"`                                                       // EXIF Date | ||||||
|     Latitude    float32     `json:"latitude"    gorm:"type:decimal(10,2)"`       // Decimal Latitude |     Latitude    *float32    `json:"latitude"    gorm:"type:decimal(10,2)"`                           // Decimal Latitude | ||||||
|     Longitude   float32     `json:"longitude"   gorm:"type:decimal(10,2)"`       // Decimal Longitude |     Longitude   *float32    `json:"longitude"   gorm:"type:decimal(10,2)"`                           // Decimal Longitude | ||||||
|     MediaType   string      `json:"media_type"  gorm:"default:Image;not null"`                       // Image, Video |     MediaType   string      `json:"media_type"  gorm:"default:Image;not null"`                       // Image, Video | ||||||
|     OrigName    string      `json:"orig_name"   gorm:"not null"`                                     // Original Name |     OrigName    string      `json:"orig_name"   gorm:"not null"`                                     // Original Name | ||||||
|     FileName    string      `json:"file_name"   gorm:"not null"`                                     // File Name |     FileName    string      `json:"file_name"   gorm:"not null"`                                     // File Name | ||||||
| @ -67,3 +70,27 @@ type Album struct { | |||||||
|     Base |     Base | ||||||
|     Name        string  `json:"name"    gorm:"not null"` // Album Name |     Name        string  `json:"name"    gorm:"not null"` // Album Name | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func JSONFields(model interface{}) map[string]struct{} { | ||||||
|  |     jsonFields := make(map[string]struct{}) | ||||||
|  |     val := reflect.ValueOf(model) | ||||||
|  |     t := val.Type() | ||||||
|  |     for i := 0; i < t.NumField(); i++ { | ||||||
|  |         jsonField := strings.TrimSpace(t.Field(i).Tag.Get("json")) | ||||||
|  | 
 | ||||||
|  |         if jsonField == "" { | ||||||
|  |             continue | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         jsonSplit := strings.Split(jsonField, ",") | ||||||
|  |         fieldVal := strings.TrimSpace(jsonSplit[0]) | ||||||
|  | 
 | ||||||
|  |         if fieldVal == "" || fieldVal == "-" { | ||||||
|  |             continue | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         jsonFields[fieldVal] = struct{}{} | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return jsonFields | ||||||
|  | } | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user