aboutsummaryrefslogtreecommitdiff
path: root/route/routes
diff options
context:
space:
mode:
authorFChannel <>2022-05-22 14:08:36 -0700
committerFChannel <>2022-06-19 12:53:29 -0700
commita66b676481d273508927e64a22e388dc302890ba (patch)
tree7c67b04dd8b39125526567ae6f08a39d0346d260 /route/routes
parent6a0f664b565716ad08301e7699d6c0393dbba977 (diff)
route organization
Diffstat (limited to 'route/routes')
-rw-r--r--route/routes/actor.go680
-rw-r--r--route/routes/admin.go291
-rw-r--r--route/routes/api.go55
-rw-r--r--route/routes/boardmgmt.go47
-rw-r--r--route/routes/main.go100
-rw-r--r--route/routes/news.go73
-rw-r--r--route/routes/webfinger.go58
7 files changed, 1304 insertions, 0 deletions
diff --git a/route/routes/actor.go b/route/routes/actor.go
new file mode 100644
index 0000000..e27133b
--- /dev/null
+++ b/route/routes/actor.go
@@ -0,0 +1,680 @@
+package routes
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "io/ioutil"
+ "mime/multipart"
+ "net/http"
+ "regexp"
+ "strconv"
+
+ "github.com/FChannel0/FChannel-Server/activitypub"
+ "github.com/FChannel0/FChannel-Server/config"
+ "github.com/FChannel0/FChannel-Server/post"
+ "github.com/FChannel0/FChannel-Server/route"
+ "github.com/FChannel0/FChannel-Server/util"
+ "github.com/FChannel0/FChannel-Server/webfinger"
+ "github.com/gofiber/fiber/v2"
+)
+
+func ActorInbox(ctx *fiber.Ctx) error {
+ activity, err := activitypub.GetActivityFromJson(ctx)
+ if err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+
+ if activity.Actor.PublicKey.Id == "" {
+ nActor, err := activitypub.FingerActor(activity.Actor.Id)
+ if err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+
+ activity.Actor = &nActor
+ }
+
+ if !activity.Actor.VerifyHeaderSignature(ctx) {
+ response := activity.Reject()
+ return response.MakeRequestInbox()
+ }
+
+ switch activity.Type {
+ case "Create":
+ for _, e := range activity.To {
+ actor := activitypub.Actor{Id: e}
+ if res, err := actor.IsLocal(); err == nil && res {
+ if res, err := activity.Actor.IsLocal(); err == nil && res {
+ reqActivity := activitypub.Activity{Id: activity.Object.Id}
+ col, err := reqActivity.GetCollection()
+ if err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+
+ if len(col.OrderedItems) < 1 {
+ break
+ }
+
+ if err := activity.Object.WriteCache(); err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+
+ actor, err := activitypub.GetActorFromDB(e)
+ if err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+
+ if err := actor.ArchivePosts(); err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+
+ //SendToFollowers(e, activity)
+ } else if err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+ } else if err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+ }
+
+ break
+
+ case "Delete":
+ for _, e := range activity.To {
+ actor, err := activitypub.GetActorFromDB(e)
+ if err != nil {
+ return util.MakeError(err, "")
+ }
+
+ if actor.Id != "" && actor.Id != config.Domain {
+ if activity.Object.Replies.OrderedItems != nil {
+ for _, k := range activity.Object.Replies.OrderedItems {
+ if err := k.Tombstone(); err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+ }
+ }
+
+ if err := activity.Object.Tombstone(); err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+ if err := actor.UnArchiveLast(); err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+ break
+ }
+ }
+ break
+
+ case "Follow":
+ for _, e := range activity.To {
+ if _, err := activitypub.GetActorFromDB(e); err == nil {
+ response := activity.AcceptFollow()
+ response, err := response.SetActorFollower()
+
+ if err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+
+ if err := response.MakeRequestInbox(); err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+
+ alreadyFollowing, err := response.Actor.IsAlreadyFollowing(response.Object.Id)
+
+ if err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+
+ objActor, err := activitypub.FingerActor(response.Object.Actor)
+
+ if err != nil || objActor.Id == "" {
+ return util.MakeError(err, "ActorInbox")
+ }
+
+ reqActivity := activitypub.Activity{Id: objActor.Following}
+ remoteActorFollowingCol, err := reqActivity.GetCollection()
+
+ if err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+
+ alreadyFollow := false
+
+ for _, e := range remoteActorFollowingCol.Items {
+ if e.Id == response.Actor.Id {
+ alreadyFollowing = true
+ }
+ }
+
+ autoSub, err := response.Actor.GetAutoSubscribe()
+
+ if err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+
+ if autoSub && !alreadyFollow && alreadyFollowing {
+ followActivity, err := response.Actor.MakeFollowActivity(response.Object.Actor)
+
+ if err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+
+ if err := followActivity.MakeRequestOutbox(); err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+ }
+ } else if err != nil {
+ return util.MakeError(err, "ActorInbox")
+ } else {
+ config.Log.Println("follow request for rejected")
+ response := activity.Reject()
+ return response.MakeRequestInbox()
+ }
+ }
+ break
+
+ case "Reject":
+ if activity.Object.Object.Type == "Follow" {
+ config.Log.Println("follow rejected")
+ if _, err := activity.SetActorFollowing(); err != nil {
+ return util.MakeError(err, "ActorInbox")
+ }
+ }
+ break
+ }
+
+ return nil
+}
+
+func ActorOutbox(ctx *fiber.Ctx) error {
+ //var activity activitypub.Activity
+ actor, err := webfinger.GetActorFromPath(ctx.Path(), "/")
+ if err != nil {
+ return util.MakeError(err, "ActorOutbox")
+ }
+
+ if activitypub.AcceptActivity(ctx.Get("Accept")) {
+ actor.GetOutbox(ctx)
+ return nil
+ }
+
+ return route.ParseOutboxRequest(ctx, actor)
+}
+
+func ActorFollowing(ctx *fiber.Ctx) error {
+ actor, _ := activitypub.GetActorFromDB(config.Domain + "/" + ctx.Params("actor"))
+ return actor.GetFollowingResp(ctx)
+}
+
+func ActorFollowers(ctx *fiber.Ctx) error {
+ actor, _ := activitypub.GetActorFromDB(config.Domain + "/" + ctx.Params("actor"))
+ return actor.GetFollowersResp(ctx)
+}
+
+func ActorReported(c *fiber.Ctx) error {
+ // STUB
+
+ return c.SendString("actor reported")
+}
+
+func ActorArchive(c *fiber.Ctx) error {
+ // STUB
+
+ return c.SendString("actor archive")
+}
+
+func ActorPost(ctx *fiber.Ctx) error {
+ header, _ := ctx.FormFile("file")
+
+ if ctx.FormValue("inReplyTo") == "" && header == nil {
+ return ctx.Render("403", fiber.Map{
+ "message": "Media is required for new posts",
+ })
+ }
+
+ var file multipart.File
+
+ if header != nil {
+ file, _ = header.Open()
+ }
+
+ if file != nil && header.Size > (7<<20) {
+ return ctx.Render("403", fiber.Map{
+ "message": "7MB max file size",
+ })
+ }
+
+ if is, _ := util.IsPostBlacklist(ctx.FormValue("comment")); is {
+ errors.New("\n\nBlacklist post blocked\n\n")
+ return ctx.Redirect("/", 301)
+ }
+
+ if ctx.FormValue("inReplyTo") == "" || file == nil {
+ if ctx.FormValue("comment") == "" && ctx.FormValue("subject") == "" {
+ return ctx.Render("403", fiber.Map{
+ "message": "Comment or Subject required",
+ })
+ }
+ }
+
+ if len(ctx.FormValue("comment")) > 2000 {
+ return ctx.Render("403", fiber.Map{
+ "message": "Comment limit 2000 characters",
+ })
+ }
+
+ if len(ctx.FormValue("subject")) > 100 || len(ctx.FormValue("name")) > 100 || len(ctx.FormValue("options")) > 100 {
+ return ctx.Render("403", fiber.Map{
+ "message": "Name, Subject or Options limit 100 characters",
+ })
+ }
+
+ if ctx.FormValue("captcha") == "" {
+ return ctx.Render("403", fiber.Map{
+ "message": "Incorrect Captcha",
+ })
+ }
+
+ b := bytes.Buffer{}
+ we := multipart.NewWriter(&b)
+
+ if file != nil {
+ var fw io.Writer
+
+ fw, err := we.CreateFormFile("file", header.Filename)
+
+ if err != nil {
+ errors.New("error with form file create")
+ }
+ _, err = io.Copy(fw, file)
+
+ if err != nil {
+ errors.New("error with form file copy")
+ }
+ }
+
+ reply, _ := post.ParseCommentForReply(ctx.FormValue("comment"))
+
+ form, _ := ctx.MultipartForm()
+
+ for key, r0 := range form.Value {
+ if key == "captcha" {
+ err := we.WriteField(key, ctx.FormValue("captchaCode")+":"+ctx.FormValue("captcha"))
+ if err != nil {
+ errors.New("error with writing captcha field")
+ }
+ } else if key == "name" {
+ name, tripcode, _ := post.CreateNameTripCode(ctx)
+
+ err := we.WriteField(key, name)
+ if err != nil {
+ errors.New("error with writing name field")
+ }
+
+ err = we.WriteField("tripcode", tripcode)
+ if err != nil {
+ errors.New("error with writing tripcode field")
+ }
+ } else {
+ err := we.WriteField(key, r0[0])
+ if err != nil {
+ errors.New("error with writing field")
+ }
+ }
+ }
+
+ if ctx.FormValue("inReplyTo") == "" && reply != "" {
+ err := we.WriteField("inReplyTo", reply)
+ if err != nil {
+ errors.New("error with writing inReplyTo field")
+ }
+ }
+
+ we.Close()
+
+ sendTo := ctx.FormValue("sendTo")
+
+ req, err := http.NewRequest("POST", sendTo, &b)
+
+ if err != nil {
+ errors.New("error with post form req")
+ }
+
+ req.Header.Set("Content-Type", we.FormDataContentType())
+
+ resp, err := util.RouteProxy(req)
+
+ if err != nil {
+ errors.New("error with post form resp")
+ }
+
+ defer resp.Body.Close()
+ body, _ := ioutil.ReadAll(resp.Body)
+
+ if resp.StatusCode == 200 {
+
+ var obj activitypub.ObjectBase
+
+ obj = post.ParseOptions(ctx, obj)
+ for _, e := range obj.Option {
+ if e == "noko" || e == "nokosage" {
+ return ctx.Redirect(config.Domain+"/"+ctx.FormValue("boardName")+"/"+util.ShortURL(ctx.FormValue("sendTo"), string(body)), 301)
+ }
+ }
+
+ if ctx.FormValue("returnTo") == "catalog" {
+ return ctx.Redirect(config.Domain+"/"+ctx.FormValue("boardName")+"/catalog", 301)
+ } else {
+ return ctx.Redirect(config.Domain+"/"+ctx.FormValue("boardName"), 301)
+ }
+ }
+
+ if resp.StatusCode == 403 {
+ return ctx.Render("403", fiber.Map{
+ "message": string(body),
+ })
+ }
+
+ return ctx.Redirect(config.Domain+"/"+ctx.FormValue("boardName"), 301)
+}
+
+func ActorPostGet(ctx *fiber.Ctx) error {
+
+ actor, err := activitypub.GetActorByNameFromDB(ctx.Params("actor"))
+ if err != nil {
+ return nil
+ }
+
+ // this is a activitpub json request return json instead of html page
+ if activitypub.AcceptActivity(ctx.Get("Accept")) {
+ route.GetActorPost(ctx, ctx.Path())
+ return nil
+ }
+
+ re := regexp.MustCompile("\\w+$")
+ postId := re.FindString(ctx.Path())
+
+ inReplyTo := actor.Id + "/" + postId
+
+ var data route.PageData
+
+ re = regexp.MustCompile("f(\\w|[!@#$%^&*<>])+-(\\w|[!@#$%^&*<>])+")
+
+ if re.MatchString(ctx.Path()) { // if non local actor post
+ name := activitypub.GetActorFollowNameFromPath(ctx.Path())
+
+ followActors, err := actor.GetFollowFromName(name)
+ if err != nil {
+ return util.MakeError(err, "PostGet")
+ }
+
+ followCollection, err := activitypub.GetActorsFollowPostFromId(followActors, postId)
+ if err != nil {
+ return util.MakeError(err, "PostGet")
+ }
+
+ if len(followCollection.OrderedItems) > 0 {
+ data.Board.InReplyTo = followCollection.OrderedItems[0].Id
+ data.Posts = append(data.Posts, followCollection.OrderedItems[0])
+
+ actor, err := activitypub.FingerActor(data.Board.InReplyTo)
+ if err != nil {
+ return util.MakeError(err, "PostGet")
+ }
+
+ data.Board.Post.Actor = actor.Id
+ }
+ } else {
+ obj := activitypub.ObjectBase{Id: inReplyTo}
+ collection, err := obj.GetCollectionFromPath()
+ if err != nil {
+ return util.MakeError(err, "PostGet")
+ }
+
+ if collection.Actor.Id != "" {
+ data.Board.Post.Actor = collection.Actor.Id
+ data.Board.InReplyTo = inReplyTo
+ }
+
+ if len(collection.OrderedItems) > 0 {
+ data.Posts = append(data.Posts, collection.OrderedItems[0])
+ }
+ }
+
+ if len(data.Posts) > 0 {
+ data.PostId = util.ShortURL(data.Board.To, data.Posts[0].Id)
+ }
+
+ data.Board.Name = actor.Name
+ data.Board.PrefName = actor.PreferredUsername
+ data.Board.To = actor.Outbox
+ data.Board.Actor = actor
+ data.Board.Summary = actor.Summary
+ data.Board.ModCred, _ = util.GetPasswordFromSession(ctx)
+ data.Board.Domain = config.Domain
+ data.Board.Restricted = actor.Restricted
+ data.ReturnTo = "feed"
+
+ capt, err := util.GetRandomCaptcha()
+ if err != nil {
+ return util.MakeError(err, "PostGet")
+ }
+ data.Board.Captcha = config.Domain + "/" + capt
+ data.Board.CaptchaCode = post.GetCaptchaCode(data.Board.Captcha)
+
+ data.Instance, err = activitypub.GetActorFromDB(config.Domain)
+ if err != nil {
+ return util.MakeError(err, "PostGet")
+ }
+
+ data.Key = config.Key
+ data.Boards = webfinger.Boards
+
+ data.Title = "/" + data.Board.Name + "/ - " + data.PostId
+
+ if len(data.Posts) > 0 {
+ data.Meta.Description = data.Posts[0].Content
+ data.Meta.Url = data.Posts[0].Id
+ data.Meta.Title = data.Posts[0].Name
+ data.Meta.Preview = data.Posts[0].Preview.Href
+ }
+
+ data.Themes = &config.Themes
+ data.ThemeCookie = route.GetThemeCookie(ctx)
+
+ return ctx.Render("npost", fiber.Map{
+ "page": data,
+ }, "layouts/main")
+}
+
+func ActorCatalogGet(ctx *fiber.Ctx) error {
+ actorName := ctx.Params("actor")
+ actor, err := activitypub.GetActorByNameFromDB(actorName)
+ if err != nil {
+ return util.MakeError(err, "CatalogGet")
+ }
+
+ collection, err := actor.GetCatalogCollection()
+
+ // TODO: implement this in template functions
+ // "showArchive": func() bool {
+ // col, err := db.GetActorCollectionDBTypeLimit(collection.Actor.Id, "Archive", 1)
+ // if err != nil {
+ // // TODO: figure out what to do here
+ // panic(err)
+ // }
+ //
+ // if len(col.OrderedItems) > 0 {
+ // return true
+ // }
+ // return false
+ //},
+
+ var data route.PageData
+ data.Board.Name = actor.Name
+ data.Board.PrefName = actor.PreferredUsername
+ data.Board.InReplyTo = ""
+ data.Board.To = actor.Outbox
+ data.Board.Actor = actor
+ data.Board.Summary = actor.Summary
+ data.Board.ModCred, _ = util.GetPasswordFromSession(ctx)
+ data.Board.Domain = config.Domain
+ data.Board.Restricted = actor.Restricted
+ data.Key = config.Key
+ data.ReturnTo = "catalog"
+
+ data.Board.Post.Actor = actor.Id
+
+ data.Instance, err = activitypub.GetActorFromDB(config.Domain)
+ if err != nil {
+ return util.MakeError(err, "CatalogGet")
+ }
+
+ capt, err := util.GetRandomCaptcha()
+ if err != nil {
+ return util.MakeError(err, "CatalogGet")
+ }
+
+ data.Board.Captcha = config.Domain + "/" + capt
+ data.Board.CaptchaCode = post.GetCaptchaCode(data.Board.Captcha)
+
+ data.Title = "/" + data.Board.Name + "/ - catalog"
+
+ data.Boards = webfinger.Boards
+ data.Posts = collection.OrderedItems
+
+ data.Meta.Description = data.Board.Summary
+ data.Meta.Url = data.Board.Actor.Id
+ data.Meta.Title = data.Title
+
+ data.Themes = &config.Themes
+ data.ThemeCookie = route.GetThemeCookie(ctx)
+
+ return ctx.Render("catalog", fiber.Map{
+ "page": data,
+ }, "layouts/main")
+}
+
+func ActorOutboxGet(ctx *fiber.Ctx) error {
+ actor, err := activitypub.GetActorByNameFromDB(ctx.Params("actor"))
+
+ if err != nil {
+ return nil
+ }
+
+ if activitypub.AcceptActivity(ctx.Get("Accept")) {
+ actor.GetInfoResp(ctx)
+ return nil
+ }
+
+ var page int
+ if postNum := ctx.Query("page"); postNum != "" {
+ if page, err = strconv.Atoi(postNum); err != nil {
+ return util.MakeError(err, "OutboxGet")
+ }
+ }
+
+ collection, err := actor.WantToServePage(page)
+ if err != nil {
+ return util.MakeError(err, "OutboxGet")
+ }
+
+ var offset = 15
+ var pages []int
+ pageLimit := (float64(collection.TotalItems) / float64(offset))
+
+ if pageLimit > 11 {
+ pageLimit = 11
+ }
+
+ for i := 0.0; i < pageLimit; i++ {
+ pages = append(pages, int(i))
+ }
+
+ var data route.PageData
+ data.Board.Name = actor.Name
+ data.Board.PrefName = actor.PreferredUsername
+ data.Board.Summary = actor.Summary
+ data.Board.InReplyTo = ""
+ data.Board.To = actor.Outbox
+ data.Board.Actor = actor
+ data.Board.ModCred, _ = util.GetPasswordFromSession(ctx)
+ data.Board.Domain = config.Domain
+ data.Board.Restricted = actor.Restricted
+ data.CurrentPage = page
+ data.ReturnTo = "feed"
+
+ data.Board.Post.Actor = actor.Id
+
+ capt, err := util.GetRandomCaptcha()
+ if err != nil {
+ return util.MakeError(err, "OutboxGet")
+ }
+ data.Board.Captcha = config.Domain + "/" + capt
+ data.Board.CaptchaCode = post.GetCaptchaCode(data.Board.Captcha)
+
+ data.Title = "/" + actor.Name + "/ - " + actor.PreferredUsername
+
+ data.Key = config.Key
+
+ data.Boards = webfinger.Boards
+ data.Posts = collection.OrderedItems
+
+ data.Pages = pages
+ data.TotalPage = len(data.Pages) - 1
+
+ data.Meta.Description = data.Board.Summary
+ data.Meta.Url = data.Board.Actor.Id
+ data.Meta.Title = data.Title
+
+ data.Themes = &config.Themes
+ data.ThemeCookie = route.GetThemeCookie(ctx)
+
+ return ctx.Render("nposts", fiber.Map{
+ "page": data,
+ }, "layouts/main")
+}
+
+func ActorArchiveGet(ctx *fiber.Ctx) error {
+ collection := ctx.Locals("collection").(activitypub.Collection)
+ actor := collection.Actor
+
+ var returnData route.PageData
+ returnData.Board.Name = actor.Name
+ returnData.Board.PrefName = actor.PreferredUsername
+ returnData.Board.InReplyTo = ""
+ returnData.Board.To = actor.Outbox
+ returnData.Board.Actor = actor
+ returnData.Board.Summary = actor.Summary
+ returnData.Board.ModCred, _ = util.GetPasswordFromSession(ctx)
+ returnData.Board.Domain = config.Domain
+ returnData.Board.Restricted = actor.Restricted
+ returnData.Key = config.Key
+ returnData.ReturnTo = "archive"
+
+ returnData.Board.Post.Actor = actor.Id
+
+ var err error
+ returnData.Instance, err = activitypub.GetActorFromDB(config.Domain)
+
+ capt, err := util.GetRandomCaptcha()
+ if err != nil {
+ return util.MakeError(err, "ArchiveGet")
+ }
+ returnData.Board.Captcha = config.Domain + "/" + capt
+ returnData.Board.CaptchaCode = post.GetCaptchaCode(returnData.Board.Captcha)
+
+ returnData.Title = "/" + actor.Name + "/ - " + actor.PreferredUsername
+
+ returnData.Boards = webfinger.Boards
+
+ returnData.Posts = collection.OrderedItems
+
+ returnData.Themes = &config.Themes
+ returnData.ThemeCookie = route.GetThemeCookie(ctx)
+
+ return ctx.Render("archive", fiber.Map{
+ "page": returnData,
+ }, "layouts/main")
+}
diff --git a/route/routes/admin.go b/route/routes/admin.go
new file mode 100644
index 0000000..86e12c6
--- /dev/null
+++ b/route/routes/admin.go
@@ -0,0 +1,291 @@
+package routes
+
+import (
+ "bytes"
+ "encoding/json"
+ "io/ioutil"
+ "net/http"
+ "time"
+
+ "github.com/FChannel0/FChannel-Server/activitypub"
+ "github.com/FChannel0/FChannel-Server/config"
+ "github.com/FChannel0/FChannel-Server/db"
+ "github.com/FChannel0/FChannel-Server/route"
+ "github.com/FChannel0/FChannel-Server/util"
+ "github.com/FChannel0/FChannel-Server/webfinger"
+ "github.com/gofiber/fiber/v2"
+)
+
+func AdminVerify(ctx *fiber.Ctx) error {
+ identifier := ctx.FormValue("id")
+ code := ctx.FormValue("code")
+
+ var verify util.Verify
+ verify.Identifier = identifier
+ verify.Code = code
+
+ j, _ := json.Marshal(&verify)
+
+ req, err := http.NewRequest("POST", config.Domain+"/auth", bytes.NewBuffer(j))
+
+ if err != nil {
+ return util.MakeError(err, "AdminVerify")
+ }
+
+ req.Header.Set("Content-Type", config.ActivityStreams)
+
+ resp, err := http.DefaultClient.Do(req)
+
+ if err != nil {
+ return util.MakeError(err, "AdminVerify")
+ }
+
+ defer resp.Body.Close()
+
+ rBody, _ := ioutil.ReadAll(resp.Body)
+
+ body := string(rBody)
+
+ if resp.StatusCode != 200 {
+ return ctx.Redirect("/"+config.Key, http.StatusPermanentRedirect)
+ }
+
+ ctx.Cookie(&fiber.Cookie{
+ Name: "session_token",
+ Value: body + "|" + verify.Code,
+ Expires: time.Now().UTC().Add(60 * 60 * 48 * time.Second),
+ })
+
+ return ctx.Redirect("/", http.StatusSeeOther)
+}
+
+// TODO remove this route it is mostly unneeded
+func AdminAuth(ctx *fiber.Ctx) error {
+ var verify util.Verify
+
+ err := json.Unmarshal(ctx.Body(), &verify)
+
+ if err != nil {
+ return util.MakeError(err, "AdminAuth")
+ }
+
+ v, _ := util.GetVerificationByCode(verify.Code)
+
+ if v.Identifier == verify.Identifier {
+ _, err := ctx.Write([]byte(v.Board))
+ return util.MakeError(err, "AdminAuth")
+ }
+
+ ctx.Response().Header.SetStatusCode(http.StatusBadRequest)
+ _, err = ctx.Write([]byte(""))
+
+ return util.MakeError(err, "AdminAuth")
+}
+
+func AdminIndex(ctx *fiber.Ctx) error {
+ id, _ := util.GetPasswordFromSession(ctx)
+ actor, _ := webfinger.GetActorFromPath(ctx.Path(), "/"+config.Key+"/")
+
+ if actor.Id == "" {
+ actor, _ = activitypub.GetActorByNameFromDB(config.Domain)
+ }
+
+ if id == "" || (id != actor.Id && id != config.Domain) {
+ return ctx.Render("verify", fiber.Map{})
+ }
+
+ actor, err := activitypub.GetActor(config.Domain)
+
+ if err != nil {
+ return util.MakeError(err, "AdminIndex")
+ }
+
+ reqActivity := activitypub.Activity{Id: actor.Following}
+ follow, _ := reqActivity.GetCollection()
+ follower, _ := reqActivity.GetCollection()
+
+ var following []string
+ var followers []string
+
+ for _, e := range follow.Items {
+ following = append(following, e.Id)
+ }
+
+ for _, e := range follower.Items {
+ followers = append(followers, e.Id)
+ }
+
+ var adminData route.AdminPage
+ adminData.Following = following
+ adminData.Followers = followers
+ adminData.Actor = actor.Id
+ adminData.Key = config.Key
+ adminData.Domain = config.Domain
+ adminData.Board.ModCred, _ = util.GetPasswordFromSession(ctx)
+ adminData.Title = actor.Name + " Admin page"
+
+ adminData.Boards = webfinger.Boards
+
+ adminData.Board.Post.Actor = actor.Id
+
+ adminData.PostBlacklist, _ = util.GetRegexBlacklist()
+
+ adminData.Themes = &config.Themes
+
+ return ctx.Render("admin", fiber.Map{
+ "page": adminData,
+ })
+}
+
+func AdminFollow(ctx *fiber.Ctx) error {
+ follow := ctx.FormValue("follow")
+ actorId := ctx.FormValue("actor")
+
+ actor := activitypub.Actor{Id: actorId}
+ followActivity, _ := actor.MakeFollowActivity(follow)
+
+ objActor := activitypub.Actor{Id: followActivity.Object.Actor}
+
+ if isLocal, _ := objActor.IsLocal(); !isLocal && followActivity.Actor.Id == config.Domain {
+ _, err := ctx.Write([]byte("main board can only follow local boards. Create a new board and then follow outside boards from it."))
+ return util.MakeError(err, "AdminIndex")
+ }
+
+ if actor, _ := activitypub.FingerActor(follow); actor.Id != "" {
+ if err := followActivity.MakeRequestOutbox(); err != nil {
+ return util.MakeError(err, "AdminFollow")
+ }
+ }
+
+ var redirect string
+ actor, _ = webfinger.GetActorFromPath(ctx.Path(), "/"+config.Key+"/")
+
+ if actor.Name != "main" {
+ redirect = actor.Name
+ }
+
+ return ctx.Redirect("/"+config.Key+"/"+redirect, http.StatusSeeOther)
+}
+
+func AdminAddBoard(ctx *fiber.Ctx) error {
+ actor, _ := activitypub.GetActorFromDB(config.Domain)
+
+ if hasValidation := actor.HasValidation(ctx); !hasValidation {
+ return nil
+ }
+
+ var newActorActivity activitypub.Activity
+ var board activitypub.Actor
+
+ var restrict bool
+ if ctx.FormValue("restricted") == "True" {
+ restrict = true
+ } else {
+ restrict = false
+ }
+
+ board.Name = ctx.FormValue("name")
+ board.PreferredUsername = ctx.FormValue("prefname")
+ board.Summary = ctx.FormValue("summary")
+ board.Restricted = restrict
+
+ newActorActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+ newActorActivity.Type = "New"
+
+ var nobj activitypub.ObjectBase
+ newActorActivity.Actor = &actor
+ newActorActivity.Object = &nobj
+
+ newActorActivity.Object.Alias = board.Name
+ newActorActivity.Object.Name = board.PreferredUsername
+ newActorActivity.Object.Summary = board.Summary
+ newActorActivity.Object.Sensitive = board.Restricted
+
+ newActorActivity.MakeRequestOutbox()
+ return ctx.Redirect("/"+config.Key, http.StatusSeeOther)
+}
+
+func AdminPostNews(c *fiber.Ctx) error {
+ // STUB
+
+ return c.SendString("admin post news")
+}
+
+func AdminNewsDelete(c *fiber.Ctx) error {
+ // STUB
+
+ return c.SendString("admin news delete")
+}
+
+func AdminActorIndex(ctx *fiber.Ctx) error {
+ actor, _ := webfinger.GetActorFromPath(ctx.Path(), "/"+config.Key+"/")
+
+ reqActivity := activitypub.Activity{Id: actor.Following}
+ follow, _ := reqActivity.GetCollection()
+
+ reqActivity.Id = actor.Followers
+ follower, _ := reqActivity.GetCollection()
+
+ reqActivity.Id = actor.Id + "/reported"
+ reported, _ := activitypub.GetActorCollectionReq(reqActivity.Id)
+
+ var following []string
+ var followers []string
+ var reports []db.Report
+
+ for _, e := range follow.Items {
+ following = append(following, e.Id)
+ }
+
+ for _, e := range follower.Items {
+ followers = append(followers, e.Id)
+ }
+
+ for _, e := range reported.Items {
+ var r db.Report
+ r.Count = int(e.Size)
+ r.ID = e.Id
+ r.Reason = e.Content
+ reports = append(reports, r)
+ }
+
+ localReports, _ := db.GetLocalReport(actor.Name)
+
+ for _, e := range localReports {
+ var r db.Report
+ r.Count = e.Count
+ r.ID = e.ID
+ r.Reason = e.Reason
+ reports = append(reports, r)
+ }
+
+ var data route.AdminPage
+ data.Following = following
+ data.Followers = followers
+ data.Reported = reports
+ data.Domain = config.Domain
+ data.IsLocal, _ = actor.IsLocal()
+
+ data.Title = "Manage /" + actor.Name + "/"
+ data.Boards = webfinger.Boards
+ data.Board.Name = actor.Name
+ data.Board.Actor = actor
+ data.Key = config.Key
+ data.Board.TP = config.TP
+
+ data.Board.Post.Actor = actor.Id
+
+ data.AutoSubscribe, _ = actor.GetAutoSubscribe()
+
+ data.Themes = &config.Themes
+
+ data.RecentPosts, _ = actor.GetRecentPosts()
+
+ if cookie := ctx.Cookies("theme"); cookie != "" {
+ data.ThemeCookie = cookie
+ }
+
+ return ctx.Render("manage", fiber.Map{
+ "page": data,
+ })
+}
diff --git a/route/routes/api.go b/route/routes/api.go
new file mode 100644
index 0000000..080d88d
--- /dev/null
+++ b/route/routes/api.go
@@ -0,0 +1,55 @@
+package routes
+
+import (
+ "io/ioutil"
+ "net/http"
+ "time"
+
+ "github.com/FChannel0/FChannel-Server/config"
+ "github.com/FChannel0/FChannel-Server/util"
+ "github.com/gofiber/fiber/v2"
+)
+
+func Media(c *fiber.Ctx) error {
+ if c.Query("hash") != "" {
+ return RouteImages(c, c.Query("hash"))
+ }
+
+ return c.SendStatus(404)
+}
+
+func RouteImages(ctx *fiber.Ctx, media string) error {
+ req, err := http.NewRequest("GET", config.MediaHashs[media], nil)
+ if err != nil {
+ return util.MakeError(err, "RouteImages")
+ }
+
+ client := http.Client{
+ Timeout: 5 * time.Second,
+ }
+
+ resp, err := client.Do(req)
+ if err != nil {
+ return util.MakeError(err, "RouteImages")
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != 200 {
+ fileBytes, err := ioutil.ReadFile("./static/notfound.png")
+ if err != nil {
+ return util.MakeError(err, "RouteImages")
+ }
+
+ _, err = ctx.Write(fileBytes)
+ return util.MakeError(err, "RouteImages")
+ }
+
+ body, _ := ioutil.ReadAll(resp.Body)
+ for name, values := range resp.Header {
+ for _, value := range values {
+ ctx.Append(name, value)
+ }
+ }
+
+ return ctx.Send(body)
+}
diff --git a/route/routes/boardmgmt.go b/route/routes/boardmgmt.go
new file mode 100644
index 0000000..15b2686
--- /dev/null
+++ b/route/routes/boardmgmt.go
@@ -0,0 +1,47 @@
+package routes
+
+import "github.com/gofiber/fiber/v2"
+
+func BoardBanMedia(ctx *fiber.Ctx) error {
+ return ctx.SendString("board ban media")
+}
+
+func BoardDelete(ctx *fiber.Ctx) error {
+ return ctx.SendString("board delete")
+}
+
+func BoardDeleteAttach(ctx *fiber.Ctx) error {
+ return ctx.SendString("board delete attach")
+}
+
+func BoardMarkSensitive(ctx *fiber.Ctx) error {
+ return ctx.SendString("board mark sensitive")
+}
+
+func BoardRemove(ctx *fiber.Ctx) error {
+ return ctx.SendString("board remove")
+}
+
+func BoardRemoveAttach(ctx *fiber.Ctx) error {
+ return ctx.SendString("board remove attach")
+}
+
+func BoardAddToIndex(ctx *fiber.Ctx) error {
+ return ctx.SendString("board add to index")
+}
+
+func BoardPopArchive(ctx *fiber.Ctx) error {
+ return ctx.SendString("board pop archive")
+}
+
+func BoardAutoSubscribe(ctx *fiber.Ctx) error {
+ return ctx.SendString("board auto subscribe")
+}
+
+func BoardBlacklist(ctx *fiber.Ctx) error {
+ return ctx.SendString("board blacklist")
+}
+
+func BoardReport(ctx *fiber.Ctx) error {
+ return ctx.SendString("board report")
+}
diff --git a/route/routes/main.go b/route/routes/main.go
new file mode 100644
index 0000000..99dad31
--- /dev/null
+++ b/route/routes/main.go
@@ -0,0 +1,100 @@
+package routes
+
+import (
+ "github.com/FChannel0/FChannel-Server/activitypub"
+ "github.com/FChannel0/FChannel-Server/config"
+ "github.com/FChannel0/FChannel-Server/db"
+ "github.com/FChannel0/FChannel-Server/route"
+ "github.com/FChannel0/FChannel-Server/util"
+ "github.com/FChannel0/FChannel-Server/webfinger"
+ "github.com/gofiber/fiber/v2"
+)
+
+func Index(ctx *fiber.Ctx) error {
+ actor, err := activitypub.GetActorFromDB(config.Domain)
+ if err != nil {
+ return util.MakeError(err, "Index")
+ }
+
+ // this is a activitpub json request return json instead of html page
+ if activitypub.AcceptActivity(ctx.Get("Accept")) {
+ actor.GetInfoResp(ctx)
+ return nil
+ }
+
+ var data route.PageData
+
+ reqActivity := activitypub.Activity{Id: "https://fchan.xyz/followers"}
+ col, err := reqActivity.GetCollection()
+ if err != nil {
+ return util.MakeError(err, "Index")
+ }
+
+ if len(col.Items) > 0 {
+ data.InstanceIndex = col.Items
+ }
+
+ data.NewsItems, err = db.GetNews(3)
+ if err != nil {
+ return util.MakeError(err, "Index")
+ }
+
+ data.Title = "Welcome to " + actor.PreferredUsername
+ data.PreferredUsername = actor.PreferredUsername
+ data.Boards = webfinger.Boards
+ data.Board.Name = ""
+ data.Key = config.Key
+ data.Board.Domain = config.Domain
+ data.Board.ModCred, _ = util.GetPasswordFromSession(ctx)
+ data.Board.Actor = actor
+ data.Board.Post.Actor = actor.Id
+ data.Board.Restricted = actor.Restricted
+ //almost certainly there is a better algorithm for this but the old one was wrong
+ //and I suck at math. This works at least.
+ data.BoardRemainer = make([]int, 3-(len(data.Boards)%3))
+
+ if len(data.BoardRemainer) == 3 {
+ data.BoardRemainer = make([]int, 0)
+ }
+
+ data.Meta.Description = data.PreferredUsername + " a federated image board based on ActivityPub. The current version of the code running on the server is still a work-in-progress product, expect a bumpy ride for the time being. Get the server code here: https://github.com/FChannel0."
+ data.Meta.Url = data.Board.Domain
+ data.Meta.Title = data.Title
+
+ data.Themes = &config.Themes
+ data.ThemeCookie = route.GetThemeCookie(ctx)
+
+ return ctx.Render("index", fiber.Map{
+ "page": data,
+ }, "layouts/main")
+}
+
+func Inbox(ctx *fiber.Ctx) error {
+ // TODO main actor Inbox route
+ return ctx.SendString("main inbox")
+}
+
+func Outbox(ctx *fiber.Ctx) error {
+ actor, err := webfinger.GetActorFromPath(ctx.Path(), "/")
+
+ if err != nil {
+ return util.MakeError(err, "Outbox")
+ }
+
+ if activitypub.AcceptActivity(ctx.Get("Accept")) {
+ actor.GetOutbox(ctx)
+ return nil
+ }
+
+ return route.ParseOutboxRequest(ctx, actor)
+}
+
+func Following(ctx *fiber.Ctx) error {
+ actor, _ := activitypub.GetActorFromDB(config.Domain)
+ return actor.GetFollowingResp(ctx)
+}
+
+func Followers(ctx *fiber.Ctx) error {
+ actor, _ := activitypub.GetActorFromDB(config.Domain)
+ return actor.GetFollowersResp(ctx)
+}
diff --git a/route/routes/news.go b/route/routes/news.go
new file mode 100644
index 0000000..0d226a5
--- /dev/null
+++ b/route/routes/news.go
@@ -0,0 +1,73 @@
+package routes
+
+import (
+ "github.com/FChannel0/FChannel-Server/activitypub"
+ "github.com/FChannel0/FChannel-Server/config"
+ "github.com/FChannel0/FChannel-Server/db"
+ "github.com/FChannel0/FChannel-Server/route"
+ "github.com/FChannel0/FChannel-Server/util"
+ "github.com/FChannel0/FChannel-Server/webfinger"
+ "github.com/gofiber/fiber/v2"
+)
+
+func NewsGet(ctx *fiber.Ctx) error {
+ timestamp := 0
+
+ actor, err := activitypub.GetActorFromDB(config.Domain)
+ if err != nil {
+ return util.MakeError(err, "NewsGet")
+ }
+
+ var data route.PageData
+ data.PreferredUsername = actor.PreferredUsername
+ data.Boards = webfinger.Boards
+ data.Board.Name = ""
+ data.Key = config.Key
+ data.Board.Domain = config.Domain
+ data.Board.ModCred, _ = util.GetPasswordFromSession(ctx)
+ data.Board.Actor = actor
+ data.Board.Post.Actor = actor.Id
+ data.Board.Restricted = actor.Restricted
+ data.NewsItems = make([]db.NewsItem, 1)
+
+ data.NewsItems[0], err = db.GetNewsItem(timestamp)
+ if err != nil {
+ return util.MakeError(err, "NewsGet")
+ }
+
+ data.Title = actor.PreferredUsername + ": " + data.NewsItems[0].Title
+
+ data.Themes = &config.Themes
+ data.ThemeCookie = route.GetThemeCookie(ctx)
+
+ return ctx.Render("news", fiber.Map{"page": data}, "layouts/main")
+}
+
+func AllNewsGet(ctx *fiber.Ctx) error {
+ actor, err := activitypub.GetActorFromDB(config.Domain)
+ if err != nil {
+ return util.MakeError(err, "AllNewsGet")
+ }
+
+ var data route.PageData
+ data.PreferredUsername = actor.PreferredUsername
+ data.Title = actor.PreferredUsername + " News"
+ data.Boards = webfinger.Boards
+ data.Board.Name = ""
+ data.Key = config.Key
+ data.Board.Domain = config.Domain
+ data.Board.ModCred, _ = util.GetPasswordFromSession(ctx)
+ data.Board.Actor = actor
+ data.Board.Post.Actor = actor.Id
+ data.Board.Restricted = actor.Restricted
+
+ data.NewsItems, err = db.GetNews(0)
+ if err != nil {
+ return util.MakeError(err, "AllNewsGet")
+ }
+
+ data.Themes = &config.Themes
+ data.ThemeCookie = route.GetThemeCookie(ctx)
+
+ return ctx.Render("anews", fiber.Map{"page": data}, "layouts/main")
+}
diff --git a/route/routes/webfinger.go b/route/routes/webfinger.go
new file mode 100644
index 0000000..3d5fa63
--- /dev/null
+++ b/route/routes/webfinger.go
@@ -0,0 +1,58 @@
+package routes
+
+import (
+ "encoding/json"
+ "strings"
+
+ "github.com/FChannel0/FChannel-Server/activitypub"
+ "github.com/FChannel0/FChannel-Server/config"
+ "github.com/FChannel0/FChannel-Server/util"
+ "github.com/gofiber/fiber/v2"
+)
+
+func Webfinger(c *fiber.Ctx) error {
+ acct := c.Query("resource")
+
+ if len(acct) < 1 {
+ c.Status(fiber.StatusBadRequest)
+ return c.Send([]byte("resource needs a value"))
+ }
+
+ acct = strings.Replace(acct, "acct:", "", -1)
+
+ actorDomain := strings.Split(acct, "@")
+
+ if len(actorDomain) < 2 {
+ c.Status(fiber.StatusBadRequest)
+ return c.Send([]byte("accepts only subject form of acct:board@instance"))
+ }
+
+ if actorDomain[0] == "main" {
+ actorDomain[0] = ""
+ } else {
+ actorDomain[0] = "/" + actorDomain[0]
+ }
+
+ actor := activitypub.Actor{Id: config.TP + "" + actorDomain[1] + "" + actorDomain[0]}
+ if res, err := actor.IsLocal(); err == nil && !res {
+ c.Status(fiber.StatusBadRequest)
+ return c.Send([]byte("actor not local"))
+ } else if err != nil {
+ return util.MakeError(err, "Webfinger")
+ }
+
+ var finger activitypub.Webfinger
+ var link activitypub.WebfingerLink
+
+ finger.Subject = "acct:" + actorDomain[0] + "@" + actorDomain[1]
+ link.Rel = "self"
+ link.Type = "application/activity+json"
+ link.Href = config.TP + "" + actorDomain[1] + "" + actorDomain[0]
+
+ finger.Links = append(finger.Links, link)
+
+ enc, _ := json.Marshal(finger)
+
+ c.Set("Content-Type", config.ActivityStreams)
+ return c.Send(enc)
+}