package main import ( "encoding/json" "fmt" "log" "strconv" "strings" "github.com/fasthttp/router" "github.com/valyala/fasthttp" "github.com/xujiajun/nutsdb" ) func RunWebserv() { r := router.New() r.GET("/", Headers(Start)) r.GET("/api/season", Headers(Season)) r.GET("/api/anime/{id}", Headers(AnimeGet)) r.GET("/api/user/{user?}", Headers(UserGet)) r.GET("/api/watch/{user?}", Headers(WatchGet)) r.GET("/api/watchext/{user?}", Headers(WatchExtendedGet)) r.POST("/api/register", Headers(Register)) r.POST("/api/watch/{user}", Headers(WatchPost)) r.DELETE("/api/register", Headers(UnRegister)) r.DELETE("/api/watch/{user}", Headers(WatchDelete)) log.Fatal(fasthttp.ListenAndServe(":"+strconv.Itoa(*webServerPort), r.Handler)) } func Start(ctx *fasthttp.RequestCtx) { season, err := GetSeasonCache() if err != nil { addErrorToCtx(ctx, err) return } WriteIndex(ctx, season) ctx.SetContentType("text/html; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusOK) } func Season(ctx *fasthttp.RequestCtx) { data, err := seasoncache.Get(seasonApiJikan) if err != nil { addErrorToCtx(ctx, err) return } err = writeResponseBody(ctx, data) if err != nil { addErrorToCtx(ctx, err) return } ctx.SetContentType("application/json; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusOK) } func AnimeGet(ctx *fasthttp.RequestCtx) { idVal := ctx.UserValue("id") if idVal == nil { ctx.SetStatusCode(fasthttp.StatusBadRequest) return } // check user exists malId, err := strconv.ParseInt(fmt.Sprintf("%s", idVal), 10, 64) if err != nil { ctx.WriteString(err.Error()) ctx.SetStatusCode(fasthttp.StatusBadRequest) return } // search season first anime, err := SearchSeason(malId) var bytes []byte if err != nil { _, bytes, err = GetAnimeDetailData(malId) if err != nil { addErrorToCtx(ctx, err) return } } else { bytes, err = json.Marshal(&anime) if err != nil { addErrorToCtx(ctx, err) return } } _, err = ctx.Write(bytes) if err != nil { addErrorToCtx(ctx, err) return } ctx.SetContentType("application/json; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusOK) } func UserGet(ctx *fasthttp.RequestCtx) { usrVal := ctx.UserValue("user") users := make([]User, 0) if usrVal != nil { // get specific user user, _, err := GetUserData(fmt.Sprintf("%s", usrVal)) if err != nil { ctx.WriteString(err.Error()) ctx.SetStatusCode(fasthttp.StatusNotFound) return } users = append(users, *user) } else { regUsers, err := ReadRegisteredUsers() if err != nil { // check if no users registered if err != nutsdb.ErrBucketEmpty { addErrorToCtx(ctx, err) return } regUsers = make([]UserData, 0) } for _, u := range regUsers { user, _, err := GetUserData(u.Username) if err != nil { addErrorToCtx(ctx, err) return } users = append(users, *user) } } bytes, err := json.Marshal(users) if err != nil { addErrorToCtx(ctx, err) return } _, err = ctx.Write(bytes) if err != nil { addErrorToCtx(ctx, err) return } ctx.SetContentType("application/json; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusOK) } func WatchGet(ctx *fasthttp.RequestCtx) { animeUsers, err := watchGetLogic(ctx) if err != nil { return } bytes, err := json.Marshal(animeUsers) if err != nil { addErrorToCtx(ctx, err) return } _, err = ctx.Write(bytes) if err != nil { addErrorToCtx(ctx, err) return } ctx.SetContentType("application/json; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusOK) } func WatchExtendedGet(ctx *fasthttp.RequestCtx) { animeUsers, err := watchGetLogic(ctx) if err != nil { return } animeUsersExtended := make([]AnimeUserExtended, 0) // make list advanced for _, a := range animeUsers { data, _, err := GetAnimeDetailData(a.Anime) if err != nil { addErrorToCtx(ctx, err) return } animeUsersExtended = append(animeUsersExtended, AnimeUserExtended{ Anime: a.Anime, Users: a.Users, Data: *data, }) } bytes, err := json.Marshal(animeUsersExtended) if err != nil { addErrorToCtx(ctx, err) return } _, err = ctx.Write(bytes) if err != nil { addErrorToCtx(ctx, err) return } ctx.SetContentType("application/json; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusOK) } func Register(ctx *fasthttp.RequestCtx) { if string(ctx.Request.Header.ContentType()) != "application/json" { ctx.SetStatusCode(fasthttp.StatusBadRequest) return } body := ctx.PostBody() var register RegisterData err := json.Unmarshal(body, ®ister) if err != nil { ctx.WriteString(err.Error()) ctx.SetStatusCode(fasthttp.StatusBadRequest) return } if register.MalID == 0 || register.Username == "" || register.Secret == "" || register.Sauce == "" { ctx.WriteString("Sprich JSON du Hurensohn") ctx.SetStatusCode(fasthttp.StatusBadRequest) return } calcSauce := Sauce(register.MalID, register.Username) if calcSauce != strings.ToLower(register.Sauce) { ctx.WriteString("Möge die Sauce mit dir sein") ctx.SetStatusCode(fasthttp.StatusBadRequest) return } // check user exists _, err = ReadUser(register.Username) if err == nil { ctx.WriteString("Nicht drängeln") ctx.SetStatusCode(fasthttp.StatusConflict) return } // check user legit userData, _, err := GetUserData(register.Username) if err != nil { ctx.WriteString(err.Error()) ctx.SetStatusCode(fasthttp.StatusExpectationFailed) return } if userData.MalID != register.MalID { ctx.WriteString("Dich gibts nicht auf MAL") ctx.SetStatusCode(fasthttp.StatusExpectationFailed) return } // REGISTER user := UserData{ Username: register.Username, MalID: register.MalID, Secret: register.Secret, } err = SaveUser(&user) if err != nil { addErrorToCtx(ctx, err) return } ctx.SetBody(body) ctx.SetContentType("application/json; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusOK) } func WatchPost(ctx *fasthttp.RequestCtx) { processUpdateReq(ctx, true) } func UnRegister(ctx *fasthttp.RequestCtx) { if string(ctx.Request.Header.ContentType()) != "application/json" { ctx.SetStatusCode(fasthttp.StatusBadRequest) return } body := ctx.PostBody() var register RegisterData err := json.Unmarshal(body, ®ister) if err != nil { ctx.WriteString(err.Error()) ctx.SetStatusCode(fasthttp.StatusBadRequest) return } if register.MalID == 0 || register.Username == "" || register.Secret == "" || register.Sauce == "" { ctx.WriteString("Sprich JSON du Hurensohn") ctx.SetStatusCode(fasthttp.StatusBadRequest) return } calcSauce := Sauce(register.MalID, register.Username) if calcSauce != strings.ToLower(register.Sauce) { ctx.WriteString("Möge die Sauce mit dir sein") ctx.SetStatusCode(fasthttp.StatusBadRequest) return } // DELETE err = DeleteUserFromAnimes(register.MalID) if err != nil { ctx.WriteString(err.Error()) ctx.SetStatusCode(fasthttp.StatusNotFound) return } err = DbDelete(bucketUsers, register.Username) if err != nil { ctx.WriteString(err.Error()) ctx.SetStatusCode(fasthttp.StatusNotFound) return } ctx.SetBody(body) ctx.SetContentType("application/json; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusOK) } func WatchDelete(ctx *fasthttp.RequestCtx) { processUpdateReq(ctx, false) } func GheddoAuth(username, auth string) (bool, int64) { user, err := ReadUser(username) if err != nil { return false, 0 } return user.Secret == auth, user.MalID } func Headers(h fasthttp.RequestHandler) fasthttp.RequestHandler { return fasthttp.RequestHandler(func(ctx *fasthttp.RequestCtx) { if *localServer { ctx.Response.Header.Set("Access-Control-Allow-Origin", "*") ctx.Response.Header.Set("Access-Control-Allow-Headers", "*") ctx.Response.Header.Set("Access-Control-Allow-Methods", "*") } h(ctx) }) } func watchGetLogic(ctx *fasthttp.RequestCtx) ([]AnimeUser, error) { usrVal := ctx.UserValue("user") var userId int64 if usrVal != nil { // check user exists user, err := ReadUser(fmt.Sprintf("%s", usrVal)) if err != nil { ctx.WriteString("Dich gibts nicht") ctx.SetStatusCode(fasthttp.StatusNotFound) return nil, err } userId = user.MalID } animesUsers, err := ReadAnimeUsers() if err != nil { // check if no anime were inserted if err != nutsdb.ErrBucketEmpty { addErrorToCtx(ctx, err) return nil, err } animesUsers = make([]AnimeUser, 0) } // apply single user logic if usrVal != nil { filteredAnimes := make([]AnimeUser, 0) for _, a := range animesUsers { for _, u := range a.Users { if u.MalID == userId { filteredAnimes = append(filteredAnimes, a) break } } } animesUsers = filteredAnimes } return animesUsers, err } func processUpdateReq(ctx *fasthttp.RequestCtx, update bool) { auth := ctx.Request.Header.Peek("X-HUSO-AUTH") if ctx.UserValue("user") == nil || auth == nil || string(auth) == "" || string(ctx.Request.Header.ContentType()) != "application/json" { ctx.SetStatusCode(fasthttp.StatusBadRequest) return } username := fmt.Sprintf("%s", ctx.UserValue("user")) legit, userId := GheddoAuth(username, string(auth)) if !legit { ctx.SetStatusCode(fasthttp.StatusUnauthorized) return } body := ctx.PostBody() var animes []AnimeUser err := json.Unmarshal(body, &animes) if err != nil || len(animes) == 0 { ctx.WriteString(err.Error()) ctx.SetStatusCode(fasthttp.StatusBadRequest) return } // iterate sent animes for i, anime := range animes { // check if anime is in season _, err = SearchSeason(anime.Anime) if err != nil { // anime not in season => holen sie diese _, _, err := GetAnimeDetailData(anime.Anime) if err != nil { ctx.WriteString(err.Error()) ctx.SetStatusCode(fasthttp.StatusNotFound) return } } var animeData *AnimeUser // update or delete request if update { // anime exitsts => save animeData, err = AddUserToAnime(username, userId, anime.Anime) } else { // anime exitsts => delete animeData, err = DeleteUserFromAnime(username, userId, anime.Anime) } if err != nil { addErrorToCtx(ctx, err) return } animes[i].Users = animeData.Users } data, err := json.Marshal(animes) if err != nil { addErrorToCtx(ctx, err) return } err = writeResponseBody(ctx, data) if err != nil { addErrorToCtx(ctx, err) return } ctx.SetContentType("application/json; charset=utf-8") } func writeResponseBody(ctx *fasthttp.RequestCtx, bytes []byte) error { _, err := ctx.Write(bytes) if err != nil { return err } ctx.SetContentType("application/json; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusOK) return err } func addErrorToCtx(ctx *fasthttp.RequestCtx, err error) { ctx.WriteString(err.Error()) ctx.SetStatusCode(fasthttp.StatusInternalServerError) }