├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── dbconfig └── configdb.go ├── go.mod ├── go.sum ├── http.http ├── internal ├── auth │ └── jwtMiddleware.go ├── dto │ ├── dtoTodo.go │ └── dtoUser.go ├── handlers │ ├── restTodo.go │ ├── restUser.go │ ├── todoErrorsHandler.go │ └── userErrorsHandler.go ├── models │ ├── todo.go │ └── user.go └── utils │ └── response.go └── main.go /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Debora Leda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # API em Golang de TODO list 2 | 3 | CRUD de TODO com criação de usuário e autenticação usando JWT. 4 | O banco de dados usado é o postgres, usando a ferramenta Neon DB. 5 | 6 | Substitua o valor da URL do seu banco de dados no arquivo .env, um exemplo dele pode ser encontrado em [.env.example](.env.example). 7 | 8 | Exemplos de uso das rotas podem ser encontrados em [http.http](http.http). 9 | -------------------------------------------------------------------------------- /dbconfig/configdb.go: -------------------------------------------------------------------------------- 1 | package dbconfig 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/joho/godotenv" 9 | _ "github.com/lib/pq" 10 | ) 11 | 12 | func loadEnvFile() error { 13 | return godotenv.Load() 14 | } 15 | 16 | var DB *sql.DB 17 | var err error 18 | 19 | func ConfigDB() { 20 | loadEnvFile() 21 | databaseURL := os.Getenv("DATABASE_URL") 22 | connStr := databaseURL 23 | DB, err = sql.Open("postgres", connStr) 24 | 25 | if err != nil { 26 | panic(err.Error()) 27 | } else { 28 | fmt.Println("Connected!") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module todos-api 2 | 3 | go 1.21.3 4 | 5 | require ( 6 | github.com/go-chi/chi/v5 v5.0.10 // indirect 7 | github.com/golang-jwt/jwt/v4 v4.5.0 // indirect 8 | github.com/joho/godotenv v1.5.1 // indirect 9 | github.com/lib/pq v1.10.9 // indirect 10 | golang.org/x/crypto v0.15.0 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= 2 | github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= 3 | github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= 4 | github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 5 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 6 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 7 | github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= 8 | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 9 | golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= 10 | golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= 11 | -------------------------------------------------------------------------------- /http.http: -------------------------------------------------------------------------------- 1 | ## User rest 2 | ### POST user 3 | POST http://localhost:8080/user 4 | Content-Type: application/json 5 | 6 | { 7 | "username": "nome", 8 | "password": "password" 9 | } 10 | 11 | ### POST login 12 | # @name login 13 | POST http://localhost:8080/login 14 | Content-Type: application/json 15 | 16 | { 17 | "username": "nome", 18 | "password": "password" 19 | } 20 | 21 | ## após fazer o request de login, substitua a variável abaixo com o valor do token retornado 22 | ### 23 | @access_token = {{login.response.body.*}} 24 | 25 | ## Todo-list rest 26 | # POST todo-list 27 | POST http://localhost:8080/ 28 | Content-Type: application/json 29 | Authorization: bearer {{access_token}} 30 | 31 | { 32 | "title": "atividades do dia 2", 33 | "description": "lavar cassa, fazer o almoco", 34 | "done": false 35 | } 36 | 37 | ### 38 | # GET /all/todo-list 39 | GET http://localhost:8080/ 40 | Authorization: bearer {{access_token}} 41 | 42 | ### 43 | # GET /todo-list/id 44 | GET http://localhost:8080/8 45 | Authorization: bearer {{access_token}} 46 | 47 | 48 | ### PUT /todo-list/id 49 | PUT http://localhost:8080/8 50 | Content-Type: application/json 51 | Authorization: bearer {{access_token}} 52 | 53 | { 54 | "title": "atividades do dia", 55 | "description": "fazer atividade de ingles", 56 | "done": true 57 | } 58 | 59 | ### DELETE /todo-list/id 60 | DELETE http://localhost:8080/8 61 | Authorization: bearer {{access_token}} 62 | 63 | -------------------------------------------------------------------------------- /internal/auth/jwtMiddleware.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/golang-jwt/jwt/v4" 10 | ) 11 | 12 | type Claims struct { 13 | Username string `json:"username"` 14 | Id int64 `json:"id"` 15 | jwt.RegisteredClaims 16 | } 17 | 18 | var jwtKey = []byte("my_secret_key") 19 | 20 | func MyMiddleware(next http.Handler) http.Handler { 21 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 22 | 23 | bearerToken := r.Header.Get("Authorization") 24 | if bearerToken == "" { 25 | http.Error(w, `{"message":"unauthorized"}`, http.StatusUnauthorized) 26 | return 27 | } 28 | reqToken := strings.Split(bearerToken, " ")[1] 29 | claims := &Claims{} 30 | tkn, err := jwt.ParseWithClaims(reqToken, claims, func(token *jwt.Token) (interface{}, error) { 31 | return jwtKey, nil 32 | }) 33 | log.Printf("%v - %v ", tkn, claims) 34 | if err != nil { 35 | if err == jwt.ErrSignatureInvalid { 36 | http.Error(w, `{"message":"unauthorized"}`, http.StatusUnauthorized) 37 | return 38 | } 39 | http.Error(w, `{"message":"bad request"}`, http.StatusBadRequest) 40 | return 41 | } 42 | if !tkn.Valid { 43 | http.Error(w, `{"message":"Invalid token"}`, http.StatusForbidden) 44 | return 45 | } 46 | log.Printf("decoded completed!") 47 | 48 | ctx := context.WithValue(r.Context(), "username", claims.Username) 49 | ctx = context.WithValue(ctx, "userId", claims.Id) 50 | 51 | next.ServeHTTP(w, r.WithContext(ctx)) 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /internal/dto/dtoTodo.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io" 7 | ) 8 | 9 | type TodoRequest struct { 10 | Title string `json:"title"` 11 | Description string `json:"description"` 12 | Done *bool `json:"done"` 13 | } 14 | 15 | func FromJsonCreateTodoRequest(body io.ReadCloser) (*TodoRequest, error) { 16 | todoRequest := TodoRequest{} 17 | if err := json.NewDecoder(body).Decode(&todoRequest); err != nil { 18 | return nil, err 19 | } 20 | 21 | if len(todoRequest.Title) < 3 { 22 | return nil, errors.New("titulo deve ter mais de 3 letras") 23 | } 24 | 25 | if len(todoRequest.Description) < 3 { 26 | return nil, errors.New("Description deve ter mais de 3 letras") 27 | } 28 | 29 | if todoRequest.Done == nil { 30 | return nil, errors.New("Deve passar a variavel done") 31 | } 32 | 33 | return &todoRequest, nil 34 | 35 | } 36 | -------------------------------------------------------------------------------- /internal/dto/dtoUser.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io" 7 | ) 8 | 9 | type UserRequest struct { 10 | Username string `json:"username"` 11 | Password string `json:"password"` 12 | } 13 | 14 | func FromJsonCreateUserRequest(body io.ReadCloser) (*UserRequest, error) { 15 | userRequest := UserRequest{} 16 | if err := json.NewDecoder(body).Decode(&userRequest); err != nil { 17 | return nil, err 18 | } 19 | 20 | if len(userRequest.Username) < 3 { 21 | return nil, errors.New("Username deve ter mais de 3 letras") 22 | } 23 | 24 | if len(userRequest.Password) < 5 { 25 | return nil, errors.New("Password deve ter mais de 5 letras") 26 | } 27 | 28 | return &userRequest, nil 29 | 30 | } 31 | -------------------------------------------------------------------------------- /internal/handlers/restTodo.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "todos-api/internal/models" 8 | "todos-api/internal/utils" 9 | ) 10 | 11 | func List(w http.ResponseWriter, r *http.Request) { 12 | userId, err := RecoverUserIdFromContext(r) 13 | if err != nil { 14 | utils.GenerateErrorResponse(w, fmt.Sprintf("%v", err), 500) 15 | return 16 | } 17 | 18 | todos, err := models.GetAllFromUser(*userId) 19 | 20 | if err != nil { 21 | errorMessage := ("Erro ao obter registros" + fmt.Sprintf("%v", err)) 22 | log.Printf(errorMessage) 23 | utils.GenerateErrorResponse(w, errorMessage, 400) 24 | return 25 | } 26 | 27 | utils.GenerateResponse(w, todos, 200) 28 | } 29 | 30 | func Update(w http.ResponseWriter, r *http.Request) { 31 | userId, err := RecoverUserIdFromContext(r) 32 | if err != nil { 33 | utils.GenerateErrorResponse(w, fmt.Sprintf("%v", err), 500) 34 | return 35 | } 36 | 37 | id, err := RecoverTodoIdFromURL(r) 38 | 39 | if err != nil { 40 | utils.GenerateErrorResponse(w, fmt.Sprintf("%v", err), 500) 41 | return 42 | } 43 | 44 | todoRequest, err := RecoverJsonTodoRequest(r) 45 | if err != nil { 46 | utils.GenerateErrorResponse(w, fmt.Sprintf("%v", err), 400) 47 | return 48 | } 49 | 50 | todo := models.Todo{Title: todoRequest.Title, Description: todoRequest.Description, Done: *todoRequest.Done, User_id: *userId} 51 | 52 | rows, err := models.Update(int64(*id), todo) 53 | 54 | if err != nil { 55 | errorMessage := ("erro ao atualizar registro") 56 | log.Printf(errorMessage) 57 | utils.GenerateErrorResponse(w, errorMessage, 400) 58 | return 59 | } 60 | 61 | if rows > 1 { 62 | log.Printf("Erro: atualizou número %d de registros", rows) 63 | } 64 | 65 | utils.GenerateResponse(w, nil, 200) 66 | 67 | } 68 | 69 | func Get(w http.ResponseWriter, r *http.Request) { 70 | id, err := RecoverTodoIdFromURL(r) 71 | 72 | if err != nil { 73 | utils.GenerateErrorResponse(w, fmt.Sprintf("%v", err), 500) 74 | return 75 | } 76 | 77 | todo, err := models.Get(int64(*id)) 78 | 79 | if err != nil { 80 | errorMessage := ("erro ao recuperar o registro " + fmt.Sprintf("%v", err)) 81 | log.Printf(errorMessage) 82 | utils.GenerateErrorResponse(w, errorMessage, 400) 83 | return 84 | } 85 | 86 | utils.GenerateResponse(w, todo, 200) 87 | 88 | } 89 | 90 | func Delete(w http.ResponseWriter, r *http.Request) { 91 | id, err := RecoverTodoIdFromURL(r) 92 | 93 | if err != nil { 94 | utils.GenerateErrorResponse(w, fmt.Sprintf("%v", err), 500) 95 | return 96 | } 97 | 98 | rows, err := models.Delete(int64(*id)) 99 | 100 | if err != nil { 101 | errorMessage := ("erro ao deletar registro") 102 | log.Printf(errorMessage) 103 | utils.GenerateErrorResponse(w, errorMessage, 400) 104 | return 105 | } 106 | 107 | if rows > 1 { 108 | log.Printf("Erro: foram removidos %d de registros", rows) 109 | } 110 | 111 | utils.GenerateResponse(w, nil, 200) 112 | 113 | } 114 | 115 | func Create(w http.ResponseWriter, r *http.Request) { 116 | 117 | userId, err := RecoverUserIdFromContext(r) 118 | if err != nil { 119 | utils.GenerateErrorResponse(w, fmt.Sprintf("%v", err), 500) 120 | return 121 | } 122 | 123 | todoRequest, err := RecoverJsonTodoRequest(r) 124 | if err != nil { 125 | utils.GenerateErrorResponse(w, fmt.Sprintf("%v", err), 400) 126 | return 127 | } 128 | 129 | todo := models.Todo{Title: todoRequest.Title, Description: todoRequest.Description, Done: *todoRequest.Done, User_id: *userId} 130 | 131 | id, err := models.Insert(todo, int64(*userId)) 132 | 133 | var resp map[string]any 134 | if err != nil { 135 | errorMessage := ("erro - " + fmt.Sprintf("%v", err)) 136 | log.Printf(errorMessage) 137 | utils.GenerateErrorResponse(w, errorMessage, 400) 138 | return 139 | } else { 140 | resp = map[string]any{ 141 | "id": id, 142 | } 143 | utils.GenerateResponse(w, resp, 200) 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /internal/handlers/restUser.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "time" 9 | "todos-api/internal/models" 10 | "todos-api/internal/utils" 11 | 12 | "golang.org/x/crypto/bcrypt" 13 | 14 | "github.com/golang-jwt/jwt/v4" 15 | ) 16 | 17 | type Claims struct { 18 | Username string `json:"username"` 19 | Id int64 `json:"id"` 20 | jwt.RegisteredClaims 21 | } 22 | 23 | var jwtKey = []byte("my_secret_key") 24 | 25 | func Login(w http.ResponseWriter, r *http.Request) { 26 | userRequest, err := RecoverJsonUserRequest(r) 27 | if err != nil { 28 | utils.GenerateErrorResponse(w, fmt.Sprintf("%v", err), 400) 29 | return 30 | } 31 | 32 | user := models.User{Username: userRequest.Username, Password: userRequest.Password} 33 | 34 | recoveredUser, err := models.GetByUserName(user.Username) 35 | 36 | if err != nil { 37 | log.Printf("usuário não encontrado - %v", err) 38 | http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) 39 | return 40 | } 41 | 42 | ispassWrong := CheckPasswordHash(recoveredUser.Password, userRequest.Password) 43 | 44 | if ispassWrong { 45 | log.Printf("senha incorreta") 46 | http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) 47 | return 48 | } 49 | 50 | token, _ := generateJWT(recoveredUser.Username, recoveredUser.ID) 51 | 52 | w.Write([]byte(token)) 53 | } 54 | 55 | func CreateUser(w http.ResponseWriter, r *http.Request) { 56 | userRequest, err := RecoverJsonUserRequest(r) 57 | if err != nil { 58 | utils.GenerateErrorResponse(w, fmt.Sprintf("%v", err), 400) 59 | return 60 | } 61 | 62 | fmt.Println(userRequest) 63 | 64 | encryptedpass, err := HashPassword(userRequest.Password) 65 | if err != nil { 66 | log.Printf("erro ao fazer encrypt da password %v", err) 67 | http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 68 | return 69 | } 70 | 71 | user := models.User{Username: userRequest.Username, Password: encryptedpass} 72 | 73 | id, err := models.InsertUser(user) 74 | 75 | var resp map[string]any 76 | if err != nil { 77 | errorMessage := ("erro -" + fmt.Sprintf("%v", err)) 78 | log.Printf(errorMessage) 79 | utils.GenerateErrorResponse(w, errorMessage, 400) 80 | return 81 | } else { 82 | utils.GenerateResponse(w, id, 200) 83 | } 84 | 85 | w.Header().Add("Content-Type", "application/json") 86 | json.NewEncoder(w).Encode(resp) 87 | 88 | } 89 | 90 | func HashPassword(password string) (string, error) { 91 | bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) 92 | return string(bytes), err 93 | } 94 | 95 | func CheckPasswordHash(password, hash string) bool { 96 | err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) 97 | return err == nil 98 | } 99 | 100 | func generateJWT(username string, id int64) (string, error) { 101 | expirationTime := time.Now().Add(5 * time.Minute) 102 | claims := &Claims{ 103 | Username: username, 104 | Id: id, 105 | RegisteredClaims: jwt.RegisteredClaims{ 106 | ExpiresAt: jwt.NewNumericDate(expirationTime), 107 | }, 108 | } 109 | 110 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 111 | 112 | return token.SignedString(jwtKey) 113 | 114 | } 115 | -------------------------------------------------------------------------------- /internal/handlers/todoErrorsHandler.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "strconv" 9 | "todos-api/internal/dto" 10 | 11 | "github.com/go-chi/chi/v5" 12 | ) 13 | 14 | func RecoverUserIdFromContext(r *http.Request) (*int64, error) { 15 | userId, ok := r.Context().Value("userId").(int64) 16 | 17 | if !ok { 18 | errorMessage := ("Erro ao obter o valor 'user id' do contexto") 19 | log.Printf(errorMessage) 20 | return nil, errors.New(errorMessage) 21 | } 22 | 23 | return &userId, nil 24 | } 25 | 26 | func RecoverTodoIdFromURL(r *http.Request) (*int, error) { 27 | id, err := strconv.Atoi(chi.URLParam(r, "id")) 28 | 29 | if err != nil { 30 | errorMessage := ("erro ao fazer parse do id" + fmt.Sprintf("%v", err)) 31 | log.Printf(errorMessage) 32 | return nil, errors.New(errorMessage) 33 | } 34 | 35 | return &id, nil 36 | } 37 | 38 | func RecoverJsonTodoRequest(r *http.Request) (*dto.TodoRequest, error) { 39 | todoRequest, err := dto.FromJsonCreateTodoRequest(r.Body) 40 | 41 | if err != nil { 42 | errorMessage := ("erro ao fazer decode do json - " + fmt.Sprintf("%v", err)) 43 | log.Printf(errorMessage) 44 | return nil, errors.New(errorMessage) 45 | } 46 | 47 | return todoRequest, nil 48 | } 49 | -------------------------------------------------------------------------------- /internal/handlers/userErrorsHandler.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "todos-api/internal/dto" 9 | ) 10 | 11 | func RecoverJsonUserRequest(r *http.Request) (*dto.UserRequest, error) { 12 | userRequest, err := dto.FromJsonCreateUserRequest(r.Body) 13 | 14 | if err != nil { 15 | errorMessage := ("erro ao fazer decode do json - " + fmt.Sprintf("%v", err)) 16 | log.Printf(errorMessage) 17 | return nil, errors.New(errorMessage) 18 | } 19 | 20 | return userRequest, nil 21 | } 22 | -------------------------------------------------------------------------------- /internal/models/todo.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "todos-api/dbconfig" 4 | 5 | type Todo struct { 6 | ID int64 `json:"id"` 7 | Title string `json:"title"` 8 | Description string `json:"description"` 9 | Done bool `json:"done"` 10 | User_id int64 `json:"user_id"` 11 | } 12 | 13 | func Update(id int64, todo Todo) (int64, error) { 14 | res, err := dbconfig.DB.Exec(`UPDATE todos SET title=$1, description=$2, done=$3 WHERE id=$4`, todo.Title, todo.Description, todo.Done, id) 15 | 16 | if err != nil { 17 | return 0, err 18 | } 19 | 20 | return res.RowsAffected() 21 | } 22 | 23 | func Insert(todo Todo, userId int64) (id int64, err error) { 24 | sql := `INSERT INTO todos (title, description, done, user_id) VALUES ($1, $2, $3, $4) RETURNING id` 25 | 26 | err = dbconfig.DB.QueryRow(sql, todo.Title, todo.Description, todo.Done, userId).Scan(&id) 27 | 28 | return 29 | } 30 | 31 | func Get(id int64) (todo Todo, err error) { 32 | row := dbconfig.DB.QueryRow(`SELECT * FROM todos WHERE id=$1`, id) 33 | 34 | err = row.Scan(&todo.ID, &todo.Title, &todo.Description, &todo.Done, &todo.User_id) 35 | 36 | return 37 | } 38 | 39 | func GetAllFromUser(userId int64) (todos []Todo, err error) { 40 | rows, err := dbconfig.DB.Query(`SELECT * FROM todos WHERE user_id=$1`, userId) 41 | 42 | if err != nil { 43 | return 44 | } 45 | 46 | for rows.Next() { 47 | var todo Todo 48 | err = rows.Scan(&todo.ID, &todo.Title, &todo.Description, &todo.Done, &todo.User_id) 49 | if err != nil { 50 | continue 51 | } 52 | 53 | todos = append(todos, todo) 54 | } 55 | 56 | return 57 | } 58 | 59 | func Delete(id int64) (int64, error) { 60 | res, err := dbconfig.DB.Exec(`DELETE FROM todos WHERE id=$1`, id) 61 | 62 | if err != nil { 63 | return 0, err 64 | } 65 | 66 | return res.RowsAffected() 67 | } 68 | -------------------------------------------------------------------------------- /internal/models/user.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "todos-api/dbconfig" 4 | 5 | type User struct { 6 | ID int64 `json:"id"` 7 | Username string `json:"username"` 8 | Password string `json:"password"` 9 | } 10 | 11 | func InsertUser(user User) (id int64, err error) { 12 | sql := `INSERT INTO users (username, password) VALUES ($1, $2) RETURNING id` 13 | 14 | err = dbconfig.DB.QueryRow(sql, user.Username, user.Password).Scan(&id) 15 | 16 | return 17 | } 18 | 19 | func GetUser(id int64) (user User, err error) { 20 | row := dbconfig.DB.QueryRow(`SELECT * FROM users WHERE id=$1`, id) 21 | 22 | err = row.Scan(&user.ID, &user.Username, &user.Password) 23 | 24 | return 25 | } 26 | 27 | func GetByUserName(userName string) (user User, err error) { 28 | row := dbconfig.DB.QueryRow(`SELECT * FROM users WHERE username=$1`, userName) 29 | 30 | err = row.Scan(&user.ID, &user.Username, &user.Password) 31 | 32 | return 33 | } 34 | -------------------------------------------------------------------------------- /internal/utils/response.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | ) 7 | 8 | type Response struct { 9 | ErrorMessage *string `json:"errorMessage,omitempty"` 10 | Data *interface{} `json:"Data,omitempty"` 11 | } 12 | 13 | func GenerateResponse(w http.ResponseWriter, data interface{}, status int) { 14 | resp := Response{} 15 | if data != nil { 16 | resp = Response{ 17 | Data: &data, 18 | } 19 | } 20 | 21 | w.Header().Add("Content-Type", "application/json") 22 | w.WriteHeader(status) 23 | json.NewEncoder(w).Encode(resp) 24 | } 25 | 26 | func GenerateErrorResponse(w http.ResponseWriter, message string, status int) { 27 | resp := Response{ 28 | ErrorMessage: &message, 29 | } 30 | 31 | w.Header().Add("Content-Type", "application/json") 32 | w.WriteHeader(status) 33 | json.NewEncoder(w).Encode(resp) 34 | } 35 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "todos-api/dbconfig" 7 | "todos-api/internal/auth" 8 | "todos-api/internal/handlers" 9 | 10 | "github.com/go-chi/chi/v5" 11 | _ "github.com/lib/pq" 12 | ) 13 | 14 | func main() { 15 | 16 | dbconfig.ConfigDB() 17 | 18 | r := chi.NewRouter() 19 | 20 | r.Group(func(r chi.Router) { 21 | r.Post("/login", handlers.Login) 22 | r.Post("/user", handlers.CreateUser) 23 | }) 24 | 25 | r.Group(func(r chi.Router) { 26 | r.Use(auth.MyMiddleware) 27 | r.Post("/", handlers.Create) 28 | r.Put("/{id}", handlers.Update) 29 | r.Delete("/{id}", handlers.Delete) 30 | r.Get("/", handlers.List) 31 | r.Get("/{id}", handlers.Get) 32 | }) 33 | 34 | http.ListenAndServe(fmt.Sprintf(":%s", "8080"), r) 35 | 36 | defer dbconfig.DB.Close() 37 | 38 | } 39 | --------------------------------------------------------------------------------