Initial commit

This commit is contained in:
Rayhan Hanaputra
2025-10-10 22:18:06 +07:00
committed by GitHub
commit ea02892f14
1246 changed files with 289342 additions and 0 deletions
Binary file not shown.
+41
View File
@@ -0,0 +1,41 @@
CREATE USER `gemasnotesuser`@`%` IDENTIFIED WITH mysql_native_password BY 'hAP[Vl1~4<?7@Sf1Xd';
USE `gemasnotes`;
DROP TABLE IF EXISTS `User`;
CREATE TABLE `User` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(150) DEFAULT NULL,
`username` varchar(50) DEFAULT NULL,
`password` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO User(email,username,password) VALUES ('checker@gemasnotes.id','checker','a6005967f8e6e0ee346804765900f41f');
DROP TABLE IF EXISTS `Notes`;
CREATE TABLE `Notes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(100) DEFAULT NULL,
`content` longtext,
`author` int(11) DEFAULT NULL,
`date` varchar(20) DEFAULT NULL,
`tags` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_user` (`author`),
CONSTRAINT `fk_user` FOREIGN KEY (`author`) REFERENCES `User` (`id`)
);
DROP TABLE IF EXISTS `rewards`;
CREATE TABLE `rewards` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`fl4gg` varchar(60),
PRIMARY KEY (`id`)
);
INSERT INTO rewards(fl4gg) VALUES ('WRECKIT50{e54e6f6ba0c7cbb4eb7a2016e2f17842}');
GRANT SELECT,INSERT,UPDATE,DELETE ON `User` TO `gemasnotesuser`@`%`;
GRANT SELECT,INSERT,UPDATE,DELETE ON `Notes` TO `gemasnotesuser`@`%`;
GRANT SELECT ON `rewards` TO `gemasnotesuser`@`%`;
FLUSH PRIVILEGES;
@@ -0,0 +1,22 @@
FROM public.ecr.aws/docker/library/golang
ARG PASSWORD
RUN echo root:${PASSWORD} | chpasswd
RUN apt-get update && apt-get install -y openssh-server curl
RUN echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config
RUN echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
RUN service ssh start
WORKDIR /app/src
COPY ./src/go.mod ./
COPY ./src/go.sum ./
RUN go mod download
COPY ./src ./
RUN rm -rf ./logs && mkdir ./logs
RUN chmod +x /app/src/*.sh
EXPOSE 3000 22
ENTRYPOINT [ "/bin/sh", "-c", "/app/src/start.sh"]
@@ -0,0 +1,15 @@
{
"db": {
"type": "mysql",
"host": "database",
"port": 3306,
"username": "gemasnotesuser",
"password": "hAP[Vl1~4<?7@Sf1Xd",
"database": "gemasnotes"
},
"log": {
"logfile" : "logs/backend.log",
"format": "json",
"timestampformat": "2006-01-02T15:04:05.000"
}
}
@@ -0,0 +1,163 @@
package notescontrollers
import (
"encoding/json"
"fmt"
"io"
"net/http"
models "note-service/models/notes"
. "note-service/utils/helper"
session "note-service/utils/session"
"strconv"
"github.com/buger/jsonparser"
"github.com/julienschmidt/httprouter"
"github.com/sirupsen/logrus"
)
type BaseHandler struct {
noteRepo models.NotesRepository
log *logrus.Logger
}
func NewBaseHandler(noteRepo models.NotesRepository, log *logrus.Logger) *BaseHandler {
return &BaseHandler{
noteRepo: noteRepo,
log: log,
}
}
func (h *BaseHandler) GetNotes(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
var notes *[]models.Notes
var err error
if r.Method == "GET" {
notes, err = h.noteRepo.GetNotes()
} else if r.Method == "POST" && r.Header.Get("Content-Type") == "application/json" {
body, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
title, err := jsonparser.GetString([]byte(body), "title")
if err != nil {
h.log.Errorln(err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
notes, err = h.noteRepo.GetNotesByTitle(title)
if err != nil {
h.log.Errorln(err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
}
if err != nil {
h.log.Errorln(err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
SendJSONResp(w, notes)
}
func (h *BaseHandler) GetDetailNotes(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
notes, err := h.noteRepo.GetDetailNotes(ps.ByName("id"))
if err != nil {
h.log.Errorln(err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
SendJSONResp(w, notes)
}
func (h *BaseHandler) DeleteNote(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
claims, respCode := session.GetData(r)
if respCode != 200 {
w.WriteHeader(respCode)
return
}
result := h.noteRepo.DeleteNote(ps.ByName("id"), claims.Id)
if result {
w.WriteHeader(http.StatusNoContent)
return
}
w.WriteHeader(http.StatusInternalServerError)
}
func (h *BaseHandler) GetNotesCount(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
if r.Header.Get("Content-Type") == "application/json" {
var raw map[string]int
var countResp string
body, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
key, err := jsonparser.GetString([]byte(body), "count_by")
if err != nil {
h.log.Errorln(err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
if key != "title" && key != "tags" {
w.WriteHeader(http.StatusBadRequest)
return
}
keyword, err := jsonparser.GetString([]byte(body), "keyword")
if err != nil {
h.log.Errorln(err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
count, err := h.noteRepo.GetNotesCount(key, keyword)
if err != nil {
countResp = `{"count":0}`
} else {
countResp = fmt.Sprintf(`{"count":%d}`, *count)
}
_ = json.Unmarshal([]byte(countResp), &raw)
SendJSONResp(w, raw)
}
}
func (h *BaseHandler) InsertUpdateNote(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
claims, respCode := session.GetData(r)
if respCode != 200 {
w.WriteHeader(respCode)
return
}
resp := map[string]int{"PUT": 201, "PATCH": 204}
method := r.Method
if r.Header.Get("Content-Type") == "application/json" {
decoder := json.NewDecoder(r.Body)
noteDetail := models.Notes{}
err := decoder.Decode(&noteDetail)
if err != nil {
h.log.Errorln(err)
w.WriteHeader(http.StatusBadRequest)
return
}
noteDetail.Author = strconv.Itoa(claims.Id)
var result bool
if method == "PUT" {
result = h.noteRepo.InsertUpdateNote(noteDetail, 1)
} else if method == "PATCH" {
result = h.noteRepo.InsertUpdateNote(noteDetail, 2)
}
if result {
w.WriteHeader(resp[method])
return
}
w.WriteHeader(http.StatusInternalServerError)
} else {
w.WriteHeader(http.StatusBadRequest)
}
}
@@ -0,0 +1,119 @@
package usercontrollers
import (
"encoding/json"
"fmt"
"net/http"
models "note-service/models/user"
. "note-service/utils/helper"
session "note-service/utils/session"
"reflect"
"github.com/julienschmidt/httprouter"
"github.com/sirupsen/logrus"
)
type BaseHandler struct {
userRepo models.UserRepository
log *logrus.Logger
}
func NewBaseHandler(userRepo models.UserRepository, log *logrus.Logger) *BaseHandler {
return &BaseHandler{
userRepo: userRepo,
log: log,
}
}
func (h *BaseHandler) GetProfile(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
var user *models.User
var err error
claims, resp_code := session.GetData(r)
if resp_code != 200 {
w.WriteHeader(resp_code)
return
}
user, err = h.userRepo.GetProfile(claims.Id)
if err != nil {
h.log.Errorln(err.Error())
}
SendJSONResp(w, user)
}
func (h *BaseHandler) Logout(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
claims, resp_code := session.GetData(r)
if resp_code != 200 {
w.WriteHeader(resp_code)
return
}
session.RemoveToken(claims.Id)
http.SetCookie(w, &http.Cookie{
Name: "token",
Value: "",
MaxAge: -1,
})
w.WriteHeader(http.StatusOK)
}
func (h *BaseHandler) LoginCheck(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
var raw map[string]interface{}
var resp string
if r.Header.Get("Content-Type") == "application/json" {
decoder := json.NewDecoder(r.Body)
userModel := models.User{}
err := decoder.Decode(&userModel)
if err != nil {
h.log.Errorln(err.Error())
w.WriteHeader(http.StatusBadRequest)
return
}
userData, err := h.userRepo.LoginCheck(userModel.Email, userModel.Password)
if err != nil || reflect.DeepEqual(userData, (models.User{})) {
resp = `{"success":false,"message":"User Not Found"}`
} else {
token, exp, err := session.GenerateToken(userData.Id, userData.Username)
if err != nil {
h.log.Errorln(err.Error())
resp = `{"success":false,"message":"User Not Found"}`
} else {
http.SetCookie(w, &http.Cookie{
Name: "token",
Value: token,
Expires: exp,
HttpOnly: true,
Path: "/",
})
resp = fmt.Sprintf(`{"success":true,"message":"Access Granted","token":"%s"}`, token)
}
}
_ = json.Unmarshal([]byte(resp), &raw)
SendJSONResp(w, raw)
} else {
w.WriteHeader(http.StatusBadRequest)
}
}
func (h *BaseHandler) AddUser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
if r.Header.Get("Content-Type") == "application/json" {
decoder := json.NewDecoder(r.Body)
userModel := models.User{}
err := decoder.Decode(&userModel)
if err != nil {
h.log.Errorln(err.Error())
w.WriteHeader(http.StatusBadRequest)
return
}
result := h.userRepo.AddUser(userModel)
if result {
w.WriteHeader(http.StatusCreated)
return
}
w.WriteHeader(http.StatusInternalServerError)
} else {
w.WriteHeader(http.StatusBadRequest)
}
}
@@ -0,0 +1,13 @@
module note-service
go 1.14
require (
github.com/buger/jsonparser v1.1.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-sql-driver/mysql v1.5.0
github.com/jmoiron/sqlx v1.3.1
github.com/julienschmidt/httprouter v1.3.0
github.com/rs/cors v1.7.0
github.com/sirupsen/logrus v1.8.1
)
@@ -0,0 +1,26 @@
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
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/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/jmoiron/sqlx v1.3.1 h1:aLN7YINNZ7cYOPK3QC83dbM6KT0NMqVMw961TqrejlE=
github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
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/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -0,0 +1,50 @@
{"level":"info","msg":"[ ] POST /api/register insomnia/2023.3.0","time":"2023-09-07T03:21:34.032"}
{"level":"info","msg":"[ ] POST /api/register insomnia/2023.3.0","time":"2023-09-07T03:23:53.730"}
{"level":"info","msg":"[ ] POST /api/login insomnia/2023.3.0","time":"2023-09-07T03:24:18.329"}
{"level":"info","msg":"[ ] POST /api/login insomnia/2023.3.0","time":"2023-09-07T03:25:14.577"}
{"level":"info","msg":"[ ] POST /api/login insomnia/2023.3.0","time":"2023-09-07T03:26:51.703"}
{"level":"info","msg":"[ ] POST /api/login insomnia/2023.3.0","time":"2023-09-07T03:29:46.715"}
{"level":"info","msg":"[ ] POST /api/login insomnia/2023.3.0","time":"2023-09-07T03:33:38.244"}
{"level":"info","msg":"[ ] POST /api/login insomnia/2023.3.0","time":"2023-09-07T03:38:06.990"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:38:14.326"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:38:30.889"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:39:08.407"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:39:19.161"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:41:13.969"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:41:18.505"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:43:46.721"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:43:50.920"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:45:21.305"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:45:22.683"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:45:23.331"}
{"level":"info","msg":"[ ] POST /api/login insomnia/2023.3.0","time":"2023-09-07T03:46:06.583"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:46:19.184"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:52:47.220"}
{"level":"error","msg":"Key path not found","time":"2023-09-07T03:52:47.221"}
{"level":"info","msg":"[ ] POST /api/login insomnia/2023.3.0","time":"2023-09-07T03:53:05.212"}
{"level":"error","msg":"json: cannot unmarshal string into Go value of type usermodels.User","time":"2023-09-07T03:53:05.213"}
{"level":"info","msg":"[ ] POST /api/login insomnia/2023.3.0","time":"2023-09-07T03:55:19.037"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:55:26.126"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:56:19.436"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:57:22.835"}
{"level":"info","msg":"[ ] POST /api/login insomnia/2023.3.0","time":"2023-09-07T03:58:13.509"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:58:30.111"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:59:00.225"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:59:14.632"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:59:20.361"}
{"level":"error","msg":"Error 1054: Unknown column 'a' in 'field list'","time":"2023-09-07T03:59:20.368"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:59:24.919"}
{"level":"error","msg":"Error 1054: Unknown column 'b' in 'field list'","time":"2023-09-07T03:59:24.920"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T03:59:40.476"}
{"level":"error","msg":"sql: Scan error on column index 0, name \"count(*)\": converting driver.Value type []uint8 (\"a\") to a int: invalid syntax","time":"2023-09-07T03:59:40.481"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T04:00:15.759"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T04:00:20.578"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T04:00:25.099"}
{"level":"info","msg":"[ ] POST /api/login insomnia/2023.3.0","time":"2023-09-07T04:01:32.865"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T04:01:40.792"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T04:01:53.832"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T04:10:12.009"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T04:11:26.797"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T04:12:18.201"}
{"level":"info","msg":"[ ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T04:16:54.433"}
{"level":"info","msg":"[ 127.0.0.1 ] POST /api/notes/count insomnia/2023.3.0","time":"2023-09-07T04:17:13.273"}
@@ -0,0 +1,65 @@
package main
import (
"fmt"
"io/ioutil"
"net/http"
notescontrollers "note-service/controllers/notes"
usercontrollers "note-service/controllers/user"
notesrepo "note-service/repository/notes"
userrepo "note-service/repository/user"
. "note-service/utils/db"
. "note-service/utils/middleware"
. "note-service/utils/session"
"github.com/julienschmidt/httprouter"
"github.com/rs/cors"
)
func main() {
data, _ := ioutil.ReadFile("config/default.json")
logfile, log := SetupLogger(data)
db := Connect_db(data)
userRepo := userrepo.NewUserRepo(db, logfile)
userHandler := usercontrollers.NewBaseHandler(userRepo, logfile)
noteRepo := notesrepo.NewNotesRepo(db, logfile)
noteHandler := notescontrollers.NewBaseHandler(noteRepo, logfile)
c := cors.New(cors.Options{
AllowedOrigins: []string{"https://*", "http://*"},
AllowCredentials: true,
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "PATCH"},
})
router := httprouter.New()
router.ServeFiles("/assets/*filepath", http.Dir("./assets"))
handler := c.Handler(router)
// users routes
router.POST("/api/register", Logger(userHandler.AddUser))
router.POST("/api/login", Logger(userHandler.LoginCheck))
router.GET("/api/logout", Logger(userHandler.Logout))
router.GET("/api/profile", Logger(AuthorizationCheck(userHandler.GetProfile)))
// notes routes
router.POST("/api/notes", Logger(noteHandler.GetNotes))
router.GET("/api/notes", Logger(noteHandler.GetNotes))
router.GET("/api/notes/:id", Logger(noteHandler.GetDetailNotes))
router.PUT("/api/notes", Logger(AuthorizationCheck(noteHandler.InsertUpdateNote)))
router.PATCH("/api/notes", Logger(AuthorizationCheck(noteHandler.InsertUpdateNote)))
router.DELETE("/api/notes/:id", Logger(AuthorizationCheck(noteHandler.DeleteNote)))
// analytics routes
router.POST("/api/notes/count", Logger(AuthorizationCheck(noteHandler.GetNotesCount)))
log.Infoln("Server running on port :3000")
err := http.ListenAndServe(":3000", handler)
if err != nil {
fmt.Println(err)
}
}
@@ -0,0 +1,19 @@
package notesmodels
type Notes struct {
Id int `json:"id" db:"id"`
Title string `json:"title" db:"title"`
Author string `json:"author" db:"author"`
Date string `json:"date" db:"date"`
Content string `json:"content,omitempty" db:"content"`
Tags string `json:"tags" db:"tags"`
}
type NotesRepository interface {
GetNotes() (*[]Notes, error)
GetNotesByTitle(title string) (*[]Notes, error)
InsertUpdateNote(note Notes, op int) bool
GetNotesCount(key string, keyword string) (*int, error)
DeleteNote(id string, author int) bool
GetDetailNotes(id string) (*Notes, error)
}
@@ -0,0 +1,14 @@
package usermodels
type User struct {
Id int `json:"id" db:"id"`
Username string `json:"username" db:"username"`
Password string `json:"password,omitempty" db:"password"`
Email string `json:"email" db:"email"`
}
type UserRepository interface {
LoginCheck(Username string, Password string) (*User, error)
GetProfile(id int) (*User, error)
AddUser(user User) bool
}
@@ -0,0 +1,104 @@
package notesrepo
import (
"database/sql"
"fmt"
models "note-service/models/notes"
"github.com/jmoiron/sqlx"
"github.com/sirupsen/logrus"
)
const simpleNote = "SELECT Notes.id, title, User.email as author, date FROM Notes, User WHERE Notes.author=User.id ORDER BY Notes.id DESC"
const simpleNoteFilter = "SELECT Notes.id, title, User.email as author, date FROM Notes, User WHERE Notes.author=User.id AND Notes.title LIKE ?"
const detailNote = "SELECT Notes.id as id, title, User.email as author, date, content, tags FROM Notes, User WHERE Notes.author=User.id AND Notes.id=?"
const insertNote = "INSERT INTO Notes(title,content,author,date,tags) VALUES (?,?,?,DATE_FORMAT(NOW(), '%M %d, %Y'),?)"
const countNote = "SELECT count(*) FROM Notes WHERE %s LIKE '%%%s%%'"
const updateNote = "UPDATE Notes SET title = ?, content = ?, date = DATE_FORMAT(NOW(), '%M %d, %Y') WHERE author = ? AND id = ?"
const deleteNote = "DELETE FROM Notes WHERE id = ? AND author = ?"
type NotesRepo struct {
db *sqlx.DB
log *logrus.Logger
}
func NewNotesRepo(db *sqlx.DB, log *logrus.Logger) *NotesRepo {
return &NotesRepo{db: db, log: log}
}
func (r *NotesRepo) GetDetailNotes(id string) (*models.Notes, error) {
data := models.Notes{}
stmt, err := r.db.Preparex(detailNote)
if err != nil {
r.log.Errorln(err.Error())
}
defer stmt.Close()
err = stmt.QueryRowx(id).StructScan(&data)
if err != nil {
r.log.Errorln(err)
}
return &data, err
}
func (r *NotesRepo) GetNotes() (*[]models.Notes, error) {
data := []models.Notes{}
stmt, err := r.db.Preparex(simpleNote)
if err != nil {
r.log.Errorln(err.Error())
}
defer stmt.Close()
err = stmt.Select(&data)
if err != nil {
return &data, err
}
return &data, nil
}
func (r *NotesRepo) GetNotesByTitle(title string) (*[]models.Notes, error) {
data := []models.Notes{}
err := r.db.Select(&data, simpleNoteFilter, title)
if err != nil {
r.log.Errorln(err.Error())
}
return &data, err
}
func (r *NotesRepo) GetNotesCount(key string, keyword string) (*int, error) {
var count int
err := r.db.Get(&count, fmt.Sprintf(countNote, key, keyword))
if err != nil {
r.log.Errorln(err.Error())
}
return &count, err
}
func (r *NotesRepo) InsertUpdateNote(notes models.Notes, op int) bool {
var result sql.Result
if op == 1 {
result = r.db.MustExec(insertNote, notes.Title, notes.Content, notes.Author, notes.Tags)
} else {
result = r.db.MustExec(updateNote, notes.Title, notes.Content, notes.Author, notes.Id)
}
affectedRows, err := result.RowsAffected()
if err != nil {
r.log.Errorln(err.Error())
return false
}
if affectedRows > 0 {
return true
}
return false
}
func (r *NotesRepo) DeleteNote(id string, author int) bool {
result := r.db.MustExec(deleteNote, id, author)
affectedRows, err := result.RowsAffected()
if err != nil {
r.log.Errorln(err.Error())
return false
}
if affectedRows > 0 {
return true
}
return false
}
@@ -0,0 +1,67 @@
package userrepo
import (
"database/sql"
models "note-service/models/user"
"github.com/jmoiron/sqlx"
"github.com/sirupsen/logrus"
)
const getProfileQuery = "SELECT id,username,email FROM User WHERE id=?"
const loginQuery = "SELECT * FROM User WHERE email=? AND password=MD5(?)"
const addUserQuery = "INSERT INTO User(username,password,email) VALUES (?,md5(?),?)"
type UserRepo struct {
db *sqlx.DB
log *logrus.Logger
}
func NewUserRepo(db *sqlx.DB, log *logrus.Logger) *UserRepo {
return &UserRepo{db: db, log: log}
}
func affected(result sql.Result, r *UserRepo) bool {
affectedRows, err := result.RowsAffected()
if err != nil {
r.log.Errorln(err.Error())
return false
}
if affectedRows > 0 {
return true
}
return false
}
func (r *UserRepo) GetProfile(id int) (*models.User, error) {
data := models.User{}
stmt, err := r.db.Preparex(getProfileQuery)
if err != nil {
r.log.Errorln(err.Error())
}
defer stmt.Close()
err = stmt.QueryRowx(id).StructScan(&data)
if err != nil {
return &data, err
}
return &data, nil
}
func (r *UserRepo) LoginCheck(user string, pass string) (*models.User, error) {
data := models.User{}
stmt, err := r.db.Preparex(loginQuery)
if err != nil {
r.log.Errorln(err.Error())
}
defer stmt.Close()
err = stmt.QueryRowx(user, pass).StructScan(&data)
if err != nil {
return &data, err
}
return &data, nil
}
func (r *UserRepo) AddUser(user models.User) bool {
result := r.db.MustExec(addUserQuery, user.Username, user.Password, user.Email)
return affected(result, r)
}
@@ -0,0 +1,3 @@
#!/bin/sh
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o gw.app .
/app/src/gw.app
@@ -0,0 +1,4 @@
#!/bin/sh
/usr/sbin/sshd -D &
cd /app/src/ && ./run.sh
@@ -0,0 +1,41 @@
package db
import (
"encoding/json"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
type DBConfig struct {
Db DBData `json:"db"`
}
type DBData struct {
DBType string `json:"type"`
DBHost string `json:"host"`
DBPort int `json:"port"`
User string `json:"username"`
Pass string `json:"password"`
DBName string `json:"database"`
}
func Connect_db(config []byte) *sqlx.DB {
data := DBConfig{}
_ = json.Unmarshal([]byte(config), &data)
connection_string := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", data.Db.User, data.Db.Pass, data.Db.DBHost, data.Db.DBPort, data.Db.DBName)
db, err := sqlx.Open(data.Db.DBType, connection_string)
if err != nil {
log.Fatalln(err)
}
err = db.Ping()
if err != nil {
log.Fatalln(err)
}
return db
}
@@ -0,0 +1,12 @@
package helper
import (
"encoding/json"
"net/http"
)
func SendJSONResp(w http.ResponseWriter, data interface{}) {
resp, _ := json.Marshal(data)
w.Header().Set("Content-Type", "application/json")
w.Write(resp)
}
@@ -0,0 +1,66 @@
package middleware
import (
"encoding/json"
"net/http"
"os"
"github.com/julienschmidt/httprouter"
"github.com/sirupsen/logrus"
)
type LogData struct {
Logfile string `json:"logfile"`
TimestampFormat string `json:"timestampformat"`
Format string `json:"format"`
}
type LogConfig struct {
Log LogData `json:"log"`
}
var Logging *logrus.Logger
var StdoutLog *logrus.Logger
func setupStdOutLog(config *LogConfig) {
StdoutLog = logrus.New()
StdoutLog.SetFormatter(&logrus.TextFormatter{
TimestampFormat: config.Log.TimestampFormat,
FullTimestamp: true,
})
}
func setupLogFile(config *LogConfig) {
Logging = logrus.New()
if config.Log.Format == "json" {
Logging.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: config.Log.TimestampFormat,
})
} else {
Logging.SetFormatter(&logrus.TextFormatter{
TimestampFormat: config.Log.TimestampFormat,
})
}
f, err := os.OpenFile(config.Log.Logfile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
Logging.Fatal("Cannot Open Logging Files")
}
Logging.SetOutput(f)
}
func SetupLogger(data []byte) (*logrus.Logger, *logrus.Logger) {
config := LogConfig{}
_ = json.Unmarshal([]byte(data), &config)
setupStdOutLog(&config)
setupLogFile(&config)
return Logging, StdoutLog
}
func Logger(next httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
Logging.Infof("[ %s ] %s %s %s", r.Header.Get("X-Real-IP"), r.Method, r.URL.Path, r.Header.Get("User-Agent"))
StdoutLog.Infof("[ %s ] %s %s %s", r.Header.Get("X-Real-IP"), r.Method, r.URL.Path, r.Header.Get("User-Agent"))
next(w, r, ps)
}
}
@@ -0,0 +1,91 @@
package session
import (
"math/rand"
"net/http"
"strings"
"time"
jwt "github.com/dgrijalva/jwt-go"
"github.com/julienschmidt/httprouter"
)
var secretKey = make([]byte, 42)
var validSession = make(map[int]bool)
const tokenExpired = 10
type Claims struct {
Id int
Username string
jwt.StandardClaims
}
func init() {
rand.Seed(time.Now().UnixNano())
rand.Read(secretKey)
}
func getToken(r *http.Request) string {
token := r.Header.Get("Authorization")
tokenString := strings.TrimPrefix(token, "Bearer ")
return tokenString
}
func GenerateToken(id int, username string) (string, time.Time, error) {
expirationTime := time.Now().Add(tokenExpired * time.Minute)
claims := &Claims{
Username: username,
Id: id,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expirationTime.Unix(),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(secretKey)
if err == nil {
validSession[id] = true
}
return tokenString, expirationTime, err
}
func GetData(r *http.Request) (*Claims, int) {
claims := &Claims{}
tokenStr := getToken(r)
if tokenStr == "" {
return claims, 401
}
tkn, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
return secretKey, nil
})
if err != nil {
if err == jwt.ErrSignatureInvalid {
return claims, 400
}
return claims, 401
}
if tkn.Valid && validSession[claims.Id] {
return claims, 200
}
return claims, 401
}
func AuthorizationCheck(next httprouter.Handle) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
_, statusCode := GetData(r)
if statusCode != 200 {
w.WriteHeader(http.StatusUnauthorized)
return
}
next(w, r, ps)
}
}
func RemoveToken(id int) {
delete(validSession, id)
}
@@ -0,0 +1,10 @@
FROM public.ecr.aws/docker/library/python:3.10
WORKDIR /app
COPY ./src/requirements.txt .
RUN pip install -r requirements.txt
COPY ./src .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
@@ -0,0 +1,12 @@
import MySQLdb
# Database configuration
db_config = {
'host': 'database',
'user': 'root',
'passwd': 'why_my_random_string_password_doesnot_working',
'db': 'gemasnotes',
}
# Create a connection to the database
conn = MySQLdb.connect(**db_config)
@@ -0,0 +1,88 @@
from fastapi import Request, FastAPI, HTTPException, Depends
from fastapi.security import HTTPBasic, HTTPBasicCredentials
import requests
import json
import re
from validation import validation_schema
from jsonschema import Draft7Validator,validate
from jsonschema.exceptions import ValidationError
from database import conn
@Draft7Validator.FORMAT_CHECKER.checks("security-validation",ValueError)
def validate_payload(data):
pattern = r'[!#$"\'`{}()]'
return not re.search(pattern, data)
app = FastAPI()
security = HTTPBasic()
note_service = "http://gemas-notes:3000"
def validate_credentials(credentials):
if credentials.username != "gemasflagreceiver" or credentials.password != "AuTeEbn%.Q5$pC_ge6":
return False
return True
@app.post("/flag_receiver")
async def flag_receiver(request: Request, credentials: HTTPBasicCredentials = Depends(security)):
validate_credentials(credentials)
body = await request.json()
flag = body["flag"]
cursor = conn.cursor()
query = "UPDATE rewards SET fl4gg=%s"
cursor.execute(query, (flag,))
conn.commit()
count = cursor.rowcount
cursor.close()
return {"success": True} if count else {"success": False}
@app.api_route("/{path_name:path}", methods=["GET","DELETE"])
def no_body_request(request: Request):
client_host = request.client.host
headers = {"X-Real-IP": client_host}
if request.headers.get("authorization"):
headers["Authorization"] = request.headers.get("authorization")
if request.method == "DELETE":
status_code = requests.delete(note_service + request.url.path, headers=headers).status_code
if status_code == 204:
return {}
raise HTTPException(status_code=500)
try:
return requests.get(note_service + request.url.path, headers=headers).json()
except:
return []
@app.api_route("/{path_name:path}", methods=["POST", "PUT","PATCH"])
async def with_body_request(request: Request):
url_path = request.url.path
body = await request.body()
client_host = request.client.host
if request.headers.get("content-type") != "application/json":
raise HTTPException(status_code=400, detail="Please specify JSON data")
try:
headers = dict(request.headers)
headers["X-Real-IP"] = client_host
validate(json.loads(body), validation_schema.get(url_path), format_checker=Draft7Validator.FORMAT_CHECKER)
if request.method == "PUT":
res = requests.put(note_service + url_path, data=body, headers=headers)
elif request.method == "PATCH":
res = requests.patch(note_service + url_path, data=body, headers=headers)
else:
res = requests.post(note_service + url_path, data=body, headers=headers)
if res.status_code == 401:
raise HTTPException(status_code=401, detail="Please Login")
if res.status_code == 200:
return res.json()
return {}
except ValidationError as e:
raise HTTPException(status_code=400, detail="Invalid JSON data")
@@ -0,0 +1,23 @@
annotated-types==0.5.0
anyio==3.7.1
attrs==23.1.0
certifi==2023.7.22
charset-normalizer==3.2.0
click==8.1.7
exceptiongroup==1.1.3
fastapi==0.103.1
h11==0.14.0
idna==3.4
jsonschema==4.19.0
jsonschema-specifications==2023.7.1
pydantic==2.3.0
pydantic_core==2.6.3
referencing==0.30.2
requests==2.31.0
rpds-py==0.10.2
sniffio==1.3.0
starlette==0.27.0
typing_extensions==4.7.1
urllib3==2.0.4
uvicorn==0.23.2
mysqlclient
@@ -0,0 +1,70 @@
validation_schema = {
"/api/register": {
"type": "object",
"properties": {
"username": {
"type": "string",
"minLength": 5,
"maxLength": 10,
"format": "security-validation"
},
"email": {
"type": "string",
"format": "email"
},
"password": {
"type": "string",
"minLength": 10,
"format": "security-validation"
}
}
},
"/api/login": {
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email"
},
"password": {
"type": "string",
"minLength": 10,
"format": "security-validation"
}
}
},
"/api/notes": {
"type": "object",
"properties": {
"title": {
"type": "string",
"maxLength": 100,
"format": "security-validation"
},
"content": {
"type": "string",
"minLength": 10,
"format": "security-validation"
},
"tags": {
"type": "string",
"maxLength": 100,
"format": "security-validation"
}
}
},
"/api/notes/count": {
"type": "object",
"properties": {
"count_by": {
"type": "string",
"enum": ["title", "tags"]
},
"keyword": {
"type": "string",
"maxLength": 10,
"format": "security-validation"
}
}
}
}