package server import ( "fmt" "io" "net/http" _ "github.com/artalkjs/artalk/v2/docs/swagger" "github.com/artalkjs/artalk/v2/internal/config" "github.com/artalkjs/artalk/v2/internal/core" "github.com/artalkjs/artalk/v2/internal/log" "github.com/artalkjs/artalk/v2/internal/pkged" "github.com/artalkjs/artalk/v2/server/common" h "github.com/artalkjs/artalk/v2/server/handler" "github.com/artalkjs/artalk/v2/server/middleware" "github.com/artalkjs/artalk/v2/server/middleware/limiter" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/filesystem" fiber_logger "github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/pprof" "github.com/gofiber/fiber/v2/middleware/requestid" "github.com/gofiber/swagger" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) // @Title Artalk API // @Version 2.0 // @Description Artalk is a modern comment system based on Golang. // @BasePath /api/v2 // @Contact.name API Support // @Contact.url https://artalk.js.org // @Contact.email artalkjs@gmail.com // @License.name MIT // @License.url https://github.com/ArtalkJS/Artalk/blob/master/LICENSE // @SecurityDefinitions.apikey ApiKeyAuth // @In header // @Name Authorization // @Description "Type 'Bearer TOKEN' to correctly set the API Key" func Serve(app *core.App) (*fiber.App, error) { // create fiber app // @see https://docs.gofiber.io/api/fiber#config fb := fiber.New(fiber.Config{ // @see https://github.com/gofiber/fiber/issues/426 // @see https://github.com/gofiber/fiber/issues/185 Immutable: true, ErrorHandler: common.ErrorHandler, ProxyHeader: app.Conf().HTTP.GetProxyHeader(), BodyLimit: app.Conf().HTTP.BodyLimit * 1024 * 1024, // MB StreamRequestBody: true, EnableIPValidation: true, }) reqID(fb) logger(fb, app.Conf().Debug) swaggerDocs(fb) cors(app, fb) actionLimit(app, fb) if app.Conf().Debug { log.Debug("[PPROF] pprof enabled, you can access it via `/debug/pprof`.") fb.Use(pprof.New()) } api := fb.Group("/api/v2") { h.CommentCreate(app, api) h.CommentList(app, api) h.CommentGet(app, api) h.VoteGet(app, api) h.VoteCreate(app, api) h.PagePV(app, api) h.Stat(app, api) h.NotifyList(app, api) h.NotifyReadAll(app, api) h.NotifyRead(app, api) h.Upload(app, api) h.Conf(app, api) h.Version(app, api) h.ConfDomain(app, api) // captcha h.Captcha(app, api) // auth h.AuthEmailLogin(app, api) h.AuthEmailRegister(app, api) h.AuthEmailSend(app, api) h.AuthMergeApply(app, api) h.AuthMergeCheck(app, api) h.AuthSocialLogin(app, api) h.AuthSSOExchange(app, api) // user h.UserInfo(app, api) h.UserInfoUpdate(app, api) h.UserLogin(app, api) h.UserStatus(app, api) // admin admin(app, api) } index(fb) static(fb) uploadedStatic(app, fb) app.OnTerminate().Add(func(e *core.TerminateEvent) error { _ = fb.Shutdown() return nil }) // listen serves HTTP requests listenAddr := fmt.Sprintf("%s:%d", app.Conf().Host, app.Conf().Port) if app.Conf().SSL.Enabled { return fb, fb.ListenTLS(listenAddr, app.Conf().SSL.CertPath, app.Conf().SSL.KeyPath) } else { return fb, fb.Listen(listenAddr) } } func admin(app *core.App, api fiber.Router) { h.CommentUpdate(app, api) h.CommentDelete(app, api) h.PageList(app, api) h.PageUpdate(app, api) h.PageDelete(app, api) h.PageFetch(app, api) h.PageFetchAll(app, api) h.PageFetchStatus(app, api) h.SiteList(app, api) h.SiteCreate(app, api) h.SiteUpdate(app, api) h.SiteDelete(app, api) h.UserList(app, api) h.UserCreate(app, api) h.UserUpdate(app, api) h.UserDelete(app, api) h.CacheWarmUp(app, api) h.CacheFlush(app, api) h.EmailSend(app, api) h.VoteSync(app, api) h.SettingGet(app, api) h.SettingApply(app, api) h.SettingTemplate(app, api) h.Transfer(app, api) } func reqID(fb *fiber.App) { fb.Use(requestid.New()) } func logger(fb *fiber.App, debugMode bool) { fb.Use(fiber_logger.New(fiber_logger.Config{ Format: "[HTTP] ${time} | ${status} | ${latency} | ${ip} | ${method} | ${path} | ${error} | ${respHeader:X-Request-ID}", Output: io.Discard, DisableColors: true, Done: func(c *fiber.Ctx, msg []byte) { l := log.StandardLogger().WithOptions( zap.AddStacktrace(zapcore.DPanicLevel), zap.WithCaller(false), ) code := c.Response().StatusCode() if (code >= 200 && code <= 299) || (code >= 300 && code <= 308) { if debugMode { l.Info(string(msg)) } } else { l.Error(string(msg)) } }, })) } func cors(app *core.App, f fiber.Router) { f.Use(middleware.CorsMiddleware(app)) } func actionLimit(app *core.App, f fiber.Router) { f.Use(limiter.ActionLimitMiddleware(app, limiter.ActionLimitConf{})) } func static(f fiber.Router) { f.Use("/", filesystem.New(filesystem.Config{ Root: http.FS(pkged.FS()), PathPrefix: "public", Browse: false, })) } func index(f fiber.Router) { f.All("/", func(c *fiber.Ctx) error { if _, err := pkged.FS().Open("public/sidebar/index.html"); err == nil { return c.Redirect("./sidebar/", fiber.StatusFound) } return c.Status(fiber.StatusOK).JSON(common.GetApiVersionDataMap()) }) } func uploadedStatic(app *core.App, f fiber.Router) { // upload static resource accessible path f.Static(config.IMG_UPLOAD_PUBLIC_PATH, app.Conf().ImgUpload.Path) } func swaggerDocs(f fiber.Router) { f.Get("/swagger/*", swagger.HandlerDefault) }