1
0
Fork 0
forked from goffee/todoapp

first commit

This commit is contained in:
Zeni Kim 2024-09-15 13:36:50 -05:00
parent 968575e907
commit 5d67da6b1c
32 changed files with 1319 additions and 0 deletions

390
handlers/authentication.go Normal file
View file

@ -0,0 +1,390 @@
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
// Use of this source code is governed by MIT-style
// license that can be found in the LICENSE file.
package handlers
import (
"encoding/json"
"errors"
"net/http"
"strconv"
"strings"
"time"
"git.smarteching.com/goffee/core"
"git.smarteching.com/goffee/todoapp/events"
"git.smarteching.com/goffee/todoapp/models"
"git.smarteching.com/goffee/todoapp/utils"
"github.com/google/uuid"
"gorm.io/gorm"
)
func Signup(c *core.Context) *core.Response {
name := c.GetRequestParam("name")
email := c.GetRequestParam("email")
password := c.GetRequestParam("password")
// check if email exists
var user models.User
res := c.GetGorm().Where("email = ?", c.CastToString(email)).First(&user)
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
c.GetLogger().Error(res.Error.Error())
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": "internal error",
}))
}
if res.Error == nil {
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "email already exists in the database",
}))
}
// validation data
data := map[string]interface{}{
"name": name,
"email": email,
"password": password,
}
// validation rules
rules := map[string]interface{}{
"name": "required|alphaNumeric",
"email": "required|email",
"password": "required|length:6,10",
}
// validate
v := c.GetValidator().Validate(data, rules)
if v.Failed() {
c.GetLogger().Error(v.GetErrorMessagesJson())
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(v.GetErrorMessagesJson())
}
//hash the password
passwordHashed, err := c.GetHashing().HashPassword(c.CastToString(password))
if err != nil {
c.GetLogger().Error(err.Error())
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]interface{}{
"message": err.Error(),
}))
}
// store the record in db
user = models.User{
Name: c.CastToString(name),
Email: c.CastToString(email),
Password: passwordHashed,
}
res = c.GetGorm().Create(&user)
if res.Error != nil {
c.GetLogger().Error(res.Error.Error())
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": res.Error.Error(),
}))
}
token, err := c.GetJWT().GenerateToken(map[string]interface{}{
"userID": user.ID,
})
if err != nil {
c.GetLogger().Error(err.Error())
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": "internal server error",
}))
}
// cache the token
userAgent := c.GetUserAgent()
hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(user.ID, userAgent)
err = c.GetCache().Set(hashedCacheKey, token)
if err != nil {
c.GetLogger().Error(err.Error())
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{
"message": "internal server error",
}))
}
// fire user registered event
err = c.GetEventsManager().Fire(&core.Event{Name: events.USER_REGISTERED, Payload: map[string]interface{}{
"user": user,
}})
if err != nil {
c.GetLogger().Error(err.Error())
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": "internal server error",
}))
}
return c.Response.Json(c.MapToJson(map[string]string{
"token": token,
}))
}
func Signin(c *core.Context) *core.Response {
email := c.GetRequestParam("email")
password := c.GetRequestParam("password")
data := map[string]interface{}{
"email": email,
"password": password,
}
rules := map[string]interface{}{
"email": "required|email",
"password": "required",
}
v := c.GetValidator().Validate(data, rules)
if v.Failed() {
c.GetLogger().Error(v.GetErrorMessagesJson())
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(v.GetErrorMessagesJson())
}
var user models.User
res := c.GetGorm().Where("email = ?", c.CastToString(email)).First(&user)
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
c.GetLogger().Error(res.Error.Error())
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": "internal server error",
}))
}
if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "invalid email or password",
}))
}
ok, err := c.GetHashing().CheckPasswordHash(user.Password, c.CastToString(password))
if err != nil {
c.GetLogger().Error(err.Error())
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": err.Error(),
}))
}
if !ok {
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "invalid email or password",
}))
}
token, err := c.GetJWT().GenerateToken(map[string]interface{}{
"userID": user.ID,
})
if err != nil {
c.GetLogger().Error(err.Error())
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": "internal server error",
}))
}
// cache the token
userAgent := c.GetUserAgent()
hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(user.ID, userAgent)
err = c.GetCache().Set(hashedCacheKey, token)
if err != nil {
c.GetLogger().Error(err.Error())
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{
"message": "internal server error",
}))
}
return c.Response.Json(c.MapToJson(map[string]string{
"token": token,
}))
}
func ResetPasswordRequest(c *core.Context) *core.Response {
email := c.GetRequestParam("email")
// validation data
data := map[string]interface{}{
"email": email,
}
// validation rules
rules := map[string]interface{}{
"email": "required|email",
}
// validate
v := c.GetValidator().Validate(data, rules)
if v.Failed() {
c.GetLogger().Error(v.GetErrorMessagesJson())
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(v.GetErrorMessagesJson())
}
// check email in the database
var user models.User
res := c.GetGorm().Where("email = ?", c.CastToString(email)).First(&user)
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
c.GetLogger().Error(res.Error.Error())
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": "internal server error",
}))
}
if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "email not found in our database",
}))
}
// generate the link
expiresAt := time.Now().Add(time.Hour * 3).Unix()
linkCodeData := map[string]string{
"userID": c.CastToString(user.ID),
"expiresAt": c.CastToString(expiresAt),
}
code := uuid.NewString()
c.GetCache().SetWithExpiration(code, c.MapToJson(linkCodeData), time.Hour*3)
err := c.GetEventsManager().Fire(&core.Event{Name: events.USER_PASSWORD_RESET_REQUESTED, Payload: map[string]interface{}{
"user": user,
"code": code,
}})
if err != nil {
c.GetLogger().Error(err.Error())
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": "internal server error",
}))
}
return c.Response.Json(c.MapToJson(map[string]string{
"message": "reset password email sent successfully",
}))
}
func SetNewPassword(c *core.Context) *core.Response {
urlCode := c.CastToString(c.GetPathParam("code"))
linkCodeDataStr, err := c.GetCache().Get(urlCode)
if err != nil {
c.GetLogger().Error(err.Error())
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "invalid link",
}))
}
var linkCode map[string]interface{}
json.Unmarshal([]byte(linkCodeDataStr), &linkCode)
expiresAtUnix, err := strconv.ParseInt(c.CastToString(linkCode["expiresAt"]), 10, 64)
if err != nil {
c.GetLogger().Error(err.Error())
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "invalid link",
}))
}
expiresAt := time.Unix(expiresAtUnix, 0)
if time.Now().After(expiresAt) {
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "invalid link",
}))
}
userID, err := strconv.ParseUint(c.CastToString(linkCode["userID"]), 10, 64)
if err != nil {
c.GetLogger().Error(err.Error())
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "invalid link",
}))
}
oldPassword := c.CastToString(c.GetRequestParam("old_password"))
newPassword := c.CastToString(c.GetRequestParam("new_password"))
newPasswordConfirm := c.CastToString(c.GetRequestParam("new_password_confirm"))
// validation data
data := map[string]interface{}{
"old_password": oldPassword,
"new_password": newPassword,
"new_password_confirm": newPasswordConfirm,
}
// validation rules
rules := map[string]interface{}{
"old_password": "required|length:6,10",
"new_password": "required|length:6,10",
"new_password_confirm": "required|length:6,10",
}
// validate
v := c.GetValidator().Validate(data, rules)
if v.Failed() {
c.GetLogger().Error(v.GetErrorMessagesJson())
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(v.GetErrorMessagesJson())
}
var user models.User
res := c.GetGorm().Where("id = ?", userID).First(&user)
if res.Error != nil {
c.GetLogger().Error(res.Error.Error())
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "invalid link",
}))
}
SamePassword, err := c.GetHashing().CheckPasswordHash(user.Password, oldPassword)
if err != nil {
c.GetLogger().Error(err.Error())
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "invalid link",
}))
}
if !SamePassword {
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "the old password is incorrect",
}))
}
if newPassword != newPasswordConfirm {
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "new password does not match new password confirmation",
}))
}
hashedNewPassword, err := c.GetHashing().HashPassword(newPassword)
if err != nil {
c.GetLogger().Error(err.Error())
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "invalid password",
}))
}
user.Password = hashedNewPassword
c.GetGorm().Save(&user)
err = c.GetEventsManager().Fire(&core.Event{Name: events.PASSWORD_CHANGED, Payload: map[string]interface{}{
"user": user,
}})
if err != nil {
c.GetLogger().Error(err.Error())
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": "internal server error",
}))
}
return c.Response.Json(c.MapToJson(map[string]string{
"message": "password changed successfully",
}))
}
func Signout(c *core.Context) *core.Response {
tokenRaw := c.GetHeader("Authorization")
token := strings.TrimSpace(strings.Replace(tokenRaw, "Bearer", "", 1))
if token == "" {
return c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
"message": "unauthorized",
}))
}
payload, err := c.GetJWT().DecodeToken(token)
if err != nil {
return c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
"message": "unauthorized",
}))
}
userAgent := c.GetUserAgent()
hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["userID"])), userAgent)
err = c.GetCache().Delete(hashedCacheKey)
if err != nil {
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{
"message": "internal error",
}))
}
return c.Response.SetStatusCode(http.StatusOK).Json(c.MapToJson(map[string]interface{}{
"message": "signed out successfully",
}))
}

21
handlers/home.go Normal file
View file

@ -0,0 +1,21 @@
// Copyright 2023 Harran Ali <harran.m@gmail.com>. All rights reserved.
// Use of this source code is governed by MIT-style
// license that can be found in the LICENSE file.
package handlers
import (
"git.smarteching.com/goffee/core"
)
// Show home page
func WelcomeHome(c *core.Context) *core.Response {
message := "{\"message\": \"Welcome to GoCondor\"}"
return c.Response.Json(message)
}
// Show dashboard
func WelcomeToDashboard(c *core.Context) *core.Response {
message := "{\"message\": \"Welcome to Dashboard\"}"
return c.Response.Json(message)
}

131
handlers/todos.go Normal file
View file

@ -0,0 +1,131 @@
package handlers
import (
"encoding/json"
"strconv"
"git.smarteching.com/goffee/core"
"git.smarteching.com/goffee/todoapp/models"
)
func ListTodos(c *core.Context) *core.Response {
var todos []models.Todo
result := c.GetGorm().Find(&todos)
if result.Error != nil {
return c.Response.SetStatusCode(500).Json(c.MapToJson(map[string]string{"message": result.Error.Error()}))
}
todosJson, err := json.Marshal(todos)
if err != nil {
return c.Response.Json(c.MapToJson(map[string]string{"message": err.Error()}))
}
return c.Response.Json(string(todosJson))
}
func CreateTodos(c *core.Context) *core.Response {
title := c.CastToString(c.GetRequestParam("title"))
body := c.CastToString(c.GetRequestParam("body"))
isDone := c.CastToString(c.GetRequestParam("isDone"))
v := c.GetValidator().Validate(map[string]interface{}{
"title": title,
"body": body,
"isDone": isDone,
}, map[string]interface{}{
"title": "required",
"body": "required",
})
if v.Failed() {
return c.Response.Json(v.GetErrorMessagesJson())
}
result := c.GetGorm().Create(&models.Todo{
Title: title,
Body: body,
IsDone: false,
})
if result.Error != nil {
return c.Response.SetStatusCode(500).Json(c.MapToJson(map[string]string{
"message": result.Error.Error(),
}))
}
return c.Response.Json(c.MapToJson(map[string]string{
"message": "created successfully",
}))
}
func ShowTodo(c *core.Context) *core.Response {
todoID := c.CastToString(c.GetPathParam("id"))
var todo models.Todo
result := c.GetGorm().First(&todo, todoID)
if result.Error != nil {
return c.Response.SetStatusCode(500).Json(c.MapToJson(map[string]string{"message": result.Error.Error()}))
}
todoJson, err := json.Marshal(todo)
if err != nil {
return c.Response.Json(c.MapToJson(map[string]string{"message": err.Error()}))
}
return c.Response.Json(string(todoJson))
}
func DeleteTodo(c *core.Context) *core.Response {
todoID := c.CastToString(c.GetPathParam("id"))
var todo models.Todo
result := c.GetGorm().Delete(&todo, todoID)
if result.Error != nil {
return c.Response.SetStatusCode(500).Json(c.MapToJson(map[string]string{"message": result.Error.Error()}))
}
return c.Response.Json(c.MapToJson(map[string]string{"message": "record deleted successfully"}))
}
func UpdateTodo(c *core.Context) *core.Response {
var title string = ""
var body string = ""
var data map[string]interface{} = map[string]interface{}{}
var rules map[string]interface{} = map[string]interface{}{}
todoID := c.GetPathParam("id")
var todo models.Todo
result := c.GetGorm().First(&todo, todoID)
if result.Error != nil {
return c.Response.Json(c.MapToJson(map[string]string{"message": result.Error.Error()}))
}
if c.RequestParamExists("title") {
title = c.CastToString(c.GetRequestParam("title"))
data["title"] = title
}
if c.RequestParamExists("body") {
body = c.CastToString(c.GetRequestParam("body"))
data["body"] = body
}
if c.RequestParamExists("isDone") {
isDoneStr := c.CastToString(c.GetRequestParam("isDone"))
data["isDone"] = isDoneStr
rules["isDone"] = "in:true,false"
}
v := c.GetValidator().Validate(data, rules)
if v.Failed() {
return c.Response.Json(v.GetErrorMessagesJson())
}
if c.RequestParamExists("title") {
todo.Title = title
}
if c.RequestParamExists("body") {
todo.Body = body
}
if c.RequestParamExists("isDone") {
isDoneStr := c.CastToString(c.GetRequestParam("isDone"))
isDone, err := strconv.ParseBool(isDoneStr)
if err != nil {
return c.Response.Json(c.MapToJson(map[string]string{"message": err.Error()}))
}
todo.IsDone = isDone
}
c.GetGorm().Save(&todo)
todoJson, err := json.Marshal(todo)
if err != nil {
return c.Response.Json(c.MapToJson(map[string]string{"message": err.Error()}))
}
return c.Response.Json(string(todoJson))
}