diff --git a/pages/base.html b/pages/base.html index b379873..a61232e 100644 --- a/pages/base.html +++ b/pages/base.html @@ -43,6 +43,9 @@ ENG +
+ +
{{index .Translation "base link log in"}} {{index .Translation "base link sign up"}} diff --git a/pages/profile.html b/pages/profile.html new file mode 100644 index 0000000..df12a98 --- /dev/null +++ b/pages/profile.html @@ -0,0 +1,74 @@ +{{ template "base" . }} + +{{ define "content" }} + +
+
+
+
+
+
+
+ +
+
+
{{ .Data.Email }}
+

{{index .Translation "profile created"}}: {{ .Data.TimeCreated }}

+
+ + +
+
+
+
+
+
+
+
+ + + + + + + +{{ end }} \ No newline at end of file diff --git a/scripts/api.js b/scripts/api.js index 0448f77..daac6bc 100644 --- a/scripts/api.js +++ b/scripts/api.js @@ -70,6 +70,10 @@ async function deleteTodo(id) { return del("/api/todo/delete/"+id); } +async function deleteAccount() { + return del("/api/user/delete"); +} + async function deleteCategory(id) { return del("/api/group/delete/"+id); } diff --git a/src/db/user.go b/src/db/user.go index 3c9a080..2893bdc 100644 --- a/src/db/user.go +++ b/src/db/user.go @@ -25,6 +25,7 @@ type User struct { Email string `json:"email"` Password string `json:"password"` TimeCreatedUnix uint64 `json:"timeCreatedUnix"` + TimeCreated string `json:"timeCreated"` ConfirmedEmail bool `json:"confirmedEmail"` } @@ -36,6 +37,9 @@ func scanUser(rows *sql.Rows) (*User, error) { return nil, err } + // Convert to Basic time string + user.TimeCreated = unixToTimeStr(user.TimeCreatedUnix) + return &user, nil } diff --git a/src/i18n/i18n.go b/src/i18n/i18n.go index 016e832..8320e01 100644 --- a/src/i18n/i18n.go +++ b/src/i18n/i18n.go @@ -13,8 +13,8 @@ func (l *Language) String() string { } const ( - Ru Language = "ru" - Eng Language = "eng" + RU Language = "RU" + ENG Language = "ENG" ) type Translations []*Translation diff --git a/src/i18n/page.go b/src/i18n/page.go index cfe08ae..ebdaaa5 100644 --- a/src/i18n/page.go +++ b/src/i18n/page.go @@ -1,6 +1,7 @@ package i18n import ( + "fmt" "path/filepath" ) @@ -12,5 +13,13 @@ func GetPageTranslation(pageName string, language Language, translationsDirPath return nil, err } + if translation.Language != language { + return translation, fmt.Errorf( + "translation language (%s) differs from what was requested (%s)", + translation.Language, + language, + ) + } + return translation, nil } diff --git a/src/server/endpoints.go b/src/server/endpoints.go index aa1e220..d144368 100644 --- a/src/server/endpoints.go +++ b/src/server/endpoints.go @@ -295,7 +295,7 @@ func (s *Server) EndpointUserUpdate(w http.ResponseWriter, req *http.Request) { } // Check whether the user in request is the user specified in JSON - email := GetLoginFromReq(req) + email := GetEmailFromReq(req) if email != user.Email { // Gotcha! logger.Warning("[Server][EndpointUserUpdate] %s tried to update user information of %s!", email, user.Email) @@ -330,8 +330,8 @@ func (s *Server) EndpointUserDelete(w http.ResponseWriter, req *http.Request) { } // Delete - email := GetLoginFromReq(req) - err := s.db.DeleteUser(email) + email := GetEmailFromReq(req) + err := s.db.DeleteUserClean(email) if err != nil { http.Error(w, "Failed to delete user", http.StatusInternalServerError) logger.Error("[Server][EndpointUserDelete] Failed to delete \"%s\": %s", email, err) @@ -357,7 +357,7 @@ func (s *Server) EndpointUserGet(w http.ResponseWriter, req *http.Request) { } // Get information from the database - email := GetLoginFromReq(req) + email := GetEmailFromReq(req) userDB, err := s.db.GetUser(email) if err != nil { logger.Error("[Server][EndpointUserGet] Failed to retrieve information on \"%s\": %s", email, err) @@ -399,7 +399,7 @@ func (s *Server) EndpointTodoUpdate(w http.ResponseWriter, req *http.Request) { } // Check if the user owns this TODO - if !s.db.DoesUserOwnTodo(todoID, GetLoginFromReq(req)) { + if !s.db.DoesUserOwnTodo(todoID, GetEmailFromReq(req)) { http.Error(w, "You don't own this TODO", http.StatusForbidden) return } @@ -455,7 +455,7 @@ func (s *Server) EndpointTodoMarkDone(w http.ResponseWriter, req *http.Request) } // Check if the user owns this TODO - if !s.db.DoesUserOwnTodo(todoID, GetLoginFromReq(req)) { + if !s.db.DoesUserOwnTodo(todoID, GetEmailFromReq(req)) { http.Error(w, "You don't own this TODO", http.StatusForbidden) return } @@ -504,7 +504,7 @@ func (s *Server) EndpointTodoDelete(w http.ResponseWriter, req *http.Request) { } // Check if the user owns this TODO - if !s.db.DoesUserOwnTodo(todoID, GetLoginFromReq(req)) { + if !s.db.DoesUserOwnTodo(todoID, GetEmailFromReq(req)) { http.Error(w, "You don't own this TODO", http.StatusForbidden) return } @@ -512,7 +512,7 @@ func (s *Server) EndpointTodoDelete(w http.ResponseWriter, req *http.Request) { // Now delete err = s.db.DeleteTodo(todoID) if err != nil { - logger.Error("[Server] Failed to delete %s's TODO: %s", GetLoginFromReq(req), err) + logger.Error("[Server] Failed to delete %s's TODO: %s", GetEmailFromReq(req), err) http.Error(w, "Failed to delete TODO", http.StatusInternalServerError) return } @@ -559,12 +559,12 @@ func (s *Server) EndpointTodoCreate(w http.ResponseWriter, req *http.Request) { return } - if !s.db.DoesUserOwnGroup(newTodo.GroupID, GetLoginFromReq(req)) { + if !s.db.DoesUserOwnGroup(newTodo.GroupID, GetEmailFromReq(req)) { http.Error(w, "You do not own this group", http.StatusForbidden) return } - newTodo.OwnerEmail = GetLoginFromReq(req) + newTodo.OwnerEmail = GetEmailFromReq(req) newTodo.TimeCreatedUnix = uint64(time.Now().Unix()) err = s.db.CreateTodo(newTodo) if err != nil { @@ -596,7 +596,7 @@ func (s *Server) EndpointUserTodosGet(w http.ResponseWriter, req *http.Request) } // Get all user TODOs - todos, err := s.db.GetAllUserTodos(GetLoginFromReq(req)) + todos, err := s.db.GetAllUserTodos(GetEmailFromReq(req)) if err != nil { http.Error(w, "Failed to get TODOs", http.StatusInternalServerError) return @@ -636,7 +636,7 @@ func (s *Server) EndpointTodoGroupDelete(w http.ResponseWriter, req *http.Reques return } - if !s.db.DoesUserOwnGroup(groupId, GetLoginFromReq(req)) { + if !s.db.DoesUserOwnGroup(groupId, GetEmailFromReq(req)) { http.Error(w, "You don't own this group", http.StatusForbidden) return } @@ -657,13 +657,13 @@ func (s *Server) EndpointTodoGroupDelete(w http.ResponseWriter, req *http.Reques // Delete all ToDos associated with this group and then delete the group itself err = s.db.DeleteTodoGroupClean(groupId) if err != nil { - logger.Error("[Server][EndpointGroupDelete] Failed to delete %s's TODO group: %s", GetLoginFromReq(req), err) + logger.Error("[Server][EndpointGroupDelete] Failed to delete %s's TODO group: %s", GetEmailFromReq(req), err) http.Error(w, "Failed to delete TODO group", http.StatusInternalServerError) return } // Success! - logger.Info("[Server][EndpointGroupDelete] Cleanly deleted group ID: %d for %s", groupId, GetLoginFromReq(req)) + logger.Info("[Server][EndpointGroupDelete] Cleanly deleted group ID: %d for %s", groupId, GetEmailFromReq(req)) w.WriteHeader(http.StatusOK) } @@ -700,7 +700,7 @@ func (s *Server) EndpointTodoGroupCreate(w http.ResponseWriter, req *http.Reques } // Add group to the database - newGroup.OwnerEmail = GetLoginFromReq(req) + newGroup.OwnerEmail = GetEmailFromReq(req) newGroup.TimeCreatedUnix = uint64(time.Now().Unix()) newGroup.Removable = true err = s.db.CreateTodoGroup(newGroup) @@ -725,7 +725,7 @@ func (s *Server) EndpointTodoGroupGet(w http.ResponseWriter, req *http.Request) } // Get groups - groups, err := s.db.GetAllUserTodoGroups(GetLoginFromReq(req)) + groups, err := s.db.GetAllUserTodoGroups(GetEmailFromReq(req)) if err != nil { http.Error(w, "Failed to get TODO groups", http.StatusInternalServerError) return diff --git a/src/server/page.go b/src/server/page.go index a1b9064..3e49e99 100644 --- a/src/server/page.go +++ b/src/server/page.go @@ -37,7 +37,7 @@ func (s *Server) GetPageData(templateNames []string, language i18n.Language) (*P pageTranslation, err := i18n.GetPageTranslation(page, language, translationsDirPath) if err != nil { // Try ENG - pageTranslation, err = i18n.GetPageTranslation(page, i18n.Eng, translationsDirPath) + pageTranslation, err = i18n.GetPageTranslation(page, i18n.ENG, translationsDirPath) if err != nil { return nil, err } diff --git a/src/server/server.go b/src/server/server.go index 6c2b84e..bd57170 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -146,7 +146,7 @@ func New(config conf.Conf) (*Server, error) { return } - indexPageData, err := GetIndexPageData(server.db, GetLoginFromReq(req)) + indexPageData, err := GetIndexPageData(server.db, GetEmailFromReq(req)) if err != nil { http.Redirect(w, req, "/error", http.StatusTemporaryRedirect) logger.Error("[Server][/] Failed to get index page data: %s", err) @@ -205,7 +205,7 @@ func New(config conf.Conf) (*Server, error) { return } - categoriesData, err := GetCategoryPageData(server.db, GetLoginFromReq(req), groupId) + categoriesData, err := GetCategoryPageData(server.db, GetEmailFromReq(req), groupId) if err != nil { http.Redirect(w, req, "/error", http.StatusTemporaryRedirect) logger.Error("[Server][/category/] Failed to get category (%d) page data: %s", groupId, err) @@ -220,6 +220,50 @@ func New(config conf.Conf) (*Server, error) { return } + } else if req.URL.Path == "/profile" { + if req.Method != "GET" { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // Auth first + if !IsUserAuthorizedReq(req, server.db) { + http.Redirect(w, req, "/about", http.StatusTemporaryRedirect) + return + } + + email := GetEmailFromReq(req) + user, err := server.db.GetUser(email) + if err != nil { + // TODO + return + } + user.Password = "" // No passwords sent + + pageData, err := server.GetPageData([]string{"profile", "base"}, LanguageFromReq(req)) + if err != nil { + // TODO + return + } + pageData.Data = user + + requestedPage, err := template.ParseFiles( + filepath.Join(pagesDirPath, "base.html"), + filepath.Join(pagesDirPath, "profile.html"), + ) + if err != nil { + http.Redirect(w, req, "/error", http.StatusTemporaryRedirect) + logger.Error("[Server][/profile] Failed to get a page: %s", err) + return + } + + err = requestedPage.ExecuteTemplate(w, "profile.html", &pageData) + if err != nil { + http.Redirect(w, req, "/error", http.StatusTemporaryRedirect) + logger.Error("[Server][/profile] Template error: %s", err) + return + } + } else { // default requestedPage, err := template.ParseFiles( diff --git a/src/server/validation.go b/src/server/validation.go index f3c24ff..a540b83 100644 --- a/src/server/validation.go +++ b/src/server/validation.go @@ -112,7 +112,7 @@ func IsUserAuthorizedReq(req *http.Request, dbase *db.DB) bool { } // Returns email value from basic auth or from cookie if the former does not exist -func GetLoginFromReq(req *http.Request) string { +func GetEmailFromReq(req *http.Request) string { email, _, ok := req.BasicAuth() if !ok || email == "" { cookie, err := req.Cookie("auth") @@ -154,14 +154,14 @@ func LocaleFromReq(req *http.Request) string { } func LanguageFromReq(req *http.Request) i18n.Language { - switch LocaleFromReq(req) { + switch strings.ToUpper(LocaleFromReq(req)) { case "ENG": - return i18n.Eng + return i18n.ENG case "RU": - return i18n.Ru + return i18n.RU default: - return i18n.Eng + return i18n.ENG } } diff --git a/static/images/person-vcard.svg b/static/images/person-vcard.svg new file mode 100644 index 0000000..40ec41e --- /dev/null +++ b/static/images/person-vcard.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/translations/eng/about.json b/translations/ENG/about.json similarity index 100% rename from translations/eng/about.json rename to translations/ENG/about.json diff --git a/translations/eng/base.json b/translations/ENG/base.json similarity index 100% rename from translations/eng/base.json rename to translations/ENG/base.json diff --git a/translations/eng/category.json b/translations/ENG/category.json similarity index 100% rename from translations/eng/category.json rename to translations/ENG/category.json diff --git a/translations/eng/error.json b/translations/ENG/error.json similarity index 100% rename from translations/eng/error.json rename to translations/ENG/error.json diff --git a/translations/eng/index.json b/translations/ENG/index.json similarity index 100% rename from translations/eng/index.json rename to translations/ENG/index.json diff --git a/translations/eng/login.json b/translations/ENG/login.json similarity index 100% rename from translations/eng/login.json rename to translations/ENG/login.json diff --git a/translations/ENG/paint.json b/translations/ENG/paint.json new file mode 100644 index 0000000..bb8636e --- /dev/null +++ b/translations/ENG/paint.json @@ -0,0 +1,3 @@ +{ + "language": "ENG" +} \ No newline at end of file diff --git a/translations/ENG/profile.json b/translations/ENG/profile.json new file mode 100644 index 0000000..f6e7e59 --- /dev/null +++ b/translations/ENG/profile.json @@ -0,0 +1,40 @@ +{ + "language": "ENG", + "messages": [ + { + "id": "profile log out", + "message": "Log Out", + "translation": "Log Out" + }, + { + "id": "profile created", + "message": "Created", + "translation": "Created" + }, + { + "id": "profile delete account", + "message": "Delete Account", + "translation": "Delete Account" + }, + { + "id": "profile modal confirm deletion", + "message": "Confirm Deletion", + "translation": "Confirm Deletion" + }, + { + "id": "profile modal deletion confirmation", + "message": "Are you sure you want to delete your account?", + "translation": "Are you sure you want to delete your account?" + }, + { + "id": "profile modal delete button", + "message": "Delete", + "translation": "Delete" + }, + { + "id": "profile modal cancel button", + "message": "Cancel", + "translation": "Cancel" + } + ] +} \ No newline at end of file diff --git a/translations/eng/register.json b/translations/ENG/register.json similarity index 100% rename from translations/eng/register.json rename to translations/ENG/register.json diff --git a/translations/ru/about.json b/translations/RU/about.json similarity index 96% rename from translations/ru/about.json rename to translations/RU/about.json index b17ded6..e7ad8d6 100644 --- a/translations/ru/about.json +++ b/translations/RU/about.json @@ -1,5 +1,5 @@ { - "language": "ENG", + "language": "RU", "messages": [ { "id": "about info", diff --git a/translations/ru/base.json b/translations/RU/base.json similarity index 96% rename from translations/ru/base.json rename to translations/RU/base.json index 0a9e006..e3f69d2 100644 --- a/translations/ru/base.json +++ b/translations/RU/base.json @@ -1,5 +1,5 @@ { - "language": "ENG", + "language": "RU", "messages": [ { "id": "base link main", diff --git a/translations/ru/category.json b/translations/RU/category.json similarity index 99% rename from translations/ru/category.json rename to translations/RU/category.json index 3a35f1b..60c3c72 100644 --- a/translations/ru/category.json +++ b/translations/RU/category.json @@ -1,5 +1,5 @@ { - "language": "ENG", + "language": "RU", "messages": [ { "id": "category modal confirm deletion", diff --git a/translations/ru/error.json b/translations/RU/error.json similarity index 96% rename from translations/ru/error.json rename to translations/RU/error.json index f33f55e..876fb77 100644 --- a/translations/ru/error.json +++ b/translations/RU/error.json @@ -1,5 +1,5 @@ { - "language": "ENG", + "language": "RU", "messages": [ { "id": "error error big", diff --git a/translations/ru/index.json b/translations/RU/index.json similarity index 97% rename from translations/ru/index.json rename to translations/RU/index.json index 76dd61e..ccacf36 100644 --- a/translations/ru/index.json +++ b/translations/RU/index.json @@ -1,5 +1,5 @@ { - "language": "ENG", + "language": "RU", "messages": [ { "id": "index categories", diff --git a/translations/ru/login.json b/translations/RU/login.json similarity index 92% rename from translations/ru/login.json rename to translations/RU/login.json index 2bcc0b6..a8c7b57 100644 --- a/translations/ru/login.json +++ b/translations/RU/login.json @@ -1,5 +1,5 @@ { - "language": "ENG", + "language": "RU", "messages": [ { "id": "login main", diff --git a/translations/RU/paint.json b/translations/RU/paint.json new file mode 100644 index 0000000..2b1ccad --- /dev/null +++ b/translations/RU/paint.json @@ -0,0 +1,3 @@ +{ + "language": "RU" +} \ No newline at end of file diff --git a/translations/RU/profile.json b/translations/RU/profile.json new file mode 100644 index 0000000..77a5052 --- /dev/null +++ b/translations/RU/profile.json @@ -0,0 +1,40 @@ +{ + "language": "RU", + "messages": [ + { + "id": "profile log out", + "message": "Log Out", + "translation": "Выйти" + }, + { + "id": "profile delete account", + "message": "Delete Account", + "translation": "Удалить Аккаунт" + }, + { + "id": "profile created", + "message": "Created", + "translation": "Создан" + }, + { + "id": "profile modal confirm deletion", + "message": "Confirm Deletion", + "translation": "Подтверждение удаления" + }, + { + "id": "profile modal deletion confirmation", + "message": "Are you sure you want to delete your account?", + "translation": "Вы действительно хотите удалить свой аккаунт?" + }, + { + "id": "profile modal delete button", + "message": "Delete", + "translation": "Удалить" + }, + { + "id": "profile modal cancel button", + "message": "Cancel", + "translation": "Отменить" + } + ] +} \ No newline at end of file diff --git a/translations/ru/register.json b/translations/RU/register.json similarity index 98% rename from translations/ru/register.json rename to translations/RU/register.json index ea44b70..fd8aeff 100644 --- a/translations/ru/register.json +++ b/translations/RU/register.json @@ -1,5 +1,5 @@ { - "language": "ENG", + "language": "RU", "messages": [ { "id": "register main", diff --git a/translations/eng/paint.json b/translations/eng/paint.json deleted file mode 100644 index 9e26dfe..0000000 --- a/translations/eng/paint.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/translations/ru/paint.json b/translations/ru/paint.json deleted file mode 100644 index 9e26dfe..0000000 --- a/translations/ru/paint.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file