opengist/internal/web/handler/gist.go

920 lines
25 KiB
Go
Raw Normal View History

2024-12-03 01:18:04 +00:00
package handler
2023-03-14 15:22:52 +00:00
import (
"archive/zip"
"bufio"
2023-03-14 15:22:52 +00:00
"bytes"
2024-11-18 01:29:05 +00:00
gojson "encoding/json"
2023-03-14 22:26:39 +00:00
"errors"
"fmt"
2024-12-03 01:18:04 +00:00
"github.com/thomiceli/opengist/internal/web/context"
"github.com/thomiceli/opengist/internal/web/server"
2023-03-14 15:22:52 +00:00
"html/template"
"net/url"
"path/filepath"
2023-06-21 16:19:17 +00:00
"regexp"
2023-03-14 15:22:52 +00:00
"strconv"
"strings"
"time"
"github.com/rs/zerolog/log"
"github.com/thomiceli/opengist/internal/git"
"github.com/thomiceli/opengist/internal/i18n"
"github.com/thomiceli/opengist/internal/index"
"github.com/thomiceli/opengist/internal/render"
"github.com/thomiceli/opengist/internal/utils"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/thomiceli/opengist/internal/config"
"github.com/thomiceli/opengist/internal/db"
"gorm.io/gorm"
2023-03-14 15:22:52 +00:00
)
2024-12-03 01:18:04 +00:00
func GistInit(next context.Handler) context.Handler {
return func(ctx *context.OGContext) error {
currUser := ctx.User
2023-10-04 16:47:50 +00:00
2023-03-14 15:22:52 +00:00
userName := ctx.Param("user")
gistName := ctx.Param("gistname")
switch filepath.Ext(gistName) {
case ".js":
2024-12-03 01:18:04 +00:00
ctx.SetData("gistpage", "js")
gistName = strings.TrimSuffix(gistName, ".js")
case ".json":
2024-12-03 01:18:04 +00:00
ctx.SetData("gistpage", "json")
gistName = strings.TrimSuffix(gistName, ".json")
case ".git":
2024-12-03 01:18:04 +00:00
ctx.SetData("gistpage", "git")
gistName = strings.TrimSuffix(gistName, ".git")
}
2023-03-14 15:22:52 +00:00
2023-09-02 22:30:57 +00:00
gist, err := db.GetGist(userName, gistName)
2023-03-14 15:22:52 +00:00
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.NotFound("Gist not found")
2023-03-14 15:22:52 +00:00
}
2023-10-04 16:47:50 +00:00
if gist.Private == db.PrivateVisibility {
2023-10-04 16:47:50 +00:00
if currUser == nil || currUser.ID != gist.UserID {
2024-12-03 01:18:04 +00:00
return ctx.NotFound("Gist not found")
2023-10-04 16:47:50 +00:00
}
}
2024-12-03 01:18:04 +00:00
ctx.SetData("gist", gist)
2023-03-14 15:22:52 +00:00
2023-04-06 23:52:56 +00:00
if config.C.SshGit {
var sshDomain string
if config.C.SshExternalDomain != "" {
sshDomain = config.C.SshExternalDomain
} else {
sshDomain = strings.Split(ctx.Request().Host, ":")[0]
}
if config.C.SshPort == "22" {
2024-12-03 01:18:04 +00:00
ctx.SetData("sshCloneUrl", sshDomain+":"+userName+"/"+gistName+".git")
2023-03-15 10:47:17 +00:00
} else {
2024-12-03 01:18:04 +00:00
ctx.SetData("sshCloneUrl", "ssh://"+sshDomain+":"+config.C.SshPort+"/"+userName+"/"+gistName+".git")
2023-03-15 10:47:17 +00:00
}
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
baseHttpUrl := ctx.GetData("baseHttpUrl").(string)
2023-04-06 23:52:56 +00:00
if config.C.HttpGit {
2024-12-03 01:18:04 +00:00
ctx.SetData("httpCloneUrl", baseHttpUrl+"/"+userName+"/"+gistName+".git")
2023-03-15 10:47:17 +00:00
}
2023-03-14 15:22:52 +00:00
2024-12-03 01:18:04 +00:00
ctx.SetData("httpCopyUrl", baseHttpUrl+"/"+userName+"/"+gistName)
ctx.SetData("currentUrl", template.URL(ctx.Request().URL.Path))
ctx.SetData("embedScript", fmt.Sprintf(`<script src="%s"></script>`, baseHttpUrl+"/"+userName+"/"+gistName+".js"))
2023-03-14 15:22:52 +00:00
nbCommits, err := gist.NbCommits()
2023-03-14 15:22:52 +00:00
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error fetching number of commits", err)
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
ctx.SetData("nbCommits", nbCommits)
2023-03-14 15:22:52 +00:00
2023-10-04 16:47:50 +00:00
if currUser != nil {
2023-03-17 13:56:39 +00:00
hasLiked, err := currUser.HasLiked(gist)
2023-03-14 15:22:52 +00:00
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Cannot get user like status", err)
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
ctx.SetData("hasLiked", hasLiked)
2023-03-14 15:22:52 +00:00
}
if gist.Private > 0 {
2024-12-03 01:18:04 +00:00
ctx.SetData("NoIndex", true)
}
2023-03-14 15:22:52 +00:00
return next(ctx)
}
}
2024-12-03 01:18:04 +00:00
// GistSoftInit try to load a gist (same as gistInit) but does not return a 404 if the gist is not found
// useful for git clients using HTTP to obfuscate the existence of a private gist
2024-12-03 01:18:04 +00:00
func GistSoftInit(next echo.HandlerFunc) context.Handler {
return func(ctx *context.OGContext) error {
userName := ctx.Param("user")
gistName := ctx.Param("gistname")
gistName = strings.TrimSuffix(gistName, ".git")
2023-09-02 22:30:57 +00:00
gist, _ := db.GetGist(userName, gistName)
2024-12-03 01:18:04 +00:00
ctx.SetData("gist", gist)
return next(ctx)
}
}
2024-12-03 01:18:04 +00:00
// GistNewPushSoftInit has the same behavior as gistSoftInit but create a new gist empty instead
func GistNewPushSoftInit(next context.Handler) context.Handler {
return func(ctx *context.OGContext) error {
ctx.SetData("gist", new(db.Gist))
return next(ctx)
}
}
2024-12-03 01:18:04 +00:00
func AllGists(ctx *context.OGContext) error {
2023-03-14 15:22:52 +00:00
var err error
2023-06-21 16:19:17 +00:00
var urlPage string
2023-03-20 12:30:25 +00:00
2023-06-21 16:19:17 +00:00
fromUserStr := ctx.Param("user")
2024-12-03 01:18:04 +00:00
userLogged := ctx.User
2023-03-14 15:22:52 +00:00
pageInt := getPage(ctx)
sort := "created"
2024-12-03 01:18:04 +00:00
sortText := ctx.TrH("gist.list.sort-by-created")
2023-03-14 15:22:52 +00:00
order := "desc"
2024-12-03 01:18:04 +00:00
orderText := ctx.TrH("gist.list.order-by-desc")
2023-03-14 15:22:52 +00:00
if ctx.QueryParam("sort") == "updated" {
sort = "updated"
2024-12-03 01:18:04 +00:00
sortText = ctx.TrH("gist.list.sort-by-updated")
2023-03-14 15:22:52 +00:00
}
if ctx.QueryParam("order") == "asc" {
order = "asc"
2024-12-03 01:18:04 +00:00
orderText = ctx.TrH("gist.list.order-by-asc")
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
ctx.SetData("sort", sortText)
ctx.SetData("order", orderText)
2023-03-14 15:22:52 +00:00
2023-09-02 22:30:57 +00:00
var gists []*db.Gist
2023-03-14 15:22:52 +00:00
var currentUserId uint
if userLogged != nil {
currentUserId = userLogged.ID
} else {
currentUserId = 0
}
2023-06-21 16:19:17 +00:00
2023-03-20 12:30:25 +00:00
if fromUserStr == "" {
2023-06-21 16:19:17 +00:00
urlctx := ctx.Request().URL.Path
if strings.HasSuffix(urlctx, "search") {
2024-12-03 01:18:04 +00:00
ctx.SetData("htmlTitle", ctx.TrH("gist.list.search-results"))
ctx.SetData("mode", "search")
ctx.SetData("searchQuery", ctx.QueryParam("q"))
ctx.SetData("searchQueryUrl", template.URL("&q="+ctx.QueryParam("q")))
2023-06-21 16:19:17 +00:00
urlPage = "search"
2023-09-02 22:30:57 +00:00
gists, err = db.GetAllGistsFromSearch(currentUserId, ctx.QueryParam("q"), pageInt-1, sort, order)
2023-06-21 16:19:17 +00:00
} else if strings.HasSuffix(urlctx, "all") {
2024-12-03 01:18:04 +00:00
ctx.SetData("htmlTitle", ctx.TrH("gist.list.all"))
ctx.SetData("mode", "all")
2023-06-21 16:19:17 +00:00
urlPage = "all"
2023-09-02 22:30:57 +00:00
gists, err = db.GetAllGistsForCurrentUser(currentUserId, pageInt-1, sort, order)
2023-06-21 16:19:17 +00:00
}
2023-03-14 15:22:52 +00:00
} else {
2023-06-21 16:19:17 +00:00
liked := false
forked := false
liked, err = regexp.MatchString(`/[^/]*/liked`, ctx.Request().URL.Path)
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error matching regexp", err)
2023-06-21 16:19:17 +00:00
}
forked, err = regexp.MatchString(`/[^/]*/forked`, ctx.Request().URL.Path)
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error matching regexp", err)
2023-06-21 16:19:17 +00:00
}
2023-03-14 15:22:52 +00:00
2023-09-02 22:30:57 +00:00
var fromUser *db.User
2023-06-21 16:19:17 +00:00
2023-09-02 22:30:57 +00:00
fromUser, err = db.GetUserByUsername(fromUserStr)
2023-03-20 12:30:25 +00:00
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
2024-12-03 01:18:04 +00:00
return ctx.NotFound("User not found")
2023-03-20 12:30:25 +00:00
}
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error fetching user", err)
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
ctx.SetData("fromUser", fromUser)
2023-03-14 15:22:52 +00:00
2023-09-02 22:30:57 +00:00
if countFromUser, err := db.CountAllGistsFromUser(fromUser.ID, currentUserId); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error counting gists", err)
2023-06-21 16:19:17 +00:00
} else {
2024-12-03 01:18:04 +00:00
ctx.SetData("countFromUser", countFromUser)
2023-06-21 16:19:17 +00:00
}
2023-09-02 22:30:57 +00:00
if countLiked, err := db.CountAllGistsLikedByUser(fromUser.ID, currentUserId); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error counting liked gists", err)
2023-06-21 16:19:17 +00:00
} else {
2024-12-03 01:18:04 +00:00
ctx.SetData("countLiked", countLiked)
2023-06-21 16:19:17 +00:00
}
2023-09-02 22:30:57 +00:00
if countForked, err := db.CountAllGistsForkedByUser(fromUser.ID, currentUserId); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error counting forked gists", err)
2023-06-21 16:19:17 +00:00
} else {
2024-12-03 01:18:04 +00:00
ctx.SetData("countForked", countForked)
2023-06-21 16:19:17 +00:00
}
if liked {
urlPage = fromUserStr + "/liked"
2024-12-03 01:18:04 +00:00
ctx.SetData("htmlTitle", ctx.TrH("gist.list.all-liked-by", fromUserStr))
ctx.SetData("mode", "liked")
2023-09-02 22:30:57 +00:00
gists, err = db.GetAllGistsLikedByUser(fromUser.ID, currentUserId, pageInt-1, sort, order)
2023-06-21 16:19:17 +00:00
} else if forked {
urlPage = fromUserStr + "/forked"
2024-12-03 01:18:04 +00:00
ctx.SetData("htmlTitle", ctx.TrH("gist.list.all-forked-by", fromUserStr))
ctx.SetData("mode", "forked")
2023-09-02 22:30:57 +00:00
gists, err = db.GetAllGistsForkedByUser(fromUser.ID, currentUserId, pageInt-1, sort, order)
2023-06-21 16:19:17 +00:00
} else {
urlPage = fromUserStr
2024-12-03 01:18:04 +00:00
ctx.SetData("htmlTitle", ctx.TrH("gist.list.all-from", fromUserStr))
ctx.SetData("mode", "fromUser")
2023-09-02 22:30:57 +00:00
gists, err = db.GetAllGistsFromUser(fromUser.ID, currentUserId, pageInt-1, sort, order)
2023-06-21 16:19:17 +00:00
}
2023-03-14 15:22:52 +00:00
}
2023-03-20 12:30:25 +00:00
2024-01-04 02:38:15 +00:00
renderedGists := make([]*render.RenderedGist, 0, len(gists))
for _, gist := range gists {
rendered, err := render.HighlightGistPreview(gist)
if err != nil {
2024-01-04 02:38:15 +00:00
log.Error().Err(err).Msg("Error rendering gist preview for " + gist.Identifier() + " - " + gist.PreviewFilename)
}
2024-01-04 02:38:15 +00:00
renderedGists = append(renderedGists, &rendered)
}
2023-03-14 15:22:52 +00:00
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error fetching gists", err)
2023-03-14 15:22:52 +00:00
}
2024-01-04 02:38:15 +00:00
if err = paginate(ctx, renderedGists, pageInt, 10, "gists", fromUserStr, 2, "&sort="+sort+"&order="+order); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(404, ctx.Tr("error.page-not-found"), nil)
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
ctx.SetData("urlPage", urlPage)
return ctx.HTML_("all.html")
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
func Search(ctx *context.OGContext) error {
2024-01-04 02:38:15 +00:00
var err error
2024-12-03 01:18:04 +00:00
content, meta := ParseSearchQueryStr(ctx.QueryParam("q"))
2024-01-04 02:38:15 +00:00
pageInt := getPage(ctx)
var currentUserId uint
2024-12-03 01:18:04 +00:00
userLogged := ctx.User
2024-01-04 02:38:15 +00:00
if userLogged != nil {
currentUserId = userLogged.ID
} else {
currentUserId = 0
}
var visibleGistsIds []uint
visibleGistsIds, err = db.GetAllGistsVisibleByUser(currentUserId)
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error fetching gists", err)
2024-01-04 02:38:15 +00:00
}
gistsIds, nbHits, langs, err := index.SearchGists(content, index.SearchGistMetadata{
Username: meta["user"],
Title: meta["title"],
Filename: meta["filename"],
Extension: meta["extension"],
Language: meta["language"],
}, visibleGistsIds, pageInt)
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error searching gists", err)
2024-01-04 02:38:15 +00:00
}
gists, err := db.GetAllGistsByIds(gistsIds)
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error fetching gists", err)
2024-01-04 02:38:15 +00:00
}
renderedGists := make([]*render.RenderedGist, 0, len(gists))
for _, gist := range gists {
rendered, err := render.HighlightGistPreview(gist)
if err != nil {
log.Error().Err(err).Msg("Error rendering gist preview for " + gist.Identifier() + " - " + gist.PreviewFilename)
}
renderedGists = append(renderedGists, &rendered)
}
if pageInt > 1 && len(renderedGists) != 0 {
2024-12-03 01:18:04 +00:00
ctx.SetData("prevPage", pageInt-1)
2024-01-04 02:38:15 +00:00
}
if 10*pageInt < int(nbHits) {
2024-12-03 01:18:04 +00:00
ctx.SetData("nextPage", pageInt+1)
}
ctx.SetData("prevLabel", ctx.TrH("pagination.previous"))
ctx.SetData("nextLabel", ctx.TrH("pagination.next"))
ctx.SetData("urlPage", "search")
ctx.SetData("urlParams", template.URL("&q="+ctx.QueryParam("q")))
ctx.SetData("htmlTitle", ctx.TrH("gist.list.search-results"))
ctx.SetData("nbHits", nbHits)
ctx.SetData("gists", renderedGists)
ctx.SetData("langs", langs)
ctx.SetData("searchQuery", ctx.QueryParam("q"))
return ctx.HTML_("search.html")
2024-01-04 02:38:15 +00:00
}
2024-12-03 01:18:04 +00:00
func GistIndex(ctx *context.OGContext) error {
if ctx.GetData("gistpage") == "js" {
return GistJs(ctx)
} else if ctx.GetData("gistpage") == "json" {
return GistJson(ctx)
}
2024-12-03 01:18:04 +00:00
gist := ctx.GetData("gist").(*db.Gist)
2023-03-14 15:22:52 +00:00
revision := ctx.Param("revision")
if revision == "" {
revision = "HEAD"
}
files, err := gist.Files(revision, true)
if _, ok := err.(*git.RevisionNotFoundError); ok {
2024-12-03 01:18:04 +00:00
return ctx.NotFound("Revision not found")
} else if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error fetching files", err)
2023-03-14 15:22:52 +00:00
}
renderedFiles := render.HighlightFiles(files)
2024-12-03 01:18:04 +00:00
ctx.SetData("page", "code")
ctx.SetData("commit", revision)
ctx.SetData("files", renderedFiles)
ctx.SetData("revision", revision)
ctx.SetData("htmlTitle", gist.Title)
return ctx.HTML_("gist.html")
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
func GistJson(ctx *context.OGContext) error {
gist := ctx.GetData("gist").(*db.Gist)
files, err := gist.Files("HEAD", true)
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error fetching files", err)
}
renderedFiles := render.HighlightFiles(files)
2024-12-03 01:18:04 +00:00
ctx.SetData("files", renderedFiles)
htmlbuf := bytes.Buffer{}
w := bufio.NewWriter(&htmlbuf)
2024-12-03 01:18:04 +00:00
if err = ctx.Echo().Renderer.Render(w, "gist_embed.html", ctx.DataMap(), ctx); err != nil {
return err
}
_ = w.Flush()
2024-12-03 01:18:04 +00:00
jsUrl, err := url.JoinPath(ctx.GetData("baseHttpUrl").(string), gist.User.Username, gist.Identifier()+".js")
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error joining js url", err)
}
2024-12-03 01:18:04 +00:00
cssUrl, err := url.JoinPath(ctx.GetData("baseHttpUrl").(string), server.ManifestEntries["embed.css"].File)
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error joining css url", err)
}
return ctx.JSON(200, map[string]interface{}{
"owner": gist.User.Username,
2023-12-26 02:24:04 +00:00
"id": gist.Identifier(),
"uuid": gist.Uuid,
"title": gist.Title,
"description": gist.Description,
"created_at": time.Unix(gist.CreatedAt, 0).Format(time.RFC3339),
"visibility": gist.VisibilityStr(),
"files": renderedFiles,
"embed": map[string]string{
"html": htmlbuf.String(),
"css": cssUrl,
"js": jsUrl,
"js_dark": jsUrl + "?dark",
},
})
}
2024-12-03 01:18:04 +00:00
func GistJs(ctx *context.OGContext) error {
if _, exists := ctx.QueryParams()["dark"]; exists {
2024-12-03 01:18:04 +00:00
ctx.SetData("dark", "dark")
}
2024-12-03 01:18:04 +00:00
gist := ctx.GetData("gist").(*db.Gist)
files, err := gist.Files("HEAD", true)
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error fetching files", err)
}
renderedFiles := render.HighlightFiles(files)
2024-12-03 01:18:04 +00:00
ctx.SetData("files", renderedFiles)
htmlbuf := bytes.Buffer{}
w := bufio.NewWriter(&htmlbuf)
2024-12-03 01:18:04 +00:00
if err = ctx.Echo().Renderer.Render(w, "gist_embed.html", ctx.DataMap(), ctx); err != nil {
return err
}
_ = w.Flush()
2024-12-03 01:18:04 +00:00
cssUrl, err := url.JoinPath(ctx.GetData("baseHttpUrl").(string), server.ManifestEntries["embed.css"].File)
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error joining css url", err)
}
2024-11-18 01:29:05 +00:00
js, err := escapeJavaScriptContent(htmlbuf.String(), cssUrl)
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error escaping JavaScript content", err)
2024-11-18 01:29:05 +00:00
}
ctx.Response().Header().Set("Content-Type", "application/javascript")
2024-12-03 01:18:04 +00:00
return ctx.PlainText(200, js)
}
2024-12-03 01:18:04 +00:00
func Revisions(ctx *context.OGContext) error {
gist := ctx.GetData("gist").(*db.Gist)
2023-03-14 15:22:52 +00:00
userName := gist.User.Username
2023-12-26 02:24:04 +00:00
gistName := gist.Identifier()
2023-03-14 15:22:52 +00:00
pageInt := getPage(ctx)
2023-04-16 14:14:12 +00:00
commits, err := gist.Log((pageInt - 1) * 10)
2023-03-18 22:18:20 +00:00
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error fetching commits log", err)
2023-03-14 15:22:52 +00:00
}
2023-03-15 00:26:56 +00:00
if err := paginate(ctx, commits, pageInt, 10, "commits", userName+"/"+gistName+"/revisions", 2); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(404, ctx.Tr("error.page-not-found"), nil)
2023-03-14 15:22:52 +00:00
}
emailsSet := map[string]struct{}{}
for _, commit := range commits {
if commit.AuthorEmail == "" {
continue
}
emailsSet[strings.ToLower(commit.AuthorEmail)] = struct{}{}
}
2023-09-02 22:30:57 +00:00
emailsUsers, err := db.GetUsersFromEmails(emailsSet)
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error fetching users emails", err)
}
2024-12-03 01:18:04 +00:00
ctx.SetData("page", "revisions")
ctx.SetData("revision", "HEAD")
ctx.SetData("emails", emailsUsers)
ctx.SetData("htmlTitle", ctx.TrH("gist.revision-of", gist.Title))
2023-03-14 15:22:52 +00:00
2024-12-03 01:18:04 +00:00
return ctx.HTML_("revisions.html")
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
func Create(ctx *context.OGContext) error {
ctx.SetData("htmlTitle", ctx.TrH("gist.new.create-a-new-gist"))
return ctx.HTML_("create.html")
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
func ProcessCreate(ctx *context.OGContext) error {
2023-03-14 15:22:52 +00:00
isCreate := false
if ctx.Request().URL.Path == "/" {
isCreate = true
}
err := ctx.Request().ParseForm()
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(400, ctx.Tr("error.bad-request"), err)
2023-03-14 15:22:52 +00:00
}
2023-09-02 22:30:57 +00:00
dto := new(db.GistDTO)
var gist *db.Gist
2023-03-14 15:22:52 +00:00
if isCreate {
2024-12-03 01:18:04 +00:00
ctx.SetData("htmlTitle", ctx.TrH("gist.new.create-a-new-gist"))
2023-03-14 15:22:52 +00:00
} else {
2024-12-03 01:18:04 +00:00
gist = ctx.GetData("gist").(*db.Gist)
ctx.SetData("htmlTitle", ctx.TrH("gist.edit.edit-gist", gist.Title))
2023-03-14 15:22:52 +00:00
}
2023-03-17 13:56:39 +00:00
if err := ctx.Bind(dto); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(400, ctx.Tr("error.cannot-bind-data"), err)
2023-03-14 15:22:52 +00:00
}
2023-09-02 22:30:57 +00:00
dto.Files = make([]db.FileDTO, 0)
2023-03-20 12:49:28 +00:00
fileCounter := 0
2023-03-14 15:22:52 +00:00
for i := 0; i < len(ctx.Request().PostForm["content"]); i++ {
name := ctx.Request().PostForm["name"][i]
content := ctx.Request().PostForm["content"][i]
if name == "" {
2023-03-20 12:49:28 +00:00
fileCounter += 1
name = "gistfile" + strconv.Itoa(fileCounter) + ".txt"
2023-03-14 15:22:52 +00:00
}
escapedValue, err := url.QueryUnescape(content)
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(400, ctx.Tr("error.invalid-character-unescaped"), err)
2023-03-14 15:22:52 +00:00
}
2023-09-02 22:30:57 +00:00
dto.Files = append(dto.Files, db.FileDTO{
2023-04-28 18:41:12 +00:00
Filename: strings.Trim(name, " "),
2023-03-14 15:22:52 +00:00
Content: escapedValue,
})
}
2023-03-17 13:56:39 +00:00
err = ctx.Validate(dto)
2023-03-14 15:22:52 +00:00
if err != nil {
2024-12-03 01:18:04 +00:00
ctx.AddFlash(utils.ValidationMessages(&err, ctx.GetData("locale").(*i18n.Locale)), "error")
2023-03-14 15:22:52 +00:00
if isCreate {
2024-12-03 01:18:04 +00:00
return ctx.HTML_("create.html")
2023-03-14 15:22:52 +00:00
} else {
files, err := gist.Files("HEAD", false)
2023-03-14 15:22:52 +00:00
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error fetching files", err)
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
ctx.SetData("files", files)
return ctx.HTML_("edit.html")
2023-03-14 15:22:52 +00:00
}
}
2023-03-17 13:56:39 +00:00
if isCreate {
gist = dto.ToGist()
} else {
gist = dto.ToExistingGist(gist)
}
2024-12-03 01:18:04 +00:00
user := ctx.User
gist.NbFiles = len(dto.Files)
2023-03-17 13:56:39 +00:00
if isCreate {
uuidGist, err := uuid.NewRandom()
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error creating an UUID", err)
2023-03-17 13:56:39 +00:00
}
gist.Uuid = strings.Replace(uuidGist.String(), "-", "", -1)
gist.UserID = user.ID
gist.User = *user
2023-03-17 13:56:39 +00:00
}
if gist.Title == "" {
if ctx.Request().PostForm["name"][0] == "" {
gist.Title = "gist:" + gist.Uuid
} else {
gist.Title = ctx.Request().PostForm["name"][0]
}
}
if len(dto.Files) > 0 {
split := strings.Split(dto.Files[0].Content, "\n")
2023-03-14 15:22:52 +00:00
if len(split) > 10 {
gist.Preview = strings.Join(split[:10], "\n")
} else {
gist.Preview = dto.Files[0].Content
2023-03-14 15:22:52 +00:00
}
gist.PreviewFilename = dto.Files[0].Filename
2023-03-14 15:22:52 +00:00
}
if err = gist.InitRepository(); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error creating the repository", err)
2023-03-14 15:22:52 +00:00
}
if err = gist.AddAndCommitFiles(&dto.Files); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error adding and committing files", err)
2023-03-14 15:22:52 +00:00
}
if isCreate {
2023-03-17 13:56:39 +00:00
if err = gist.Create(); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error creating the gist", err)
2023-03-14 15:22:52 +00:00
}
} else {
2023-03-17 13:56:39 +00:00
if err = gist.Update(); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error updating the gist", err)
2023-03-14 15:22:52 +00:00
}
}
2024-01-04 02:38:15 +00:00
gist.AddInIndex()
2024-12-03 01:18:04 +00:00
return ctx.RedirectTo("/" + user.Username + "/" + gist.Identifier())
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
func EditVisibility(ctx *context.OGContext) error {
gist := ctx.GetData("gist").(*db.Gist)
2023-03-14 15:22:52 +00:00
dto := new(db.VisibilityDTO)
if err := ctx.Bind(dto); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(400, ctx.Tr("error.cannot-bind-data"), err)
}
gist.Private = dto.Private
if err := gist.UpdateNoTimestamps(); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error updating this gist", err)
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
ctx.AddFlash(ctx.Tr("flash.gist.visibility-changed"), "success")
return ctx.RedirectTo("/" + gist.User.Username + "/" + gist.Identifier())
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
func DeleteGist(ctx *context.OGContext) error {
gist := ctx.GetData("gist").(*db.Gist)
2023-03-14 15:22:52 +00:00
2023-03-17 13:56:39 +00:00
if err := gist.Delete(); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error deleting this gist", err)
2023-03-14 15:22:52 +00:00
}
2024-01-04 02:38:15 +00:00
gist.RemoveFromIndex()
2023-03-14 15:22:52 +00:00
2024-12-03 01:18:04 +00:00
ctx.AddFlash(ctx.Tr("flash.gist.deleted"), "success")
return ctx.RedirectTo("/")
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
func Like(ctx *context.OGContext) error {
gist := ctx.GetData("gist").(*db.Gist)
currentUser := ctx.User
2023-03-14 15:22:52 +00:00
2023-03-17 13:56:39 +00:00
hasLiked, err := currentUser.HasLiked(gist)
2023-03-14 15:22:52 +00:00
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error checking if user has liked a gist", err)
2023-03-14 15:22:52 +00:00
}
if hasLiked {
2024-12-03 01:18:04 +00:00
err = gist.RemoveUserLike(ctx.User)
2023-03-14 15:22:52 +00:00
} else {
2024-12-03 01:18:04 +00:00
err = gist.AppendUserLike(ctx.User)
2023-03-14 15:22:52 +00:00
}
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error liking/dislking this gist", err)
2023-03-14 15:22:52 +00:00
}
2023-12-26 02:24:04 +00:00
redirectTo := "/" + gist.User.Username + "/" + gist.Identifier()
2023-03-14 15:22:52 +00:00
if r := ctx.QueryParam("redirecturl"); r != "" {
redirectTo = r
}
2024-12-03 01:18:04 +00:00
return ctx.RedirectTo(redirectTo)
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
func Fork(ctx *context.OGContext) error {
gist := ctx.GetData("gist").(*db.Gist)
currentUser := ctx.User
2023-03-14 22:26:39 +00:00
2023-03-17 13:56:39 +00:00
alreadyForked, err := gist.GetForkParent(currentUser)
2023-03-14 22:26:39 +00:00
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error checking if gist is already forked", err)
2023-03-14 22:26:39 +00:00
}
2023-03-15 00:01:42 +00:00
if gist.User.ID == currentUser.ID {
2024-12-03 01:18:04 +00:00
ctx.AddFlash(ctx.Tr("flash.gist.fork-own-gist"), "error")
return ctx.RedirectTo("/" + gist.User.Username + "/" + gist.Identifier())
2023-03-15 00:01:42 +00:00
}
2023-03-14 22:26:39 +00:00
if alreadyForked.ID != 0 {
2024-12-03 01:18:04 +00:00
return ctx.RedirectTo("/" + alreadyForked.User.Username + "/" + alreadyForked.Identifier())
2023-03-14 22:26:39 +00:00
}
uuidGist, err := uuid.NewRandom()
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error creating an UUID", err)
2023-03-14 22:26:39 +00:00
}
2023-09-02 22:30:57 +00:00
newGist := &db.Gist{
2023-03-14 22:26:39 +00:00
Uuid: strings.Replace(uuidGist.String(), "-", "", -1),
Title: gist.Title,
Preview: gist.Preview,
PreviewFilename: gist.PreviewFilename,
Description: gist.Description,
Private: gist.Private,
UserID: currentUser.ID,
ForkedID: gist.ID,
2023-03-18 23:52:09 +00:00
NbFiles: gist.NbFiles,
2023-03-14 22:26:39 +00:00
}
2023-03-17 13:56:39 +00:00
if err = newGist.CreateForked(); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error forking the gist in database", err)
2023-03-14 22:26:39 +00:00
}
if err = gist.ForkClone(currentUser.Username, newGist.Uuid); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error cloning the repository while forking", err)
2023-03-14 22:26:39 +00:00
}
2023-03-17 13:56:39 +00:00
if err = gist.IncrementForkCount(); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error incrementing the fork count", err)
2023-03-14 22:26:39 +00:00
}
2024-12-03 01:18:04 +00:00
ctx.AddFlash(ctx.Tr("flash.gist.forked"), "success")
2023-03-15 00:01:42 +00:00
2024-12-03 01:18:04 +00:00
return ctx.RedirectTo("/" + currentUser.Username + "/" + newGist.Identifier())
2023-03-14 22:26:39 +00:00
}
2024-12-03 01:18:04 +00:00
func RawFile(ctx *context.OGContext) error {
gist := ctx.GetData("gist").(*db.Gist)
file, err := gist.File(ctx.Param("revision"), ctx.Param("file"), false)
2023-03-14 15:22:52 +00:00
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error getting file content", err)
2023-03-14 15:22:52 +00:00
}
if file == nil {
2024-12-03 01:18:04 +00:00
return ctx.NotFound("File not found")
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
return ctx.PlainText(200, file.Content)
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
func DownloadFile(ctx *context.OGContext) error {
gist := ctx.GetData("gist").(*db.Gist)
file, err := gist.File(ctx.Param("revision"), ctx.Param("file"), false)
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error getting file content", err)
}
if file == nil {
2024-12-03 01:18:04 +00:00
return ctx.NotFound("File not found")
}
ctx.Response().Header().Set("Content-Type", "text/plain")
ctx.Response().Header().Set("Content-Disposition", "attachment; filename="+file.Filename)
ctx.Response().Header().Set("Content-Length", strconv.Itoa(len(file.Content)))
_, err = ctx.Response().Write([]byte(file.Content))
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error downloading the file", err)
}
return nil
}
2024-12-03 01:18:04 +00:00
func Edit(ctx *context.OGContext) error {
gist := ctx.GetData("gist").(*db.Gist)
2023-03-14 15:22:52 +00:00
files, err := gist.Files("HEAD", false)
2023-03-14 15:22:52 +00:00
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error fetching files from repository", err)
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
ctx.SetData("files", files)
ctx.SetData("htmlTitle", ctx.TrH("gist.edit.edit-gist", gist.Title))
2023-03-14 15:22:52 +00:00
2024-12-03 01:18:04 +00:00
return ctx.HTML_("edit.html")
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
func DownloadZip(ctx *context.OGContext) error {
gist := ctx.GetData("gist").(*db.Gist)
revision := ctx.Param("revision")
2023-03-14 15:22:52 +00:00
files, err := gist.Files(revision, false)
2023-03-14 15:22:52 +00:00
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error fetching files from repository", err)
2023-03-14 15:22:52 +00:00
}
if len(files) == 0 {
2024-12-03 01:18:04 +00:00
return ctx.NotFound("No files found in this revision")
2023-03-14 15:22:52 +00:00
}
zipFile := new(bytes.Buffer)
zipWriter := zip.NewWriter(zipFile)
for _, file := range files {
2023-03-23 15:27:38 +00:00
fh := &zip.FileHeader{
Name: file.Filename,
Method: zip.Deflate,
}
f, err := zipWriter.CreateHeader(fh)
2023-03-14 15:22:52 +00:00
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error adding a file the to the zip archive", err)
2023-03-14 15:22:52 +00:00
}
_, err = f.Write([]byte(file.Content))
2023-03-14 15:22:52 +00:00
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error adding file content the to the zip archive", err)
2023-03-14 15:22:52 +00:00
}
}
err = zipWriter.Close()
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error closing the zip archive", err)
2023-03-14 15:22:52 +00:00
}
ctx.Response().Header().Set("Content-Type", "application/zip")
2023-12-26 02:24:04 +00:00
ctx.Response().Header().Set("Content-Disposition", "attachment; filename="+gist.Identifier()+".zip")
2023-03-14 15:22:52 +00:00
ctx.Response().Header().Set("Content-Length", strconv.Itoa(len(zipFile.Bytes())))
_, err = ctx.Response().Write(zipFile.Bytes())
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error writing the zip archive", err)
2023-03-14 15:22:52 +00:00
}
return nil
}
2024-12-03 01:18:04 +00:00
func Likes(ctx *context.OGContext) error {
gist := ctx.GetData("gist").(*db.Gist)
2023-03-14 15:22:52 +00:00
pageInt := getPage(ctx)
2023-03-17 13:56:39 +00:00
likers, err := gist.GetUsersLikes(pageInt - 1)
2023-03-14 15:22:52 +00:00
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error getting users who liked this gist", err)
2023-03-14 15:22:52 +00:00
}
2023-12-26 02:24:04 +00:00
if err = paginate(ctx, likers, pageInt, 30, "likers", gist.User.Username+"/"+gist.Identifier()+"/likes", 1); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(404, ctx.Tr("error.page-not-found"), nil)
2023-03-14 15:22:52 +00:00
}
2024-12-03 01:18:04 +00:00
ctx.SetData("htmlTitle", ctx.TrH("gist.likes.for", gist.Title))
ctx.SetData("revision", "HEAD")
return ctx.HTML_("likes.html")
2023-03-14 15:22:52 +00:00
}
2023-03-14 22:26:39 +00:00
2024-12-03 01:18:04 +00:00
func Forks(ctx *context.OGContext) error {
gist := ctx.GetData("gist").(*db.Gist)
2023-03-14 22:26:39 +00:00
pageInt := getPage(ctx)
2024-12-03 01:18:04 +00:00
currentUser := ctx.User
2023-03-14 22:26:39 +00:00
var fromUserID uint = 0
if currentUser != nil {
fromUserID = currentUser.ID
}
2023-03-17 13:56:39 +00:00
forks, err := gist.GetForks(fromUserID, pageInt-1)
2023-03-14 22:26:39 +00:00
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error getting users who liked this gist", err)
2023-03-14 22:26:39 +00:00
}
2023-12-26 02:24:04 +00:00
if err = paginate(ctx, forks, pageInt, 30, "forks", gist.User.Username+"/"+gist.Identifier()+"/forks", 2); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(404, ctx.Tr("error.page-not-found"), nil)
2023-03-14 22:26:39 +00:00
}
2024-12-03 01:18:04 +00:00
ctx.SetData("htmlTitle", ctx.TrH("gist.forks.for", gist.Title))
ctx.SetData("revision", "HEAD")
return ctx.HTML_("forks.html")
2023-03-14 22:26:39 +00:00
}
2024-12-03 01:18:04 +00:00
func Checkbox(ctx *context.OGContext) error {
filename := ctx.FormValue("file")
checkboxNb := ctx.FormValue("checkbox")
i, err := strconv.Atoi(checkboxNb)
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(400, ctx.Tr("error.invalid-number"), nil)
}
2024-12-03 01:18:04 +00:00
gist := ctx.GetData("gist").(*db.Gist)
file, err := gist.File("HEAD", filename, false)
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error getting file content", err)
} else if file == nil {
2024-12-03 01:18:04 +00:00
return ctx.NotFound("File not found")
}
markdown, err := render.Checkbox(file.Content, i)
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error checking checkbox", err)
}
if err = gist.AddAndCommitFile(&db.FileDTO{
Filename: filename,
Content: markdown,
}); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error adding and committing files", err)
}
if err = gist.UpdatePreviewAndCount(true); err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error updating the gist", err)
}
2024-12-03 01:18:04 +00:00
return ctx.PlainText(200, "ok")
}
2024-02-24 17:09:23 +00:00
2024-12-03 01:18:04 +00:00
func Preview(ctx *context.OGContext) error {
2024-02-24 17:09:23 +00:00
content := ctx.FormValue("content")
previewStr, err := render.MarkdownString(content)
if err != nil {
2024-12-03 01:18:04 +00:00
return ctx.ErrorRes(500, "Error rendering markdown", err)
2024-02-24 17:09:23 +00:00
}
2024-12-03 01:18:04 +00:00
return ctx.PlainText(200, previewStr)
2024-02-24 17:09:23 +00:00
}
2024-11-18 01:29:05 +00:00
func escapeJavaScriptContent(htmlContent, cssUrl string) (string, error) {
jsonContent, err := gojson.Marshal(htmlContent)
if err != nil {
return "", fmt.Errorf("failed to encode content: %w", err)
}
jsonCssUrl, err := gojson.Marshal(cssUrl)
if err != nil {
return "", fmt.Errorf("failed to encode CSS URL: %w", err)
}
js := fmt.Sprintf(`
document.write('<link rel="stylesheet" href=%s>');
document.write(%s);
`,
string(jsonCssUrl),
string(jsonContent),
)
return js, nil
}