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 }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{index .Translation "profile modal deletion confirmation"}}
+
+
+
+
+
+
+
+
+
+{{ 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