package main import ( "encoding/json" "fmt" "log" "strconv" "strings" "time" "github.com/fasthttp/router" "github.com/gookit/color" "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/nextseason", Headers(SeasonNext)) r.GET("/api/nextnextseason", Headers(SeasonNextNext)) r.GET("/api/lastseason", Headers(SeasonLast)) r.GET("/api/auth/{user}", Headers(AuthTest)) r.GET("/api/anime/{id}", Headers(AnimeGet)) r.GET("/api/animesearch", Headers(AnimeSearchGet)) r.GET("/api/appointment", Headers(AppointmentGet)) r.GET("/api/chat/{id}", Headers(ChatGet)) r.GET("/api/log", Headers(LogGet)) r.GET("/api/user/{user?}", Headers(UserGet)) r.GET("/api/watch/{user?}", Headers(WatchGet)) r.GET("/api/watchext/{user?}", Headers(WatchExtendedGet)) r.POST("/api/appointment/{user}", Headers(AppointmentPost)) r.POST("/api/chat/{id}/{user}", Headers(ChatPost)) r.POST("/api/register", Headers(Register)) r.POST("/api/watch/{user}", Headers(WatchPost)) r.PATCH("/api/register", Headers(RegisterUpdate)) r.DELETE("/api/appointment/{user}", Headers(AppointmentDelete)) 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(GetCurrentSeasonString()) if err != nil { addErrorToCtx(ctx, err) return } oracles, err := MmReadOracle() if err != nil { color.Errorln(err.Error()) } charts, err := BuildMovieCharts() if err != nil { color.Errorln(err.Error()) } WriteIndex(ctx, season, oracles, charts, logOut.String()) ctx.SetContentType("text/html; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusOK) } func AuthTest(ctx *fasthttp.RequestCtx) { auth := ctx.Request.Header.Peek("X-HUSO-AUTH") if ctx.UserValue("user") == nil || auth == nil || string(auth) == "" { ctx.SetStatusCode(fasthttp.StatusBadRequest) return } username := fmt.Sprintf("%s", ctx.UserValue("user")) legit, _ := GheddoAuth(username, string(auth)) if !legit { ctx.SetStatusCode(fasthttp.StatusUnauthorized) return } ctx.SetStatusCode(fasthttp.StatusOK) } func Season(ctx *fasthttp.RequestCtx) { data, err := seasoncache.Get(GetCurrentSeasonString()) 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 SeasonNext(ctx *fasthttp.RequestCtx) { data, err := seasoncache.Get(GetNextSeasonString()) 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 SeasonNextNext(ctx *fasthttp.RequestCtx) { data, err := seasoncache.Get(GetNextNextSeasonString()) 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 SeasonLast(ctx *fasthttp.RequestCtx) { data, err := seasoncache.Get(GetLastSeasonString()) 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 anime exists malId, err := strconv.ParseInt(fmt.Sprintf("%s", idVal), 10, 64) if err != nil { ctx.WriteString(err.Error()) ctx.SetStatusCode(fasthttp.StatusBadRequest) return } // search anime anime, err := SearchAnime(malId) if err != nil { addErrorToCtx(ctx, err) return } 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 AnimeSearchGet(ctx *fasthttp.RequestCtx) { if !ctx.QueryArgs().Has("q") || string(ctx.QueryArgs().Peek("q")) == "" { ctx.SetStatusCode(fasthttp.StatusBadRequest) return } query := string(ctx.QueryArgs().Peek("q")) // search with query _, bytes, err := SearchAnimeData(query) 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 AppointmentGet(ctx *fasthttp.RequestCtx) { appoints, err := ReadAppointments() // check if no appointments exists if err != nil { if strings.Contains(err.Error(), "not found") || err == nutsdb.ErrBucketEmpty { appoints = make([]Appointment, 0) } else { addErrorToCtx(ctx, err) return } } bytes, err := json.Marshal(appoints) 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 ChatGet(ctx *fasthttp.RequestCtx) { idVal := ctx.UserValue("id") if idVal == nil { ctx.SetStatusCode(fasthttp.StatusBadRequest) return } animeId, err := strconv.ParseInt(fmt.Sprintf("%s", idVal), 10, 64) if err != nil { ctx.WriteString(err.Error()) ctx.SetStatusCode(fasthttp.StatusBadRequest) return } found, err := CheckAnimeExistInDb(animeId) if err != nil { addErrorToCtx(ctx, err) return } if !found { ctx.SetStatusCode(fasthttp.StatusNotFound) return } text, err := ReadChat(animeId) if err != nil { if strings.Contains(err.Error(), "not found") { ctx.WriteString(err.Error()) ctx.SetStatusCode(fasthttp.StatusNoContent) return } addErrorToCtx(ctx, err) return } _, err = ctx.WriteString(text) if err != nil { addErrorToCtx(ctx, err) return } ctx.SetContentType("text/plain; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusOK) } func LogGet(ctx *fasthttp.RequestCtx) { _, err := ctx.WriteString(logOut.String()) if err != nil { addErrorToCtx(ctx, err) return } ctx.SetContentType("text/plain; 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 { addErrorToCtx(ctx, err) 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 { // search anime data, err := SearchAnime(a.Anime) if err != nil { addErrorToCtx(ctx, err) return } apps, err := SearchAppointments(a.Anime) if err != nil { addErrorToCtx(ctx, err) return } animeUsersExtended = append(animeUsersExtended, AnimeUserExtended{ Anime: a.Anime, Users: a.Users, Data: *data, Appointments: apps, }) } 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 !strings.Contains(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 } // check user discord id if register.DiscordID != 0 && discc != nil { _, err = discc.User(fmt.Sprint(register.DiscordID != 0)) if err != nil { ctx.WriteString("Discord user id is kaputt") ctx.SetStatusCode(fasthttp.StatusExpectationFailed) return } } // REGISTER user := UserData{ Username: register.Username, MalID: register.MalID, Secret: register.Secret, DiscordID: register.DiscordID, } 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 RegisterUpdate(ctx *fasthttp.RequestCtx) { auth := ctx.Request.Header.Peek("X-HUSO-AUTH") if auth == nil || string(auth) == "" || !strings.Contains(string(ctx.Request.Header.ContentType()), "application/json") { ctx.SetStatusCode(fasthttp.StatusBadRequest) return } body := ctx.PostBody() var regUpdate RegisterData err := json.Unmarshal(body, ®Update) if err != nil { ctx.WriteString(err.Error()) ctx.SetStatusCode(fasthttp.StatusBadRequest) return } if regUpdate.MalID == 0 || regUpdate.Username == "" || regUpdate.Sauce == "" { ctx.WriteString("Sprich JSON du Hurensohn") ctx.SetStatusCode(fasthttp.StatusBadRequest) return } legit, _ := GheddoAuth(regUpdate.Username, string(auth)) if !legit { ctx.SetStatusCode(fasthttp.StatusUnauthorized) return } calcSauce := Sauce(regUpdate.MalID, regUpdate.Username) if calcSauce != strings.ToLower(regUpdate.Sauce) { ctx.WriteString("Möge die Sauce mit dir sein") ctx.SetStatusCode(fasthttp.StatusBadRequest) return } // check user exists user, err := ReadUser(regUpdate.Username) if err != nil { ctx.WriteString("Dich gibts hier nicht wtf") ctx.SetStatusCode(fasthttp.StatusNotFound) return } if regUpdate.MalID != user.MalID { ctx.WriteString("MAL id ändern is nich") ctx.SetStatusCode(fasthttp.StatusBadRequest) return } if regUpdate.Secret != "" { user.Secret = regUpdate.Secret } // check user discord id if regUpdate.DiscordID != 0 && discc != nil { _, err = discc.User(fmt.Sprint(regUpdate.DiscordID)) if err != nil { ctx.WriteString("Discord user id is kaputt") ctx.SetStatusCode(fasthttp.StatusExpectationFailed) return } } user.DiscordID = regUpdate.DiscordID 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 AppointmentPost(ctx *fasthttp.RequestCtx) { processUpdateAppointmentReq(ctx, true) } func ChatPost(ctx *fasthttp.RequestCtx) { auth := ctx.Request.Header.Peek("X-HUSO-AUTH") if ctx.UserValue("id") == nil || ctx.UserValue("user") == nil || auth == nil || string(auth) == "" || !strings.Contains(string(ctx.Request.Header.ContentType()), "text/plain") { ctx.SetStatusCode(fasthttp.StatusBadRequest) return } username := fmt.Sprintf("%s", ctx.UserValue("user")) legit, _ := GheddoAuth(username, string(auth)) if !legit { ctx.SetStatusCode(fasthttp.StatusUnauthorized) return } animeId, err := strconv.ParseInt(fmt.Sprintf("%s", ctx.UserValue("id")), 10, 64) if err != nil { ctx.WriteString(err.Error()) ctx.SetStatusCode(fasthttp.StatusBadRequest) return } found, err := CheckAnimeExistInDb(animeId) if err != nil { addErrorToCtx(ctx, err) return } if !found { ctx.SetStatusCode(fasthttp.StatusNotFound) return } sent := strings.TrimSpace(string(ctx.PostBody())) if sent == "" { ctx.SetStatusCode(fasthttp.StatusBadRequest) return } text, err := SaveChat(animeId, username, sent) if err != nil { addErrorToCtx(ctx, err) return } _, err = ctx.WriteString(text) if err != nil { addErrorToCtx(ctx, err) return } ctx.SetContentType("text/plain; charset=utf-8") ctx.SetStatusCode(fasthttp.StatusOK) } func WatchPost(ctx *fasthttp.RequestCtx) { processUpdateReq(ctx, true) } func AppointmentDelete(ctx *fasthttp.RequestCtx) { processUpdateAppointmentReq(ctx, false) } func UnRegister(ctx *fasthttp.RequestCtx) { auth := ctx.Request.Header.Peek("X-HUSO-AUTH") if auth == nil || string(auth) == "" || !strings.Contains(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 } legit, _ := GheddoAuth(register.Username, string(auth)) if !legit { ctx.SetStatusCode(fasthttp.StatusUnauthorized) 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 { if err != nutsdb.ErrBucketEmpty { addErrorToCtx(ctx, err) return } } err = DeleteUserFromAppointmens(register.Username) if err != nil { if err != nutsdb.ErrBucketEmpty { addErrorToCtx(ctx, err) 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 } err = nil 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) == "" || !strings.Contains(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 { if err != nil { ctx.WriteString(err.Error()) } ctx.SetStatusCode(fasthttp.StatusBadRequest) return } // iterate sent animes for i, anime := range animes { // anime holen sie diese _, err = SearchAnime(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 // get watch progress progress, updated, _, listState, _ := FetchProgress(anime.Anime, username) // anime is already completed big baka user if listState == malApiStatusC { ctx.WriteString("Du hast schon fertig geschaut bro") ctx.SetStatusCode(fasthttp.StatusConflict) return } // anime is already dropped big baka user if listState == malApiStatusD { ctx.WriteString("Du hast schon gedropped bro") ctx.SetStatusCode(fasthttp.StatusConflict) return } animeData, err = AddUserToAnime(username, userId, anime.Anime, progress, updated) } else { // anime exists => 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 processUpdateAppointmentReq(ctx *fasthttp.RequestCtx, update bool) { auth := ctx.Request.Header.Peek("X-HUSO-AUTH") if ctx.UserValue("user") == nil || auth == nil || string(auth) == "" || !strings.Contains(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 appoints []Appointment err := json.Unmarshal(body, &appoints) if err != nil || len(appoints) == 0 { if err != nil { ctx.WriteString(err.Error()) } ctx.SetStatusCode(fasthttp.StatusBadRequest) return } // iterate sent appointments for i, appointment := range appoints { var appData *Appointment var found bool fresh := false if update { // kann sich (noch) nicht in der Vergagenheit verabreden if appointment.Time.Before(time.Now()) { ctx.WriteString("kann sich (noch) nicht in der Vergagenheit verabreden") ctx.SetStatusCode(fasthttp.StatusBadRequest) return } // check if user is a troll found, err = CheckAnimeExistInDbAndUserWatches(appointment.Anime, userId) if err != nil { addErrorToCtx(ctx, err) return } if !found { ctx.WriteString("Anime gibts net oder du schaust net") ctx.SetStatusCode(fasthttp.StatusNotFound) return } // save appointment and get list appData, fresh, err = AddUserToAppointment(username, appointment.Anime, appointment.Time) } else { found, err = CheckAnimeExistInDb(appointment.Anime) if err != nil { addErrorToCtx(ctx, err) return } if !found { ctx.WriteString("Anime gibts net") ctx.SetStatusCode(fasthttp.StatusNotFound) return } // anime exists => delete appData, err = DeleteUserFromAppointment(username, appointment.Anime, appointment.Time) } if err != nil { addErrorToCtx(ctx, err) return } appoints[i].Users = appData.Users if update && fresh { SendAppointBroadcast(username, appData) } } data, err := json.Marshal(appoints) 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) }