#1 feature/initial-project

Merged
tovi merged 24 commits from feature/initial-project into develop 2 years ago
  1. +1
    -0
      .gitignore
  2. +9
    -0
      Backend/Api/Auth/Check.go
  3. +108
    -0
      Backend/Api/Auth/Login.go
  4. +40
    -0
      Backend/Api/Auth/Logout.go
  5. +22
    -0
      Backend/Api/Auth/Passwords.go
  6. +54
    -0
      Backend/Api/Auth/Session.go
  7. +95
    -0
      Backend/Api/Auth/Signup.go
  8. +70
    -0
      Backend/Api/Friends/AcceptFriendRequest.go
  9. +41
    -0
      Backend/Api/Friends/EncryptedFriendsList.go
  10. +60
    -0
      Backend/Api/Friends/FriendRequest.go
  11. +87
    -0
      Backend/Api/Friends/Friends.go
  12. +42
    -0
      Backend/Api/Friends/RejectFriendRequest.go
  13. +76
    -0
      Backend/Api/JsonSerialization/DeserializeUserJson.go
  14. +109
    -0
      Backend/Api/JsonSerialization/VerifyJson.go
  15. +84
    -0
      Backend/Api/Messages/Conversations.go
  16. +58
    -0
      Backend/Api/Messages/CreateConversation.go
  17. +41
    -0
      Backend/Api/Messages/CreateMessage.go
  18. +45
    -0
      Backend/Api/Messages/MessageThread.go
  19. +56
    -0
      Backend/Api/Messages/UpdateConversation.go
  20. +79
    -0
      Backend/Api/Routes.go
  21. +56
    -0
      Backend/Api/Users/SearchUsers.go
  22. +41
    -0
      Backend/Database/ConversationDetailUsers.go
  23. +55
    -0
      Backend/Database/ConversationDetails.go
  24. +63
    -0
      Backend/Database/FriendRequests.go
  25. +69
    -0
      Backend/Database/Init.go
  26. +39
    -0
      Backend/Database/MessageData.go
  27. +60
    -0
      Backend/Database/Messages.go
  28. +113
    -0
      Backend/Database/Seeder/FriendSeeder.go
  29. +313
    -0
      Backend/Database/Seeder/MessageSeeder.go
  30. +97
    -0
      Backend/Database/Seeder/Seed.go
  31. +68
    -0
      Backend/Database/Seeder/UserSeeder.go
  32. +188
    -0
      Backend/Database/Seeder/encryption.go
  33. +38
    -0
      Backend/Database/Sessions.go
  34. +91
    -0
      Backend/Database/UserConversations.go
  35. +95
    -0
      Backend/Database/Users.go
  36. +31
    -0
      Backend/Models/Base.go
  37. +35
    -0
      Backend/Models/Conversations.go
  38. +19
    -0
      Backend/Models/Friends.go
  39. +24
    -0
      Backend/Models/Messages.go
  40. +18
    -0
      Backend/Models/Sessions.go
  41. +23
    -0
      Backend/Models/Users.go
  42. +21
    -0
      Backend/Util/Bytes.go
  43. +21
    -0
      Backend/Util/Strings.go
  44. +51
    -0
      Backend/Util/UserHelper.go
  45. +26
    -0
      Backend/go.mod
  46. +192
    -0
      Backend/go.sum
  47. +45
    -0
      Backend/main.go
  48. +16
    -1
      README.md
  49. +46
    -0
      mobile/.gitignore
  50. +10
    -0
      mobile/.metadata
  51. +16
    -0
      mobile/README.md
  52. +30
    -0
      mobile/analysis_options.yaml
  53. +13
    -0
      mobile/android/.gitignore
  54. +68
    -0
      mobile/android/app/build.gradle
  55. +7
    -0
      mobile/android/app/src/debug/AndroidManifest.xml
  56. +35
    -0
      mobile/android/app/src/main/AndroidManifest.xml
  57. +6
    -0
      mobile/android/app/src/main/kotlin/com/example/mobile/MainActivity.kt
  58. +12
    -0
      mobile/android/app/src/main/res/drawable-v21/launch_background.xml
  59. +12
    -0
      mobile/android/app/src/main/res/drawable/launch_background.xml
  60. BIN
      mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
  61. BIN
      mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
  62. BIN
      mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
  63. BIN
      mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  64. BIN
      mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  65. +18
    -0
      mobile/android/app/src/main/res/values-night/styles.xml
  66. +18
    -0
      mobile/android/app/src/main/res/values/styles.xml
  67. +7
    -0
      mobile/android/app/src/profile/AndroidManifest.xml
  68. +31
    -0
      mobile/android/build.gradle
  69. +3
    -0
      mobile/android/gradle.properties
  70. +6
    -0
      mobile/android/gradle/wrapper/gradle-wrapper.properties
  71. +11
    -0
      mobile/android/settings.gradle
  72. +34
    -0
      mobile/ios/.gitignore
  73. +26
    -0
      mobile/ios/Flutter/AppFrameworkInfo.plist
  74. +1
    -0
      mobile/ios/Flutter/Debug.xcconfig
  75. +1
    -0
      mobile/ios/Flutter/Release.xcconfig
  76. +481
    -0
      mobile/ios/Runner.xcodeproj/project.pbxproj
  77. +7
    -0
      mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  78. +8
    -0
      mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  79. +8
    -0
      mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  80. +87
    -0
      mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  81. +7
    -0
      mobile/ios/Runner.xcworkspace/contents.xcworkspacedata
  82. +8
    -0
      mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  83. +8
    -0
      mobile/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  84. +13
    -0
      mobile/ios/Runner/AppDelegate.swift
  85. +122
    -0
      mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
  86. BIN
      mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
  87. BIN
      mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
  88. BIN
      mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
  89. BIN
      mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
  90. BIN
      mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
  91. BIN
      mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
  92. BIN
      mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
  93. BIN
      mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
  94. BIN
      mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
  95. BIN
      mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
  96. BIN
      mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
  97. BIN
      mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
  98. BIN
      mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
  99. BIN
      mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
  100. BIN
      mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png

+ 1
- 0
.gitignore View File

@ -0,0 +1 @@
/mobile/.env

+ 9
- 0
Backend/Api/Auth/Check.go View File

@ -0,0 +1,9 @@
package Auth
import (
"net/http"
)
func Check(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}

+ 108
- 0
Backend/Api/Auth/Login.go View File

@ -0,0 +1,108 @@
package Auth
import (
"encoding/json"
"net/http"
"time"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
)
type Credentials struct {
Username string `json:"username"`
Password string `json:"password"`
}
type loginResponse struct {
Status string `json:"status"`
Message string `json:"message"`
AsymmetricPublicKey string `json:"asymmetric_public_key"`
AsymmetricPrivateKey string `json:"asymmetric_private_key"`
UserID string `json:"user_id"`
Username string `json:"username"`
}
func makeLoginResponse(w http.ResponseWriter, code int, message, pubKey, privKey string, user Models.User) {
var (
status string = "error"
returnJson []byte
err error
)
if code > 200 && code < 300 {
status = "success"
}
returnJson, err = json.MarshalIndent(loginResponse{
Status: status,
Message: message,
AsymmetricPublicKey: pubKey,
AsymmetricPrivateKey: privKey,
UserID: user.ID.String(),
Username: user.Username,
}, "", " ")
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
// Return updated json
w.WriteHeader(code)
w.Write(returnJson)
}
func Login(w http.ResponseWriter, r *http.Request) {
var (
creds Credentials
userData Models.User
session Models.Session
expiresAt time.Time
err error
)
err = json.NewDecoder(r.Body).Decode(&creds)
if err != nil {
makeLoginResponse(w, http.StatusInternalServerError, "An error occurred", "", "", userData)
return
}
userData, err = Database.GetUserByUsername(creds.Username)
if err != nil {
makeLoginResponse(w, http.StatusUnauthorized, "An error occurred", "", "", userData)
return
}
if !CheckPasswordHash(creds.Password, userData.Password) {
makeLoginResponse(w, http.StatusUnauthorized, "An error occurred", "", "", userData)
return
}
// TODO: Revisit before production
expiresAt = time.Now().Add(12 * time.Hour)
session = Models.Session{
UserID: userData.ID,
Expiry: expiresAt,
}
err = Database.CreateSession(&session)
if err != nil {
makeLoginResponse(w, http.StatusUnauthorized, "An error occurred", "", "", userData)
return
}
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: session.ID.String(),
Expires: expiresAt,
})
makeLoginResponse(
w,
http.StatusOK,
"Successfully logged in",
userData.AsymmetricPublicKey,
userData.AsymmetricPrivateKey,
userData,
)
}

+ 40
- 0
Backend/Api/Auth/Logout.go View File

@ -0,0 +1,40 @@
package Auth
import (
"log"
"net/http"
"time"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
)
func Logout(w http.ResponseWriter, r *http.Request) {
var (
c *http.Cookie
sessionToken string
err error
)
c, err = r.Cookie("session_token")
if err != nil {
if err == http.ErrNoCookie {
w.WriteHeader(http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusBadRequest)
return
}
sessionToken = c.Value
err = Database.DeleteSessionById(sessionToken)
if err != nil {
log.Println("Could not delete session cookie")
}
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: "",
Expires: time.Now(),
})
}

+ 22
- 0
Backend/Api/Auth/Passwords.go View File

@ -0,0 +1,22 @@
package Auth
import (
"golang.org/x/crypto/bcrypt"
)
func HashPassword(password string) (string, error) {
var (
bytes []byte
err error
)
bytes, err = bcrypt.GenerateFromPassword([]byte(password), 14)
return string(bytes), err
}
func CheckPasswordHash(password, hash string) bool {
var (
err error
)
err = bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}

+ 54
- 0
Backend/Api/Auth/Session.go View File

@ -0,0 +1,54 @@
package Auth
import (
"errors"
"net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
)
func CheckCookie(r *http.Request) (Models.Session, error) {
var (
c *http.Cookie
sessionToken string
userSession Models.Session
err error
)
c, err = r.Cookie("session_token")
if err != nil {
return userSession, err
}
sessionToken = c.Value
// We then get the session from our session map
userSession, err = Database.GetSessionById(sessionToken)
if err != nil {
return userSession, errors.New("Cookie not found")
}
// If the session is present, but has expired, we can delete the session, and return
// an unauthorized status
if userSession.IsExpired() {
Database.DeleteSession(&userSession)
return userSession, errors.New("Cookie expired")
}
return userSession, nil
}
func CheckCookieCurrentUser(w http.ResponseWriter, r *http.Request) (Models.User, error) {
var (
session Models.Session
userData Models.User
err error
)
session, err = CheckCookie(r)
if err != nil {
return userData, err
}
return session.User, nil
}

+ 95
- 0
Backend/Api/Auth/Signup.go View File

@ -0,0 +1,95 @@
package Auth
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/JsonSerialization"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
)
type signupResponse struct {
Status string `json:"status"`
Message string `json:"message"`
}
func makeSignupResponse(w http.ResponseWriter, code int, message string) {
var (
status string = "error"
returnJson []byte
err error
)
if code > 200 && code < 300 {
status = "success"
}
returnJson, err = json.MarshalIndent(signupResponse{
Status: status,
Message: message,
}, "", " ")
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
// Return updated json
w.WriteHeader(code)
w.Write(returnJson)
}
func Signup(w http.ResponseWriter, r *http.Request) {
var (
userData Models.User
requestBody []byte
err error
)
requestBody, err = ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error encountered reading POST body: %s\n", err.Error())
makeSignupResponse(w, http.StatusInternalServerError, "An error occurred")
return
}
userData, err = JsonSerialization.DeserializeUser(requestBody, []string{
"id",
}, false)
if err != nil {
log.Printf("Invalid data provided to Signup: %s\n", err.Error())
makeSignupResponse(w, http.StatusUnprocessableEntity, "Invalid data provided")
return
}
if userData.Username == "" ||
userData.Password == "" ||
userData.ConfirmPassword == "" ||
len(userData.AsymmetricPrivateKey) == 0 ||
len(userData.AsymmetricPublicKey) == 0 {
makeSignupResponse(w, http.StatusUnprocessableEntity, "Invalid data provided")
return
}
err = Database.CheckUniqueUsername(userData.Username)
if err != nil {
makeSignupResponse(w, http.StatusUnprocessableEntity, "Invalid data provided")
return
}
userData.Password, err = HashPassword(userData.Password)
if err != nil {
makeSignupResponse(w, http.StatusInternalServerError, "An error occurred")
return
}
err = Database.CreateUser(&userData)
if err != nil {
makeSignupResponse(w, http.StatusInternalServerError, "An error occurred")
return
}
makeSignupResponse(w, http.StatusCreated, "Successfully signed up")
}

+ 70
- 0
Backend/Api/Friends/AcceptFriendRequest.go View File

@ -0,0 +1,70 @@
package Friends
import (
"encoding/json"
"io/ioutil"
"net/http"
"time"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"github.com/gorilla/mux"
)
// AcceptFriendRequest accepts friend requests
func AcceptFriendRequest(w http.ResponseWriter, r *http.Request) {
var (
oldFriendRequest Models.FriendRequest
newFriendRequest Models.FriendRequest
urlVars map[string]string
friendRequestID string
requestBody []byte
ok bool
err error
)
urlVars = mux.Vars(r)
friendRequestID, ok = urlVars["requestID"]
if !ok {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
oldFriendRequest, err = Database.GetFriendRequestByID(friendRequestID)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
oldFriendRequest.AcceptedAt.Time = time.Now()
oldFriendRequest.AcceptedAt.Valid = true
requestBody, err = ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
err = json.Unmarshal(requestBody, &newFriendRequest)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
err = Database.UpdateFriendRequest(&oldFriendRequest)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
newFriendRequest.AcceptedAt.Time = time.Now()
newFriendRequest.AcceptedAt.Valid = true
err = Database.CreateFriendRequest(&newFriendRequest)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}

+ 41
- 0
Backend/Api/Friends/EncryptedFriendsList.go View File

@ -0,0 +1,41 @@
package Friends
import (
"encoding/json"
"net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Auth"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
)
// EncryptedFriendRequestList gets friend request list
func EncryptedFriendRequestList(w http.ResponseWriter, r *http.Request) {
var (
userSession Models.Session
friends []Models.FriendRequest
returnJSON []byte
err error
)
userSession, err = Auth.CheckCookie(r)
if err != nil {
http.Error(w, "Forbidden", http.StatusUnauthorized)
return
}
friends, err = Database.GetFriendRequestsByUserID(userSession.UserID.String())
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
returnJSON, err = json.MarshalIndent(friends, "", " ")
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write(returnJSON)
}

+ 60
- 0
Backend/Api/Friends/FriendRequest.go View File

@ -0,0 +1,60 @@
package Friends
import (
"encoding/json"
"io/ioutil"
"net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Util"
)
func FriendRequest(w http.ResponseWriter, r *http.Request) {
var (
user Models.User
requestBody []byte
requestJson map[string]interface{}
friendID string
friendRequest Models.FriendRequest
ok bool
err error
)
user, err = Util.GetUserById(w, r)
if err != nil {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
requestBody, err = ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
json.Unmarshal(requestBody, &requestJson)
if requestJson["id"] == nil {
http.Error(w, "Invalid Data", http.StatusBadRequest)
return
}
friendID, ok = requestJson["id"].(string)
if !ok {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
friendRequest = Models.FriendRequest{
UserID: user.ID,
FriendID: friendID,
}
err = Database.CreateFriendRequest(&friendRequest)
if requestJson["id"] == nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}

+ 87
- 0
Backend/Api/Friends/Friends.go View File

@ -0,0 +1,87 @@
package Friends
import (
"encoding/json"
"io/ioutil"
"net/http"
"time"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
)
// CreateFriendRequest creates a FriendRequest from post data
func CreateFriendRequest(w http.ResponseWriter, r *http.Request) {
var (
friendRequest Models.FriendRequest
requestBody []byte
returnJSON []byte
err error
)
requestBody, err = ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
err = json.Unmarshal(requestBody, &friendRequest)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
friendRequest.AcceptedAt.Scan(nil)
err = Database.CreateFriendRequest(&friendRequest)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
returnJSON, err = json.MarshalIndent(friendRequest, "", " ")
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
// Return updated json
w.WriteHeader(http.StatusOK)
w.Write(returnJSON)
}
// CreateFriendRequestQrCode creates a FriendRequest from post data from qr code scan
func CreateFriendRequestQrCode(w http.ResponseWriter, r *http.Request) {
var (
friendRequests []Models.FriendRequest
requestBody []byte
i int
err error
)
requestBody, err = ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
err = json.Unmarshal(requestBody, &friendRequests)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
for i = range friendRequests {
friendRequests[i].AcceptedAt.Time = time.Now()
friendRequests[i].AcceptedAt.Valid = true
}
err = Database.CreateFriendRequests(&friendRequests)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
// Return updated json
w.WriteHeader(http.StatusOK)
}

+ 42
- 0
Backend/Api/Friends/RejectFriendRequest.go View File

@ -0,0 +1,42 @@
package Friends
import (
"net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"github.com/gorilla/mux"
)
// RejectFriendRequest rejects friend requests
func RejectFriendRequest(w http.ResponseWriter, r *http.Request) {
var (
friendRequest Models.FriendRequest
urlVars map[string]string
friendRequestID string
ok bool
err error
)
urlVars = mux.Vars(r)
friendRequestID, ok = urlVars["requestID"]
if !ok {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
friendRequest, err = Database.GetFriendRequestByID(friendRequestID)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
err = Database.DeleteFriendRequest(&friendRequest)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}

+ 76
- 0
Backend/Api/JsonSerialization/DeserializeUserJson.go View File

@ -0,0 +1,76 @@
package JsonSerialization
import (
"encoding/json"
"errors"
"fmt"
"strings"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
schema "github.com/Kangaroux/go-map-schema"
)
func DeserializeUser(data []byte, allowMissing []string, allowAllMissing bool) (Models.User, error) {
var (
userData Models.User = Models.User{}
jsonStructureTest map[string]interface{} = make(map[string]interface{})
jsonStructureTestResults *schema.CompareResults
field schema.FieldMissing
allowed string
missingFields []string
i int
err error
)
// Verify the JSON has the correct structure
json.Unmarshal(data, &jsonStructureTest)
jsonStructureTestResults, err = schema.CompareMapToStruct(
&userData,
jsonStructureTest,
&schema.CompareOpts{
ConvertibleFunc: CanConvert,
TypeNameFunc: schema.DetailedTypeName,
})
if err != nil {
return userData, err
}
if len(jsonStructureTestResults.MismatchedFields) > 0 {
return userData, errors.New(fmt.Sprintf(
"MismatchedFields found when deserializing data: %s",
jsonStructureTestResults.Errors().Error(),
))
}
// Remove allowed missing fields from MissingFields
for _, allowed = range allowMissing {
for i, field = range jsonStructureTestResults.MissingFields {
if allowed == field.String() {
jsonStructureTestResults.MissingFields = append(
jsonStructureTestResults.MissingFields[:i],
jsonStructureTestResults.MissingFields[i+1:]...,
)
}
}
}
if !allowAllMissing && len(jsonStructureTestResults.MissingFields) > 0 {
for _, field = range jsonStructureTestResults.MissingFields {
missingFields = append(missingFields, field.String())
}
return userData, errors.New(fmt.Sprintf(
"MissingFields found when deserializing data: %s",
strings.Join(missingFields, ", "),
))
}
// Deserialize the JSON into the struct
err = json.Unmarshal(data, &userData)
if err != nil {
return userData, err
}
return userData, err
}

+ 109
- 0
Backend/Api/JsonSerialization/VerifyJson.go View File

@ -0,0 +1,109 @@
package JsonSerialization
import (
"math"
"reflect"
)
// isIntegerType returns whether the type is an integer and if it's unsigned.
// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L328
func isIntegerType(t reflect.Type) (bool, bool) {
var (
yes bool
unsigned bool
)
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
yes = true
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
yes = true
unsigned = true
}
return yes, unsigned
}
// isFloatType returns true if the type is a floating point. Note that this doesn't
// care about the value -- unmarshaling the number "0" gives a float, not an int.
// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L319
func isFloatType(t reflect.Type) bool {
var (
yes bool
)
switch t.Kind() {
case reflect.Float32, reflect.Float64:
yes = true
}
return yes
}
// CanConvert returns whether value v is convertible to type t.
//
// If t is a pointer and v is not nil, it checks if v is convertible to the type that
// t points to.
// Modified due to not handling slices (DefaultCanConvert fails on PhotoUrls and Tags)
// See: https://github.com/Kangaroux/go-map-schema/blob/master/schema.go#L191
func CanConvert(t reflect.Type, v reflect.Value) bool {
var (
isPtr bool
isStruct bool
isArray bool
dstType reflect.Type
dstInt bool
unsigned bool
f float64
srcInt bool
)
isPtr = t.Kind() == reflect.Ptr
isStruct = t.Kind() == reflect.Struct
isArray = t.Kind() == reflect.Array
dstType = t
// Check if v is a nil value.
if !v.IsValid() || (v.CanAddr() && v.IsNil()) {
return isPtr
}
// If the dst is a pointer, check if we can convert to the type it's pointing to.
if isPtr {
dstType = t.Elem()
isStruct = t.Elem().Kind() == reflect.Struct
}
// If the dst is a struct, we should check its nested fields.
if isStruct {
return v.Kind() == reflect.Map
}
if isArray {
return v.Kind() == reflect.String
}
if t.Kind() == reflect.Slice {
return v.Kind() == reflect.Slice
}
if !v.Type().ConvertibleTo(dstType) {
return false
}
// Handle converting to an integer type.
dstInt, unsigned = isIntegerType(dstType)
if dstInt {
if isFloatType(v.Type()) {
f = v.Float()
if math.Trunc(f) != f || unsigned && f < 0 {
return false
}
}
srcInt, _ = isIntegerType(v.Type())
if srcInt && unsigned && v.Int() < 0 {
return false
}
}
return true
}

+ 84
- 0
Backend/Api/Messages/Conversations.go View File

@ -0,0 +1,84 @@
package Messages
import (
"encoding/json"
"net/http"
"net/url"
"strings"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Auth"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
)
// EncryptedConversationList returns an encrypted list of all Conversations
func EncryptedConversationList(w http.ResponseWriter, r *http.Request) {
var (
userConversations []Models.UserConversation
userSession Models.Session
returnJSON []byte
err error
)
userSession, err = Auth.CheckCookie(r)
if err != nil {
http.Error(w, "Forbidden", http.StatusUnauthorized)
return
}
userConversations, err = Database.GetUserConversationsByUserId(
userSession.UserID.String(),
)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
returnJSON, err = json.MarshalIndent(userConversations, "", " ")
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write(returnJSON)
}
// EncryptedConversationDetailsList returns an encrypted list of all ConversationDetails
func EncryptedConversationDetailsList(w http.ResponseWriter, r *http.Request) {
var (
userConversations []Models.ConversationDetail
query url.Values
conversationIds []string
returnJSON []byte
ok bool
err error
)
query = r.URL.Query()
conversationIds, ok = query["conversation_detail_ids"]
if !ok {
http.Error(w, "Invalid Data", http.StatusBadGateway)
return
}
// TODO: Fix error handling here
conversationIds = strings.Split(conversationIds[0], ",")
userConversations, err = Database.GetConversationDetailsByIds(
conversationIds,
)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
returnJSON, err = json.MarshalIndent(userConversations, "", " ")
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write(returnJSON)
}

+ 58
- 0
Backend/Api/Messages/CreateConversation.go View File

@ -0,0 +1,58 @@
package Messages
import (
"encoding/json"
"net/http"
"github.com/gofrs/uuid"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
)
// RawCreateConversationData for holding POST payload
type RawCreateConversationData struct {
ID string `json:"id"`
Name string `json:"name"`
TwoUser string `json:"two_user"`
Users []Models.ConversationDetailUser `json:"users"`
UserConversations []Models.UserConversation `json:"user_conversations"`
}
// CreateConversation creates ConversationDetail, ConversationDetailUsers and UserConversations
func CreateConversation(w http.ResponseWriter, r *http.Request) {
var (
rawConversationData RawCreateConversationData
messageThread Models.ConversationDetail
err error
)
err = json.NewDecoder(r.Body).Decode(&rawConversationData)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
messageThread = Models.ConversationDetail{
Base: Models.Base{
ID: uuid.FromStringOrNil(rawConversationData.ID),
},
Name: rawConversationData.Name,
TwoUser: rawConversationData.TwoUser,
Users: rawConversationData.Users,
}
err = Database.CreateConversationDetail(&messageThread)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
err = Database.CreateUserConversations(&rawConversationData.UserConversations)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}

+ 41
- 0
Backend/Api/Messages/CreateMessage.go View File

@ -0,0 +1,41 @@
package Messages
import (
"encoding/json"
"net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
)
type RawMessageData struct {
MessageData Models.MessageData `json:"message_data"`
Messages []Models.Message `json:"message"`
}
func CreateMessage(w http.ResponseWriter, r *http.Request) {
var (
rawMessageData RawMessageData
err error
)
err = json.NewDecoder(r.Body).Decode(&rawMessageData)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
err = Database.CreateMessageData(&rawMessageData.MessageData)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
err = Database.CreateMessages(&rawMessageData.Messages)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}

+ 45
- 0
Backend/Api/Messages/MessageThread.go View File

@ -0,0 +1,45 @@
package Messages
import (
"encoding/json"
"net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"github.com/gorilla/mux"
)
// Messages gets messages by the associationKey
func Messages(w http.ResponseWriter, r *http.Request) {
var (
messages []Models.Message
urlVars map[string]string
associationKey string
returnJSON []byte
ok bool
err error
)
urlVars = mux.Vars(r)
associationKey, ok = urlVars["associationKey"]
if !ok {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
messages, err = Database.GetMessagesByAssociationKey(associationKey)
if !ok {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
returnJSON, err = json.MarshalIndent(messages, "", " ")
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write(returnJSON)
}

+ 56
- 0
Backend/Api/Messages/UpdateConversation.go View File

@ -0,0 +1,56 @@
package Messages
import (
"encoding/json"
"net/http"
"github.com/gofrs/uuid"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
)
type RawUpdateConversationData struct {
ID string `json:"id"`
Name string `json:"name"`
Users []Models.ConversationDetailUser `json:"users"`
UserConversations []Models.UserConversation `json:"user_conversations"`
}
func UpdateConversation(w http.ResponseWriter, r *http.Request) {
var (
rawConversationData RawCreateConversationData
messageThread Models.ConversationDetail
err error
)
err = json.NewDecoder(r.Body).Decode(&rawConversationData)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
messageThread = Models.ConversationDetail{
Base: Models.Base{
ID: uuid.FromStringOrNil(rawConversationData.ID),
},
Name: rawConversationData.Name,
Users: rawConversationData.Users,
}
err = Database.UpdateConversationDetail(&messageThread)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
if len(rawConversationData.UserConversations) > 0 {
err = Database.UpdateOrCreateUserConversations(&rawConversationData.UserConversations)
if err != nil {
http.Error(w, "Error", http.StatusInternalServerError)
return
}
}
w.WriteHeader(http.StatusOK)
}

+ 79
- 0
Backend/Api/Routes.go View File

@ -0,0 +1,79 @@
package Api
import (
"log"
"net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Auth"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Friends"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Messages"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Users"
"github.com/gorilla/mux"
)
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf(
"%s %s, Content Length: %d",
r.Method,
r.RequestURI,
r.ContentLength,
)
next.ServeHTTP(w, r)
})
}
func authenticationMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error
_, err = Auth.CheckCookie(r)
if err != nil {
http.Error(w, "Forbidden", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
// InitAPIEndpoints initializes all API endpoints required by mobile app
func InitAPIEndpoints(router *mux.Router) {
var (
api *mux.Router
authAPI *mux.Router
)
log.Println("Initializing API routes...")
api = router.PathPrefix("/api/v1/").Subrouter()
api.Use(loggingMiddleware)
// Define routes for authentication
api.HandleFunc("/signup", Auth.Signup).Methods("POST")
api.HandleFunc("/login", Auth.Login).Methods("POST")
api.HandleFunc("/logout", Auth.Logout).Methods("GET")
authAPI = api.PathPrefix("/auth/").Subrouter()
authAPI.Use(authenticationMiddleware)
authAPI.HandleFunc("/check", Auth.Check).Methods("GET")
authAPI.HandleFunc("/users", Users.SearchUsers).Methods("GET")
authAPI.HandleFunc("/friend_requests", Friends.EncryptedFriendRequestList).Methods("GET")
authAPI.HandleFunc("/friend_request", Friends.CreateFriendRequest).Methods("POST")
authAPI.HandleFunc("/friend_request/qr_code", Friends.CreateFriendRequestQrCode).Methods("POST")
authAPI.HandleFunc("/friend_request/{requestID}", Friends.AcceptFriendRequest).Methods("POST")
authAPI.HandleFunc("/friend_request/{requestID}", Friends.RejectFriendRequest).Methods("DELETE")
authAPI.HandleFunc("/conversations", Messages.EncryptedConversationList).Methods("GET")
authAPI.HandleFunc("/conversation_details", Messages.EncryptedConversationDetailsList).Methods("GET")
authAPI.HandleFunc("/conversations", Messages.CreateConversation).Methods("POST")
authAPI.HandleFunc("/conversations", Messages.UpdateConversation).Methods("PUT")
authAPI.HandleFunc("/message", Messages.CreateMessage).Methods("POST")
authAPI.HandleFunc("/messages/{associationKey}", Messages.Messages).Methods("GET")
}

+ 56
- 0
Backend/Api/Users/SearchUsers.go View File

@ -0,0 +1,56 @@
package Users
import (
"encoding/json"
"net/http"
"net/url"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
)
// SearchUsers searches a for a user by username
func SearchUsers(w http.ResponseWriter, r *http.Request) {
var (
user Models.User
query url.Values
rawUsername []string
username string
returnJSON []byte
ok bool
err error
)
query = r.URL.Query()
rawUsername, ok = query["username"]
if !ok {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
if len(rawUsername) != 1 {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
username = rawUsername[0]
user, err = Database.GetUserByUsername(username)
if err != nil {
http.Error(w, "Not Found", http.StatusNotFound)
return
}
user.Password = ""
user.AsymmetricPrivateKey = ""
returnJSON, err = json.MarshalIndent(user, "", " ")
if err != nil {
panic(err)
http.Error(w, "Not Found", http.StatusNotFound)
return
}
w.WriteHeader(http.StatusOK)
w.Write(returnJSON)
}

+ 41
- 0
Backend/Database/ConversationDetailUsers.go View File

@ -0,0 +1,41 @@
package Database
import (
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func GetConversationDetailUserById(id string) (Models.ConversationDetailUser, error) {
var (
messageThread Models.ConversationDetailUser
err error
)
err = DB.Preload(clause.Associations).
Where("id = ?", id).
First(&messageThread).
Error
return messageThread, err
}
func CreateConversationDetailUser(messageThread *Models.ConversationDetailUser) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Create(messageThread).
Error
}
func UpdateConversationDetailUser(messageThread *Models.ConversationDetailUser) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Where("id = ?", messageThread.ID).
Updates(messageThread).
Error
}
func DeleteConversationDetailUser(messageThread *Models.ConversationDetailUser) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Delete(messageThread).
Error
}

+ 55
- 0
Backend/Database/ConversationDetails.go View File

@ -0,0 +1,55 @@
package Database
import (
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func GetConversationDetailById(id string) (Models.ConversationDetail, error) {
var (
messageThread Models.ConversationDetail
err error
)
err = DB.Preload(clause.Associations).
Where("id = ?", id).
First(&messageThread).
Error
return messageThread, err
}
func GetConversationDetailsByIds(id []string) ([]Models.ConversationDetail, error) {
var (
messageThread []Models.ConversationDetail
err error
)
err = DB.Preload(clause.Associations).
Where("id IN ?", id).
Find(&messageThread).
Error
return messageThread, err
}
func CreateConversationDetail(messageThread *Models.ConversationDetail) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Create(messageThread).
Error
}
func UpdateConversationDetail(messageThread *Models.ConversationDetail) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Where("id = ?", messageThread.ID).
Updates(messageThread).
Error
}
func DeleteConversationDetail(messageThread *Models.ConversationDetail) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Delete(messageThread).
Error
}

+ 63
- 0
Backend/Database/FriendRequests.go View File

@ -0,0 +1,63 @@
package Database
import (
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// GetFriendRequestByID gets friend request
func GetFriendRequestByID(id string) (Models.FriendRequest, error) {
var (
friendRequest Models.FriendRequest
err error
)
err = DB.Preload(clause.Associations).
First(&friendRequest, "id = ?", id).
Error
return friendRequest, err
}
// GetFriendRequestsByUserID gets friend request by user id
func GetFriendRequestsByUserID(userID string) ([]Models.FriendRequest, error) {
var (
friends []Models.FriendRequest
err error
)
err = DB.Model(Models.FriendRequest{}).
Where("user_id = ?", userID).
Find(&friends).
Error
return friends, err
}
// CreateFriendRequest creates friend request
func CreateFriendRequest(friendRequest *Models.FriendRequest) error {
return DB.Create(friendRequest).
Error
}
// CreateFriendRequests creates multiple friend requests
func CreateFriendRequests(friendRequest *[]Models.FriendRequest) error {
return DB.Create(friendRequest).
Error
}
// UpdateFriendRequest Updates friend request
func UpdateFriendRequest(friendRequest *Models.FriendRequest) error {
return DB.Where("id = ?", friendRequest.ID).
Updates(friendRequest).
Error
}
// DeleteFriendRequest deletes friend request
func DeleteFriendRequest(friendRequest *Models.FriendRequest) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Delete(friendRequest).
Error
}

+ 69
- 0
Backend/Database/Init.go View File

@ -0,0 +1,69 @@
package Database
import (
"log"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
const (
dbUrl = "postgres://postgres:@localhost:5432/envelope"
dbTestUrl = "postgres://postgres:@localhost:5432/envelope_test"
)
var DB *gorm.DB
func GetModels() []interface{} {
return []interface{}{
&Models.Session{},
&Models.User{},
&Models.FriendRequest{},
&Models.MessageData{},
&Models.Message{},
&Models.ConversationDetail{},
&Models.ConversationDetailUser{},
&Models.UserConversation{},
}
}
func Init() {
var (
model interface{}
err error
)
log.Println("Initializing database...")
DB, err = gorm.Open(postgres.Open(dbUrl), &gorm.Config{})
if err != nil {
log.Fatalln(err)
}
log.Println("Running AutoMigrate...")
for _, model = range GetModels() {
DB.AutoMigrate(model)
}
}
func InitTest() {
var (
model interface{}
err error
)
DB, err = gorm.Open(postgres.Open(dbTestUrl), &gorm.Config{})
if err != nil {
log.Fatalln(err)
}
for _, model = range GetModels() {
DB.Migrator().DropTable(model)
DB.AutoMigrate(model)
}
}

+ 39
- 0
Backend/Database/MessageData.go View File

@ -0,0 +1,39 @@
package Database
import (
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func GetMessageDataById(id string) (Models.MessageData, error) {
var (
messageData Models.MessageData
err error
)
err = DB.Preload(clause.Associations).
First(&messageData, "id = ?", id).
Error
return messageData, err
}
func CreateMessageData(messageData *Models.MessageData) error {
var (
err error
)
err = DB.Session(&gorm.Session{FullSaveAssociations: true}).
Create(messageData).
Error
return err
}
func DeleteMessageData(messageData *Models.MessageData) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Delete(messageData).
Error
}

+ 60
- 0
Backend/Database/Messages.go View File

@ -0,0 +1,60 @@
package Database
import (
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func GetMessageById(id string) (Models.Message, error) {
var (
message Models.Message
err error
)
err = DB.Preload(clause.Associations).
First(&message, "id = ?", id).
Error
return message, err
}
func GetMessagesByAssociationKey(associationKey string) ([]Models.Message, error) {
var (
messages []Models.Message
err error
)
err = DB.Preload("MessageData").
Find(&messages, "association_key = ?", associationKey).
Error
return messages, err
}
func CreateMessage(message *Models.Message) error {
var err error
err = DB.Session(&gorm.Session{FullSaveAssociations: true}).
Create(message).
Error
return err
}
func CreateMessages(messages *[]Models.Message) error {
var err error
err = DB.Session(&gorm.Session{FullSaveAssociations: true}).
Create(messages).
Error
return err
}
func DeleteMessage(message *Models.Message) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Delete(message).
Error
}

+ 113
- 0
Backend/Database/Seeder/FriendSeeder.go View File

@ -0,0 +1,113 @@
package Seeder
import (
"encoding/base64"
"time"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
)
func seedFriend(userRequestTo, userRequestFrom Models.User, accepted bool) error {
var (
friendRequest Models.FriendRequest
symKey aesKey
encPublicKey []byte
err error
)
symKey, err = generateAesKey()
if err != nil {
return err
}
encPublicKey, err = symKey.aesEncrypt([]byte(publicKey))
if err != nil {
return err
}
friendRequest = Models.FriendRequest{
UserID: userRequestTo.ID,
FriendID: base64.StdEncoding.EncodeToString(
encryptWithPublicKey(
[]byte(userRequestFrom.ID.String()),
decodedPublicKey,
),
),
FriendUsername: base64.StdEncoding.EncodeToString(
encryptWithPublicKey(
[]byte(userRequestFrom.Username),
decodedPublicKey,
),
),
FriendPublicAsymmetricKey: base64.StdEncoding.EncodeToString(
encPublicKey,
),
SymmetricKey: base64.StdEncoding.EncodeToString(
encryptWithPublicKey(symKey.Key, decodedPublicKey),
),
}
if accepted {
friendRequest.AcceptedAt.Time = time.Now()
friendRequest.AcceptedAt.Valid = true
}
return Database.CreateFriendRequest(&friendRequest)
}
// SeedFriends creates dummy friends for testing/development
func SeedFriends() {
var (
primaryUser Models.User
secondaryUser Models.User
accepted bool
i int
err error
)
primaryUser, err = Database.GetUserByUsername("testUser")
if err != nil {
panic(err)
}
secondaryUser, err = Database.GetUserByUsername("ATestUser2")
if err != nil {
panic(err)
}
err = seedFriend(primaryUser, secondaryUser, true)
if err != nil {
panic(err)
}
err = seedFriend(secondaryUser, primaryUser, true)
if err != nil {
panic(err)
}
accepted = false
for i = 0; i <= 5; i++ {
secondaryUser, err = Database.GetUserByUsername(userNames[i])
if err != nil {
panic(err)
}
if i > 3 {
accepted = true
}
err = seedFriend(primaryUser, secondaryUser, accepted)
if err != nil {
panic(err)
}
if accepted {
err = seedFriend(secondaryUser, primaryUser, accepted)
if err != nil {
panic(err)
}
}
}
}

+ 313
- 0
Backend/Database/Seeder/MessageSeeder.go View File

@ -0,0 +1,313 @@
package Seeder
import (
"encoding/base64"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"github.com/gofrs/uuid"
)
func seedMessage(
primaryUser, secondaryUser Models.User,
primaryUserAssociationKey, secondaryUserAssociationKey string,
i int,
) error {
var (
message Models.Message
messageData Models.MessageData
key, userKey aesKey
keyCiphertext []byte
plaintext string
dataCiphertext []byte
senderIDCiphertext []byte
err error
)
plaintext = "Test Message"
userKey, err = generateAesKey()
if err != nil {
panic(err)
}
key, err = generateAesKey()
if err != nil {
panic(err)
}
dataCiphertext, err = key.aesEncrypt([]byte(plaintext))
if err != nil {
panic(err)
}
senderIDCiphertext, err = key.aesEncrypt([]byte(primaryUser.ID.String()))
if err != nil {
panic(err)
}
if i%2 == 0 {
senderIDCiphertext, err = key.aesEncrypt([]byte(secondaryUser.ID.String()))
if err != nil {
panic(err)
}
}
keyCiphertext, err = userKey.aesEncrypt(
[]byte(base64.StdEncoding.EncodeToString(key.Key)),
)
if err != nil {
panic(err)
}
messageData = Models.MessageData{
Data: base64.StdEncoding.EncodeToString(dataCiphertext),
SenderID: base64.StdEncoding.EncodeToString(senderIDCiphertext),
SymmetricKey: base64.StdEncoding.EncodeToString(keyCiphertext),
}
message = Models.Message{
MessageData: messageData,
SymmetricKey: base64.StdEncoding.EncodeToString(
encryptWithPublicKey(userKey.Key, decodedPublicKey),
),
AssociationKey: primaryUserAssociationKey,
}
err = Database.CreateMessage(&message)
if err != nil {
return err
}
message = Models.Message{
MessageData: messageData,
SymmetricKey: base64.StdEncoding.EncodeToString(
encryptWithPublicKey(userKey.Key, decodedPublicKey),
),
AssociationKey: secondaryUserAssociationKey,
}
return Database.CreateMessage(&message)
}
func seedConversationDetail(key aesKey) (Models.ConversationDetail, error) {
var (
messageThread Models.ConversationDetail
name string
nameCiphertext []byte
twoUserCiphertext []byte
err error
)
name = "Test Conversation"
nameCiphertext, err = key.aesEncrypt([]byte(name))
if err != nil {
panic(err)
}
twoUserCiphertext, err = key.aesEncrypt([]byte("false"))
if err != nil {
panic(err)
}
messageThread = Models.ConversationDetail{
Name: base64.StdEncoding.EncodeToString(nameCiphertext),
TwoUser: base64.StdEncoding.EncodeToString(twoUserCiphertext),
}
err = Database.CreateConversationDetail(&messageThread)
return messageThread, err
}
func seedUserConversation(
user Models.User,
threadID uuid.UUID,
key aesKey,
) (Models.UserConversation, error) {
var (
messageThreadUser Models.UserConversation
conversationDetailIDCiphertext []byte
adminCiphertext []byte
err error
)
conversationDetailIDCiphertext, err = key.aesEncrypt([]byte(threadID.String()))
if err != nil {
return messageThreadUser, err
}
adminCiphertext, err = key.aesEncrypt([]byte("true"))
if err != nil {
return messageThreadUser, err
}
messageThreadUser = Models.UserConversation{
UserID: user.ID,
ConversationDetailID: base64.StdEncoding.EncodeToString(conversationDetailIDCiphertext),
Admin: base64.StdEncoding.EncodeToString(adminCiphertext),
SymmetricKey: base64.StdEncoding.EncodeToString(
encryptWithPublicKey(key.Key, decodedPublicKey),
),
}
err = Database.CreateUserConversation(&messageThreadUser)
return messageThreadUser, err
}
func seedConversationDetailUser(
user Models.User,
conversationDetail Models.ConversationDetail,
associationKey uuid.UUID,
admin bool,
key aesKey,
) (Models.ConversationDetailUser, error) {
var (
conversationDetailUser Models.ConversationDetailUser
userIDCiphertext []byte
usernameCiphertext []byte
adminCiphertext []byte
associationKeyCiphertext []byte
publicKeyCiphertext []byte
adminString = "false"
err error
)
if admin {
adminString = "true"
}
userIDCiphertext, err = key.aesEncrypt([]byte(user.ID.String()))
if err != nil {
return conversationDetailUser, err
}
usernameCiphertext, err = key.aesEncrypt([]byte(user.Username))
if err != nil {
return conversationDetailUser, err
}
adminCiphertext, err = key.aesEncrypt([]byte(adminString))
if err != nil {
return conversationDetailUser, err
}
associationKeyCiphertext, err = key.aesEncrypt([]byte(associationKey.String()))
if err != nil {
return conversationDetailUser, err
}
publicKeyCiphertext, err = key.aesEncrypt([]byte(user.AsymmetricPublicKey))
if err != nil {
return conversationDetailUser, err
}
conversationDetailUser = Models.ConversationDetailUser{
ConversationDetailID: conversationDetail.ID,
UserID: base64.StdEncoding.EncodeToString(userIDCiphertext),
Username: base64.StdEncoding.EncodeToString(usernameCiphertext),
Admin: base64.StdEncoding.EncodeToString(adminCiphertext),
AssociationKey: base64.StdEncoding.EncodeToString(associationKeyCiphertext),
PublicKey: base64.StdEncoding.EncodeToString(publicKeyCiphertext),
}
err = Database.CreateConversationDetailUser(&conversationDetailUser)
return conversationDetailUser, err
}
// SeedMessages seeds messages & conversations for testing
func SeedMessages() {
var (
conversationDetail Models.ConversationDetail
key aesKey
primaryUser Models.User
primaryUserAssociationKey uuid.UUID
secondaryUser Models.User
secondaryUserAssociationKey uuid.UUID
i int
err error
)
key, err = generateAesKey()
if err != nil {
panic(err)
}
conversationDetail, err = seedConversationDetail(key)
primaryUserAssociationKey, err = uuid.NewV4()
if err != nil {
panic(err)
}
secondaryUserAssociationKey, err = uuid.NewV4()
if err != nil {
panic(err)
}
primaryUser, err = Database.GetUserByUsername("testUser")
if err != nil {
panic(err)
}
_, err = seedUserConversation(
primaryUser,
conversationDetail.ID,
key,
)
if err != nil {
panic(err)
}
secondaryUser, err = Database.GetUserByUsername("ATestUser2")
if err != nil {
panic(err)
}
_, err = seedUserConversation(
secondaryUser,
conversationDetail.ID,
key,
)
if err != nil {
panic(err)
}
_, err = seedConversationDetailUser(
primaryUser,
conversationDetail,
primaryUserAssociationKey,
true,
key,
)
if err != nil {
panic(err)
}
_, err = seedConversationDetailUser(
secondaryUser,
conversationDetail,
secondaryUserAssociationKey,
false,
key,
)
if err != nil {
panic(err)
}
for i = 0; i <= 20; i++ {
err = seedMessage(
primaryUser,
secondaryUser,
primaryUserAssociationKey.String(),
secondaryUserAssociationKey.String(),
i,
)
if err != nil {
panic(err)
}
}
}

+ 97
- 0
Backend/Database/Seeder/Seed.go View File

@ -0,0 +1,97 @@
package Seeder
import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"log"
)
const (
// Encrypted with "password"
encryptedPrivateKey string = `sPhQsHpXYFqPb7qdmTY7APFwBb4m7meCITujDeKMQFnIjplOVm9ijjXU+YAmGvrX13ukBj8zo9MTVhjJUjJ917pyLhl4w8uyg1jCvplUYtJVXhGA9Wy3NqHMuq3SU3fKdlEM+oR4zYkbAYWp42XvulbcuVBEWiWkvHOrbdKPFpMmd54SL2c/vcWrmjgC7rTlJf2TYICZwRK+6Y0XZi5fSWeU0vg7+rHWKHc5MHHtAdAiL+HCa90c5gfh+hXkT5ojGHOkhT9kdLy3PTPN19EGpdXgZ3WFq1z9CZ6zX7uM091uR0IvgzfwaLx8HJCx7ViWQhioH9LJZgC73RMf/dwzejg2COy4QT/E59RPOczgd779rxiRmphMoR8xJYBFRlkTVmcUO4NcUE50Cc39hXezcekHuV1YQK4BXTrxGX1ceiCXYlKAWS9wHZpog9OldTCPBpw5XAWExh3kRzqdvsdHxHVE+TpAEIjDljAlc3r+FPHYH1zWWk41eQ/zz3Vkx5Zl4dMF9x+uUOspQXVb/4K42e9fMKychNUN5o/JzIwy7xOzgXa6iwf223On/mXKV6FK6Q8lojK7Wc8g7AwfqnN9//HjI14pVqGBJtn5ggL/g4qt0JFl3pV/6n/ZLMG6k8wpsaApLGvsTPqZHcv+C69Z33rZQ4TagXVxpmnWMpPCaR0+Dawn4iAce2UvUtIN2KbJNcTtRQo4z30+BbgmVKHgkR0EHMu4cYjJPYwJ5H8IYcQuFKb7+Cp33FD2Lv54I9uvtVHH9bWcid9K82y68PufJi/0icZ3EyEqZygez9mgJzxXO1b7xZMiosGs82QRv7IIOSzqBPRYv1Lxi3fWkgnOvw4dWFxJnKEI2+KD9K0z+XsgVlm26fdRklQAAf6xOJ1nJXBScbm12FBTWLMjLzHWz/iI9mQ+eGV9AREqrgQjUayXdnCsa0Q9bTTktxBkrJND4NUEDSGklhj9SY+VM0mhgAbkCvSE59vKtcNmCHx2Y+JnbZyKzJ71EaErX9vOpYCneKOjn8phVBJHQHM16QRLGyW4DUfn2CtAvb7Kks56kf/mn9YZDU68zSoLzm9rz7fjS2OUsxwmuv2IRCv/UTGgtfEfCs34qzagADfTNKTou7qkedhoygvuHiN4PzgGnjw1DQMks9PWr44z1gvIV4pEGiqgIuNHDjxKsfgQy0Cp2AV1+FNLWd1zd5t/K2pXR+knDoeHIZ2m6txQMl9I4GIyQ1bQFJWrYXPS8oMjvoH0YYVsHyShBsU2SKlG7nGbuUyoCR1EtRIzHMgP1Dq+Whqdbv67pRvhGVmydkCh0wbD+LJBcp2KJK+EQT9vv6GT5JW0oVHnE5TEXCnEJOW/rMhNMTMSccRmnVdguIE4HZsXx+cmV36jHgEt9bzcsvyWvFFoG4xL+t2UUnztX870vu//XaeVuOEAgehY/KLncrY7lhsQA4puCFIWpPteiCNhU1D8DTKc8V0ZtLT9a31SL1NLhZ+YHiD8Hs5SYdj6FW50E5yYUqPRPkg5mpbh88cRcPdsngCxU8iusNN3MSP07lO0h8zULDqtQsAq9p5o7IFTvWlAjekMy1sKTj3CuH7FuAkMHvwU0odMFeaS9T+8+4OGeprHwogWTzTbPnoOqOP/RC6vGfBvpju5s264hYguT24iXzhDFYk/8JQQe+USIbkQ7wXRw+/9cK8h5cs4LyaxMOx0pXHooxJ01bF8BYgYG4s0RB2gItzMk/L5/XhrOdWxEAdYR27s0dCN58gyvoU6phgQbTqvNTFYAObRcjfKfHu3PrFCYBBAKJ7Nm58C3rz832+ZTGVdQ3490TvO+sCLYKzpgtsqr8KyedG9LKa8wn/wlRD7kYn+J2SrMPY2Q0e4evyJaCAsolp/BQfy9JFtyRDPWTHn+jOHjW8ZN7vswGkRwYlSJSl0UC8mmJyS4lwnO/Vv4wBnDHQEzIycjn3JZAlV5ing0HKqUfW6G07453JXd8oZiMC/kIQjgWkdg34zxBYarVVrHFG5FIH9w7QWY8PCDU/kkcLniT0yD1/gkqAG2HpwaXEcSqX8Ofrbpd/IA7R7iCXYE5Q1mAvSvICpPg9Cf3CHjLyAEDz9cwKnZHkocXC8evdsTf2e7Wz8FFPAI3onFvym0MfZuRrIZitX1V8NOLedd3y74CwuErfzrr60DjyPRxGbJ4llMbm+ojeENe0HBedNm71jf+McSihKbSo5GDBxfVYVreYZ8A4iP0LsxtzQFxuzdeDL5KA9uNNw+LN9FN9vKhdALhQSnSfLPfMBsM/ey7dbxb4eRT0fpApX`
// Private key for testing server side
privateKey string = `-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJScQQJxWxKwqf
FmXH64QnRBVyW7cU25F+O9Zy96dqTjbV4ruWrzb4+txmK20ZPQvMxDLefhEzTXWb
HZV1P/XxgmEpaBVHwHnkhaPzzChOa/G18CDoCNrgyVzh5a31OotTCuGlS1bSkR53
ExPXQq8nPJKqN1tdwAslr4cT61zypKSmZsJa919IZeHL9p1/UMXknfThcR9z3rR4
ll6O7+dmrZzaDdJz39IC38K5yJsThau5hnddpssw76k4rZ9KFFlbWJSHFvWIAhst
lCV0kCwdAmPNVNavzQfvTxWeN1x9uJUstVlg60DRCmRhjC88K77sU+1jp4cp/Uv8
aSGRpytlAgMBAAECggEBALFbJr8YwRs/EnfEU2AI24OBkOgXecSOBq9UaAsavU+E
pPpmceU+c1CEMUhwwQs457m/shaqu9sZSCOpuHP8LGdk+tlyFTYImR5KxoBdBbK7
l9k4QLZSfxELO6TrLBDkSbic4N8098ZHCbHfhF7qKcyHqa8DYaTEPs4wz/M0Mcy0
xziCxMUFh/LhSLDH8PMMXZ+HV3+zmxdEqmaZvk3FQOGD1O39I9TA8PnFa11whVbN
nMSjxgmK+byPIM4LFXNHk+TZsJm1FaYaGVdLetAPET7p6XMrMWy+z/4dcb4GbYjY
0i5Xv1lVlIRgDB9xj0MOW5hzQzTPHC4JN4nIoBFSc20CgYEA5IgymckwqKJJWXRn
AIJ3guuEp4vBtjmdVCJnFmbPEeW+WY+CNuwn9DK78Zavfn1HruryE/hkYLVNPm8y
KSf16+tIadUXcao1UIVDNSVC6jtFmRLgWuPXbNKFQwUor1ai9IK+F3JV8pfr36HE
8rk/LEM0DIgsTg+j+IKT39a7IucCgYEA4XtKGhvnGUdcveMPcrvuQlSnucSpw5Ly
4KuRsTySdMihhxX1GSyg6F2T4YKFRqKZERsYgYk6A32u53If+VkXacvOsInwuoBa
FTb3fOQpw1xBSI7R3RgiriY4cCsDetexEBbg7/SrodpQu254A8+5PKxrSR1U+idx
boX745k1gdMCgYEAuZ7CctTOV/pQ137LdseBqO4BNlE2yvrrBf5XewOQZzoTLQ16
N4ADR765lxXMf1HkmnesnnnflglMr0yEEpepkLDvhT6WpzUXzsoe95jHTBdOhXGm
l0x+mp43rWMQU7Jr82wKWGL+2md5J5ButrOuUxZWvWMRkWn0xhHRaDsyjrsCgYAq
zNRMEG/VhI4+HROZm8KmJJuRz5rJ3OLtcqO9GNpUAKFomupjVO1WLi0b6UKTHdog
PRxxujKg5wKEPE2FbzvagS1CpWxkemifDkf8FPM4ehKKS1HavfIXTHn6ELAgaUDa
5Pzdj3vkxSP98AIn9w4aTkAvKLowobwOVrBxi2t0sQKBgHh2TrGSnlV3s1DijfNM
0JiwsHWz0hljybcZaZP45nsgGRiR15TcIiOLwkjaCws2tYtOSOT4sM7HV/s2mpPa
b0XvaLzh1iKG7HZ9tvPt/VhHlKKosNBK/j4fvgMZg7/bhRfHmaDQKoqlGbtyWjEQ
mj1b2/Gnbk3VYDR16BFfj7m2
-----END PRIVATE KEY-----`
publicKey string = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyUnEECcVsSsKnxZlx+uE
J0QVclu3FNuRfjvWcvenak421eK7lq82+PrcZittGT0LzMQy3n4RM011mx2VdT/1
8YJhKWgVR8B55IWj88woTmvxtfAg6Aja4Mlc4eWt9TqLUwrhpUtW0pEedxMT10Kv
JzySqjdbXcALJa+HE+tc8qSkpmbCWvdfSGXhy/adf1DF5J304XEfc960eJZeju/n
Zq2c2g3Sc9/SAt/CucibE4WruYZ3XabLMO+pOK2fShRZW1iUhxb1iAIbLZQldJAs
HQJjzVTWr80H708VnjdcfbiVLLVZYOtA0QpkYYwvPCu+7FPtY6eHKf1L/Gkhkacr
ZQIDAQAB
-----END PUBLIC KEY-----`
)
var (
decodedPublicKey *rsa.PublicKey
decodedPrivateKey *rsa.PrivateKey
)
func Seed() {
var (
block *pem.Block
decKey any
ok bool
err error
)
block, _ = pem.Decode([]byte(publicKey))
decKey, err = x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
panic(err)
}
decodedPublicKey, ok = decKey.(*rsa.PublicKey)
if !ok {
panic(errors.New("Invalid decodedPublicKey"))
}
block, _ = pem.Decode([]byte(privateKey))
decKey, err = x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
panic(err)
}
decodedPrivateKey, ok = decKey.(*rsa.PrivateKey)
if !ok {
panic(errors.New("Invalid decodedPrivateKey"))
}
log.Println("Seeding users...")
SeedUsers()
log.Println("Seeding friend connections...")
SeedFriends()
log.Println("Seeding messages...")
SeedMessages()
}

+ 68
- 0
Backend/Database/Seeder/UserSeeder.go View File

@ -0,0 +1,68 @@
package Seeder
import (
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Api/Auth"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
)
var userNames = []string{
"assuredcoot",
"quotesteeve",
"blueberriessiemens",
"eliteexaggerate",
"twotrice",
"moderagged",
"duleelderly",
"stringdetailed",
"nodesanymore",
"sacredpolitical",
"pajamasenergy",
}
func createUser(username string) (Models.User, error) {
var (
userData Models.User
password string
err error
)
password, err = Auth.HashPassword("password")
if err != nil {
return Models.User{}, err
}
userData = Models.User{
Username: username,
Password: password,
AsymmetricPrivateKey: encryptedPrivateKey,
AsymmetricPublicKey: publicKey,
}
err = Database.CreateUser(&userData)
return userData, err
}
func SeedUsers() {
var (
i int
err error
)
// Seed users used for conversation seeding
_, err = createUser("testUser")
if err != nil {
panic(err)
}
_, err = createUser("ATestUser2")
if err != nil {
panic(err)
}
for i = 0; i <= 10; i++ {
_, err = createUser(userNames[i])
if err != nil {
panic(err)
}
}
}

+ 188
- 0
Backend/Database/Seeder/encryption.go View File

@ -0,0 +1,188 @@
package Seeder
// THIS FILE IS ONLY USED FOR SEEDING DATA DURING DEVELOPMENT
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"fmt"
"hash"
"golang.org/x/crypto/pbkdf2"
)
type aesKey struct {
Key []byte
Iv []byte
}
func (key aesKey) encode() string {
return base64.StdEncoding.EncodeToString(key.Key)
}
// Appends padding.
func pkcs7Padding(data []byte, blocklen int) ([]byte, error) {
var (
padlen int = 1
pad []byte
)
if blocklen <= 0 {
return nil, fmt.Errorf("invalid blocklen %d", blocklen)
}
for ((len(data) + padlen) % blocklen) != 0 {
padlen = padlen + 1
}
pad = bytes.Repeat([]byte{byte(padlen)}, padlen)
return append(data, pad...), nil
}
// pkcs7strip remove pkcs7 padding
func pkcs7strip(data []byte, blockSize int) ([]byte, error) {
var (
length int
padLen int
ref []byte
)
length = len(data)
if length == 0 {
return nil, fmt.Errorf("pkcs7: Data is empty")
}
if (length % blockSize) != 0 {
return nil, fmt.Errorf("pkcs7: Data is not block-aligned")
}
padLen = int(data[length-1])
ref = bytes.Repeat([]byte{byte(padLen)}, padLen)
if padLen > blockSize || padLen == 0 || !bytes.HasSuffix(data, ref) {
return nil, fmt.Errorf("pkcs7: Invalid padding")
}
return data[:length-padLen], nil
}
func generateAesKey() (aesKey, error) {
var (
saltBytes []byte = []byte{}
password []byte
seed []byte
iv []byte
err error
)
password = make([]byte, 64)
_, err = rand.Read(password)
if err != nil {
return aesKey{}, err
}
seed = make([]byte, 64)
_, err = rand.Read(seed)
if err != nil {
return aesKey{}, err
}
iv = make([]byte, 16)
_, err = rand.Read(iv)
if err != nil {
return aesKey{}, err
}
return aesKey{
Key: pbkdf2.Key(
password,
saltBytes,
1000,
32,
func() hash.Hash { return hmac.New(sha256.New, seed) },
),
Iv: iv,
}, nil
}
func (key aesKey) aesEncrypt(plaintext []byte) ([]byte, error) {
var (
bPlaintext []byte
ciphertext []byte
block cipher.Block
err error
)
bPlaintext, err = pkcs7Padding(plaintext, 16)
block, err = aes.NewCipher(key.Key)
if err != nil {
return []byte{}, err
}
ciphertext = make([]byte, len(bPlaintext))
mode := cipher.NewCBCEncrypter(block, key.Iv)
mode.CryptBlocks(ciphertext, bPlaintext)
ciphertext = append(key.Iv, ciphertext...)
return ciphertext, nil
}
func (key aesKey) aesDecrypt(ciphertext []byte) ([]byte, error) {
var (
plaintext []byte
iv []byte
block cipher.Block
err error
)
iv = ciphertext[:aes.BlockSize]
plaintext = ciphertext[aes.BlockSize:]
block, err = aes.NewCipher(key.Key)
if err != nil {
return []byte{}, err
}
decMode := cipher.NewCBCDecrypter(block, iv)
decMode.CryptBlocks(plaintext, plaintext)
return plaintext, nil
}
// EncryptWithPublicKey encrypts data with public key
func encryptWithPublicKey(msg []byte, pub *rsa.PublicKey) []byte {
var (
hash hash.Hash
)
hash = sha256.New()
ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, pub, msg, nil)
if err != nil {
panic(err)
}
return ciphertext
}
// DecryptWithPrivateKey decrypts data with private key
func decryptWithPrivateKey(ciphertext []byte, priv *rsa.PrivateKey) ([]byte, error) {
var (
hash hash.Hash
plaintext []byte
err error
)
hash = sha256.New()
plaintext, err = rsa.DecryptOAEP(hash, rand.Reader, priv, ciphertext, nil)
if err != nil {
return plaintext, err
}
return plaintext, nil
}

+ 38
- 0
Backend/Database/Sessions.go View File

@ -0,0 +1,38 @@
package Database
import (
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"gorm.io/gorm/clause"
)
func GetSessionById(id string) (Models.Session, error) {
var (
session Models.Session
err error
)
err = DB.Preload(clause.Associations).
First(&session, "id = ?", id).
Error
return session, err
}
func CreateSession(session *Models.Session) error {
var (
err error
)
err = DB.Create(session).Error
return err
}
func DeleteSession(session *Models.Session) error {
return DB.Delete(session).Error
}
func DeleteSessionById(id string) error {
return DB.Delete(&Models.Session{}, id).Error
}

+ 91
- 0
Backend/Database/UserConversations.go View File

@ -0,0 +1,91 @@
package Database
import (
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func GetUserConversationById(id string) (Models.UserConversation, error) {
var (
message Models.UserConversation
err error
)
err = DB.First(&message, "id = ?", id).
Error
return message, err
}
func GetUserConversationsByUserId(id string) ([]Models.UserConversation, error) {
var (
conversations []Models.UserConversation
err error
)
err = DB.Find(&conversations, "user_id = ?", id).
Error
return conversations, err
}
func CreateUserConversation(userConversation *Models.UserConversation) error {
var err error
err = DB.Session(&gorm.Session{FullSaveAssociations: true}).
Create(userConversation).
Error
return err
}
func CreateUserConversations(userConversations *[]Models.UserConversation) error {
var err error
err = DB.Create(userConversations).
Error
return err
}
func UpdateUserConversation(userConversation *Models.UserConversation) error {
var err error
err = DB.Model(Models.UserConversation{}).
Updates(userConversation).
Error
return err
}
func UpdateUserConversations(userConversations *[]Models.UserConversation) error {
var err error
err = DB.Model(Models.UserConversation{}).
Updates(userConversations).
Error
return err
}
func UpdateOrCreateUserConversations(userConversations *[]Models.UserConversation) error {
var err error
err = DB.Model(Models.UserConversation{}).
Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.AssignmentColumns([]string{"admin"}),
}).
Create(userConversations).
Error
return err
}
func DeleteUserConversation(userConversation *Models.UserConversation) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Delete(userConversation).
Error
}

+ 95
- 0
Backend/Database/Users.go View File

@ -0,0 +1,95 @@
package Database
import (
"errors"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func GetUserById(id string) (Models.User, error) {
var (
user Models.User
err error
)
err = DB.Preload(clause.Associations).
First(&user, "id = ?", id).
Error
return user, err
}
func GetUserByUsername(username string) (Models.User, error) {
var (
user Models.User
err error
)
err = DB.Preload(clause.Associations).
First(&user, "username = ?", username).
Error
return user, err
}
func CheckUniqueUsername(username string) error {
var (
exists bool
err error
)
err = DB.Model(Models.User{}).
Select("count(*) > 0").
Where("username = ?", username).
Find(&exists).
Error
if err != nil {
return err
}
if exists {
return errors.New("Invalid username")
}
return nil
}
func CreateUser(user *Models.User) error {
var err error
err = DB.Session(&gorm.Session{FullSaveAssociations: true}).
Create(user).
Error
return err
}
func UpdateUser(id string, user *Models.User) error {
var err error
err = DB.Model(&user).
Omit("id").
Where("id = ?", id).
Updates(user).
Error
if err != nil {
return err
}
err = DB.Model(Models.User{}).
Where("id = ?", id).
First(user).
Error
return err
}
func DeleteUser(user *Models.User) error {
return DB.Session(&gorm.Session{FullSaveAssociations: true}).
Delete(user).
Error
}

+ 31
- 0
Backend/Models/Base.go View File

@ -0,0 +1,31 @@
package Models
import (
"github.com/gofrs/uuid"
"gorm.io/gorm"
)
// Base contains common columns for all tables.
type Base struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;" json:"id"`
}
// BeforeCreate will set a UUID rather than numeric ID.
func (base *Base) BeforeCreate(tx *gorm.DB) error {
var (
id uuid.UUID
err error
)
if !base.ID.IsNil() {
return nil
}
id, err = uuid.NewV4()
if err != nil {
return err
}
base.ID = id
return nil
}

+ 35
- 0
Backend/Models/Conversations.go View File

@ -0,0 +1,35 @@
package Models
import (
"github.com/gofrs/uuid"
)
// ConversationDetail stores the name for the conversation
type ConversationDetail struct {
Base
Name string `gorm:"not null" json:"name"` // Stored encrypted
Users []ConversationDetailUser ` json:"users"`
TwoUser string `gorm:"not null" json:"two_user"`
}
// ConversationDetailUser all users associated with a customer
type ConversationDetailUser struct {
Base
ConversationDetailID uuid.UUID `gorm:"not null" json:"conversation_detail_id"`
ConversationDetail ConversationDetail `gorm:"not null" json:"conversation"`
UserID string `gorm:"not null" json:"user_id"` // Stored encrypted
Username string `gorm:"not null" json:"username"` // Stored encrypted
Admin string `gorm:"not null" json:"admin"` // Stored encrypted
AssociationKey string `gorm:"not null" json:"association_key"` // Stored encrypted
PublicKey string `gorm:"not null" json:"public_key"` // Stored encrypted
}
// UserConversation Used to link the current user to their conversations
type UserConversation struct {
Base
UserID uuid.UUID `gorm:"type:uuid;column:user_id;not null;" json:"user_id"`
User User ` json:"user"`
ConversationDetailID string `gorm:"not null" json:"conversation_detail_id"` // Stored encrypted
Admin string `gorm:"not null" json:"admin"` // Bool if user is admin of thread, stored encrypted
SymmetricKey string `gorm:"not null" json:"symmetric_key"` // Stored encrypted
}

+ 19
- 0
Backend/Models/Friends.go View File

@ -0,0 +1,19 @@
package Models
import (
"database/sql"
"github.com/gofrs/uuid"
)
// FriendRequest Set with Friend being the requestee, and RequestFromID being the requester
type FriendRequest struct {
Base
UserID uuid.UUID `gorm:"type:uuid;column:user_id;not null;" json:"user_id"`
User User ` json:"user"`
FriendID string `gorm:"not null" json:"friend_id"` // Stored encrypted
FriendUsername string ` json:"friend_username"` // Stored encrypted
FriendPublicAsymmetricKey string ` json:"asymmetric_public_key"` // Stored encrypted
SymmetricKey string `gorm:"not null" json:"symmetric_key"` // Stored encrypted
AcceptedAt sql.NullTime ` json:"accepted_at"`
}

+ 24
- 0
Backend/Models/Messages.go View File

@ -0,0 +1,24 @@
package Models
import (
"time"
"github.com/gofrs/uuid"
)
// TODO: Add support for images
type MessageData struct {
Base
Data string `gorm:"not null" json:"data"` // Stored encrypted
SenderID string `gorm:"not null" json:"sender_id"` // Stored encrypted
SymmetricKey string `gorm:"not null" json:"symmetric_key"` // Stored encrypted
}
type Message struct {
Base
MessageDataID uuid.UUID `json:"message_data_id"`
MessageData MessageData `json:"message_data"`
SymmetricKey string `json:"symmetric_key" gorm:"not null"` // Stored encrypted
AssociationKey string `json:"association_key" gorm:"not null"` // TODO: This links all encrypted messages for a user in a thread together. Find a way to fix this
CreatedAt time.Time `json:"created_at" gorm:"not null"`
}

+ 18
- 0
Backend/Models/Sessions.go View File

@ -0,0 +1,18 @@
package Models
import (
"time"
"github.com/gofrs/uuid"
)
func (s Session) IsExpired() bool {
return s.Expiry.Before(time.Now())
}
type Session struct {
Base
UserID uuid.UUID `gorm:"type:uuid;column:user_id;not null;"`
User User
Expiry time.Time
}

+ 23
- 0
Backend/Models/Users.go View File

@ -0,0 +1,23 @@
package Models
import (
"gorm.io/gorm"
)
// Prevent updating the email if it has not changed
// This stops a unique constraint error
func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
if !tx.Statement.Changed("Username") {
tx.Statement.Omit("Username")
}
return nil
}
type User struct {
Base
Username string `gorm:"not null;unique" json:"username"`
Password string `gorm:"not null" json:"password"`
ConfirmPassword string `gorm:"-" json:"confirm_password"`
AsymmetricPrivateKey string `gorm:"not null" json:"asymmetric_private_key"` // Stored encrypted
AsymmetricPublicKey string `gorm:"not null" json:"asymmetric_public_key"`
}

+ 21
- 0
Backend/Util/Bytes.go View File

@ -0,0 +1,21 @@
package Util
import (
"bytes"
"encoding/gob"
)
func ToBytes(key interface{}) ([]byte, error) {
var (
buf bytes.Buffer
enc *gob.Encoder
err error
)
enc = gob.NewEncoder(&buf)
err = enc.Encode(key)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

+ 21
- 0
Backend/Util/Strings.go View File

@ -0,0 +1,21 @@
package Util
import (
"math/rand"
)
var (
letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
)
func RandomString(n int) string {
var (
b []rune
i int
)
b = make([]rune, n)
for i = range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}

+ 51
- 0
Backend/Util/UserHelper.go View File

@ -0,0 +1,51 @@
package Util
import (
"errors"
"log"
"net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Models"
"github.com/gorilla/mux"
)
func GetUserId(r *http.Request) (string, error) {
var (
urlVars map[string]string
id string
ok bool
)
urlVars = mux.Vars(r)
id, ok = urlVars["userID"]
if !ok {
return id, errors.New("Could not get id")
}
return id, nil
}
func GetUserById(w http.ResponseWriter, r *http.Request) (Models.User, error) {
var (
postData Models.User
id string
err error
)
id, err = GetUserId(r)
if err != nil {
log.Printf("Error encountered getting id\n")
http.Error(w, "Error", http.StatusInternalServerError)
return postData, err
}
postData, err = Database.GetUserById(id)
if err != nil {
log.Printf("Could not find user with id %s\n", id)
http.Error(w, "Error", http.StatusInternalServerError)
return postData, err
}
return postData, nil
}

+ 26
- 0
Backend/go.mod View File

@ -0,0 +1,26 @@
module git.tovijaeschke.xyz/tovi/Envelope/Backend
go 1.18
require (
github.com/Kangaroux/go-map-schema v0.6.1
github.com/gofrs/uuid v4.2.0+incompatible
github.com/gorilla/mux v1.8.0
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
gorm.io/driver/postgres v1.3.4
gorm.io/gorm v1.23.4
)
require (
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.11.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.2.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jackc/pgtype v1.10.0 // indirect
github.com/jackc/pgx/v4 v4.15.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.4 // indirect
golang.org/x/text v0.3.7 // indirect
)

+ 192
- 0
Backend/go.sum View File

@ -0,0 +1,192 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Kangaroux/go-map-schema v0.6.1 h1:jXpOzi7kNFC6M8QSvJuI7xeDxObBrVHwA3D6vSrxuG4=
github.com/Kangaroux/go-map-schema v0.6.1/go.mod h1:56jN+6h/N8Pmn5D+JL9gREOvZTlVEAvXtXyLd/NRjh4=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
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/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0=
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.11.0 h1:HiHArx4yFbwl91X3qqIHtUFoiIfLNJXCQRsnzkiwwaQ=
github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns=
github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38=
github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w=
github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
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.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/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-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/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.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=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.3.4 h1:evZ7plF+Bp+Lr1mO5NdPvd6M/N98XtwHixGB+y7fdEQ=
gorm.io/driver/postgres v1.3.4/go.mod h1:y0vEuInFKJtijuSGu9e5bs5hzzSzPK+LancpKpvbRBw=
gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.23.4 h1:1BKWM67O6CflSLcwGQR7ccfmC4ebOxQrTfOQGRE9wjg=
gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

+ 45
- 0
Backend/main.go View File

@ -0,0 +1,45 @@
package main
import (
"flag"
"log"
"net/http"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Api"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database"
"git.tovijaeschke.xyz/tovi/Envelope/Backend/Database/Seeder"
"github.com/gorilla/mux"
)
var seed bool
func init() {
Database.Init()
flag.BoolVar(&seed, "seed", false, "Seed database for development")
flag.Parse()
}
func main() {
var (
router *mux.Router
err error
)
if seed {
Seeder.Seed()
return
}
router = mux.NewRouter()
Api.InitAPIEndpoints(router)
log.Println("Listening on port :8080")
err = http.ListenAndServe(":8080", router)
if err != nil {
panic(err)
}
}

+ 16
- 1
README.md View File

@ -1,3 +1,18 @@
# Envelope # Envelope
Encrypted messaging app
Encrypted messaging app
## TODO
[x] Fix adding users to conversations
[x] Fix users recieving messages
[x] Fix the admin checks on conversation settings page
[x] Fix sending messages in a conversation that includes users that are not the current users friend
[x] Add admin checks to conversation settings page
[ ] Add admin checks on backend
[ ] Add errors to login / signup page
[ ] Add errors when updating conversations
[ ] Refactor the update conversations function
[ ] Finish the friends list page
[ ] Allow adding friends
[ ] Finish the disappearing messages functionality

+ 46
- 0
mobile/.gitignore View File

@ -0,0 +1,46 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

+ 10
- 0
mobile/.metadata View File

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: c860cba910319332564e1e9d470a17074c1f2dfd
channel: stable
project_type: app

+ 16
- 0
mobile/README.md View File

@ -0,0 +1,16 @@
# mobile
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

+ 30
- 0
mobile/analysis_options.yaml View File

@ -0,0 +1,30 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
prefer_single_quotes: true
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

+ 13
- 0
mobile/android/.gitignore View File

@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks

+ 68
- 0
mobile/android/app/build.gradle View File

@ -0,0 +1,68 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion flutter.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.mobile"
minSdkVersion 20
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

+ 7
- 0
mobile/android/app/src/debug/AndroidManifest.xml View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mobile">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

+ 35
- 0
mobile/android/app/src/main/AndroidManifest.xml View File

@ -0,0 +1,35 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mobile">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:label="Envelope"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

+ 6
- 0
mobile/android/app/src/main/kotlin/com/example/mobile/MainActivity.kt View File

@ -0,0 +1,6 @@
package com.example.mobile
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

+ 12
- 0
mobile/android/app/src/main/res/drawable-v21/launch_background.xml View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

+ 12
- 0
mobile/android/app/src/main/res/drawable/launch_background.xml View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

BIN
mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png View File

Before After
Width: 72  |  Height: 72  |  Size: 544 B

BIN
mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png View File

Before After
Width: 48  |  Height: 48  |  Size: 442 B

BIN
mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png View File

Before After
Width: 96  |  Height: 96  |  Size: 721 B

BIN
mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png View File

Before After
Width: 144  |  Height: 144  |  Size: 1.0 KiB

BIN
mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png View File

Before After
Width: 192  |  Height: 192  |  Size: 1.4 KiB

+ 18
- 0
mobile/android/app/src/main/res/values-night/styles.xml View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

+ 18
- 0
mobile/android/app/src/main/res/values/styles.xml View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

+ 7
- 0
mobile/android/app/src/profile/AndroidManifest.xml View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mobile">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

+ 31
- 0
mobile/android/build.gradle View File

@ -0,0 +1,31 @@
buildscript {
ext.kotlin_version = '1.6.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}

+ 3
- 0
mobile/android/gradle.properties View File

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

+ 6
- 0
mobile/android/gradle/wrapper/gradle-wrapper.properties View File

@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip

+ 11
- 0
mobile/android/settings.gradle View File

@ -0,0 +1,11 @@
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"

+ 34
- 0
mobile/ios/.gitignore View File

@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

+ 26
- 0
mobile/ios/Flutter/AppFrameworkInfo.plist View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>9.0</string>
</dict>
</plist>

+ 1
- 0
mobile/ios/Flutter/Debug.xcconfig View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

+ 1
- 0
mobile/ios/Flutter/Release.xcconfig View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

+ 481
- 0
mobile/ios/Runner.xcodeproj/project.pbxproj View File

@ -0,0 +1,481 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.mobile;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

+ 7
- 0
mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

+ 8
- 0
mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

+ 8
- 0
mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

+ 87
- 0
mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

+ 7
- 0
mobile/ios/Runner.xcworkspace/contents.xcworkspacedata View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

+ 8
- 0
mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

+ 8
- 0
mobile/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

+ 13
- 0
mobile/ios/Runner/AppDelegate.swift View File

@ -0,0 +1,13 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

+ 122
- 0
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json View File

@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

BIN
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png View File

Before After
Width: 1024  |  Height: 1024  |  Size: 11 KiB

BIN
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png View File

Before After
Width: 20  |  Height: 20  |  Size: 564 B

BIN
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png View File

Before After
Width: 40  |  Height: 40  |  Size: 1.3 KiB

BIN
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png View File

Before After
Width: 60  |  Height: 60  |  Size: 1.6 KiB

BIN
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png View File

Before After
Width: 29  |  Height: 29  |  Size: 1.0 KiB

BIN
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png View File

Before After
Width: 58  |  Height: 58  |  Size: 1.7 KiB

BIN
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png View File

Before After
Width: 87  |  Height: 87  |  Size: 1.9 KiB

BIN
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png View File

Before After
Width: 40  |  Height: 40  |  Size: 1.3 KiB

BIN
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png View File

Before After
Width: 80  |  Height: 80  |  Size: 1.9 KiB

BIN
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png View File

Before After
Width: 120  |  Height: 120  |  Size: 2.6 KiB

BIN
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png View File

Before After
Width: 120  |  Height: 120  |  Size: 2.6 KiB

BIN
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png View File

Before After
Width: 180  |  Height: 180  |  Size: 3.7 KiB

BIN
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png View File

Before After
Width: 76  |  Height: 76  |  Size: 1.8 KiB

BIN
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png View File

Before After
Width: 152  |  Height: 152  |  Size: 3.2 KiB

BIN
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png View File

Before After
Width: 167  |  Height: 167  |  Size: 3.5 KiB

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save