mirror of
https://github.com/gogs/gogs.git
synced 2026-02-01 20:19:23 +01:00
27 KiB
27 KiB
Flamego Migration: Code Examples
This document provides practical, side-by-side code examples showing how to migrate from Macaron to Flamego in the Gogs codebase.
Table of Contents
- Basic Application Setup
- Middleware Configuration
- Route Definitions
- Handler Functions
- Context Usage
- Form Binding
- Template Rendering
- Custom Middleware
- Complete Example
Basic Application Setup
Macaron (Current)
// internal/cmd/web.go
package cmd
import (
"gopkg.in/macaron.v1"
"github.com/go-macaron/session"
"github.com/go-macaron/csrf"
)
func newMacaron() *macaron.Macaron {
m := macaron.New()
// Basic middleware
if !conf.Server.DisableRouterLog {
m.Use(macaron.Logger())
}
m.Use(macaron.Recovery())
// Optional gzip
if conf.Server.EnableGzip {
m.Use(gzip.Gziper())
}
return m
}
func runWeb(c *cli.Context) error {
m := newMacaron()
// Configure routes
m.Get("/", route.Home)
// Start server
return http.ListenAndServe(":3000", m)
}
Flamego (Target)
// internal/cmd/web.go
package cmd
import (
"github.com/flamego/flamego"
"github.com/flamego/session"
"github.com/flamego/csrf"
)
func newFlamego() *flamego.Flame {
f := flamego.New()
// Basic middleware
if !conf.Server.DisableRouterLog {
f.Use(flamego.Logger())
}
f.Use(flamego.Recovery())
// Optional gzip
if conf.Server.EnableGzip {
f.Use(gzip.Gziper())
}
return f
}
func runWeb(c *cli.Context) error {
f := newFlamego()
// Configure routes
f.Get("/", route.Home)
// Start server
return f.Run(":3000")
}
Middleware Configuration
Session Middleware
Macaron (Current)
import "github.com/go-macaron/session"
m.Use(session.Sessioner(session.Options{
Provider: conf.Session.Provider,
ProviderConfig: conf.Session.ProviderConfig,
CookieName: conf.Session.CookieName,
CookiePath: conf.Server.Subpath,
Gclifetime: conf.Session.GCInterval,
Maxlifetime: conf.Session.MaxLifeTime,
Secure: conf.Session.CookieSecure,
}))
// Handler usage
func handler(sess session.Store) {
sess.Set("user_id", 123)
userID := sess.Get("user_id")
}
Flamego (Target)
import "github.com/flamego/session"
f.Use(session.Sessioner(session.Options{
// Config depends on provider type
Config: session.RedisConfig{
Options: &redis.Options{
Addr: conf.Session.ProviderConfig,
},
},
Cookie: session.CookieOptions{
Name: conf.Session.CookieName,
Path: conf.Server.Subpath,
MaxAge: conf.Session.MaxLifeTime,
Secure: conf.Session.CookieSecure,
},
// For memory provider:
// Config: session.MemoryConfig{
// GCInterval: conf.Session.GCInterval,
// },
}))
// Handler usage - interface name changed
func handler(sess session.Session) {
sess.Set("user_id", 123)
userID := sess.Get("user_id")
}
CSRF Middleware
Macaron (Current)
import "github.com/go-macaron/csrf"
m.Use(csrf.Csrfer(csrf.Options{
Secret: conf.Security.SecretKey,
Header: "X-CSRF-Token",
Cookie: conf.Session.CSRFCookieName,
CookieDomain: conf.Server.URL.Hostname(),
CookiePath: conf.Server.Subpath,
CookieHttpOnly: true,
SetCookie: true,
Secure: conf.Server.URL.Scheme == "https",
}))
// Handler usage
func handler(x csrf.CSRF) {
token := x.GetToken()
}
Flamego (Target)
import "github.com/flamego/csrf"
f.Use(csrf.Csrfer(csrf.Options{
Secret: conf.Security.SecretKey,
Header: "X-CSRF-Token",
Cookie: conf.Session.CSRFCookieName,
CookiePath: conf.Server.Subpath,
Secure: conf.Server.URL.Scheme == "https",
}))
// Handler usage - method name changed
func handler(x csrf.CSRF) {
token := x.Token() // Changed from GetToken()
}
Template Middleware
Macaron (Current)
m.Use(macaron.Renderer(macaron.RenderOptions{
Directory: filepath.Join(conf.WorkDir(), "templates"),
AppendDirectories: []string{customDir},
Funcs: template.FuncMap(),
IndentJSON: macaron.Env != macaron.PROD,
}))
// Handler usage
func handler(c *macaron.Context) {
c.Data["Title"] = "Home"
c.HTML(200, "home")
}
Flamego (Target)
import "github.com/flamego/template"
f.Use(template.Templater(template.Options{
Directory: filepath.Join(conf.WorkDir(), "templates"),
AppendDirectories: []string{customDir},
FuncMaps: []template.FuncMap{template.FuncMap()},
}))
// Handler usage - separate template and data injection
func handler(t template.Template, data template.Data) {
data["Title"] = "Home"
t.HTML(200, "home")
}
Cache Middleware
Macaron (Current)
import "github.com/go-macaron/cache"
m.Use(cache.Cacher(cache.Options{
Adapter: conf.Cache.Adapter,
AdapterConfig: conf.Cache.Host,
Interval: conf.Cache.Interval,
}))
// Handler usage
func handler(cache cache.Cache) {
cache.Put("key", "value", 60)
value := cache.Get("key")
cache.Delete("key")
}
Flamego (Target)
import "github.com/flamego/cache"
var cacheConfig cache.Config
switch conf.Cache.Adapter {
case "memory":
cacheConfig = cache.MemoryConfig{
GCInterval: conf.Cache.Interval,
}
case "redis":
cacheConfig = cache.RedisConfig{
Options: &redis.Options{
Addr: conf.Cache.Host,
},
}
}
f.Use(cache.Cacher(cache.Options{
Config: cacheConfig,
}))
// Handler usage - method names changed
func handler(c cache.Cache) {
c.Set("key", "value", 60) // Changed from Put
value := c.Get("key")
c.Delete("key")
}
i18n Middleware
Macaron (Current)
import "github.com/go-macaron/i18n"
m.Use(i18n.I18n(i18n.Options{
SubURL: conf.Server.Subpath,
Files: localeFiles,
CustomDirectory: filepath.Join(conf.CustomDir(), "conf", "locale"),
Langs: conf.I18n.Langs,
Names: conf.I18n.Names,
DefaultLang: "en-US",
Redirect: true,
}))
// Handler usage
func handler(l i18n.Locale) {
text := l.Tr("user.login")
}
Flamego (Target)
import "github.com/flamego/i18n"
f.Use(i18n.I18n(i18n.Options{
URLPrefix: conf.Server.Subpath,
Files: localeFiles,
CustomDirectory: filepath.Join(conf.CustomDir(), "conf", "locale"),
Languages: conf.I18n.Langs, // Changed from Langs
Names: conf.I18n.Names,
DefaultLanguage: "en-US", // Changed from DefaultLang
Redirect: true,
}))
// Handler usage - same interface
func handler(l i18n.Locale) {
text := l.Tr("user.login")
}
Route Definitions
Basic Routes
Macaron (Current)
m.Get("/", ignSignIn, route.Home)
m.Post("/login", bindIgnErr(form.SignIn{}), user.LoginPost)
m.Get("/:username", user.Profile)
m.Get("/:username/:reponame", context.RepoAssignment(), repo.Home)
Flamego (Target)
f.Get("/", ignSignIn, route.Home)
f.Post("/login", binding.Form(form.SignIn{}), user.LoginPost)
f.Get("/<username>", user.Profile)
f.Get("/<username>/<reponame>", context.RepoAssignment(), repo.Home)
Key Changes:
:parambecomes<param>bindIgnErr(form)becomesbinding.Form(form)
Route Groups
Macaron (Current)
m.Group("/user", func() {
m.Group("/login", func() {
m.Combo("").Get(user.Login).
Post(bindIgnErr(form.SignIn{}), user.LoginPost)
m.Combo("/two_factor").Get(user.LoginTwoFactor).
Post(user.LoginTwoFactorPost)
})
m.Get("/sign_up", user.SignUp)
m.Post("/sign_up", bindIgnErr(form.Register{}), user.SignUpPost)
}, reqSignOut)
Flamego (Target)
f.Group("/user", func() {
f.Group("/login", func() {
f.Combo("").Get(user.Login).
Post(binding.Form(form.SignIn{}), user.LoginPost)
f.Combo("/two_factor").Get(user.LoginTwoFactor).
Post(user.LoginTwoFactorPost)
})
f.Get("/sign_up", user.SignUp)
f.Post("/sign_up", binding.Form(form.Register{}), user.SignUpPost)
}, reqSignOut)
Key Changes:
m.Groupbecomesf.GroupbindIgnErrbecomesbinding.Form
Regex Routes
Macaron (Current)
m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues)
Flamego (Target)
f.Get("/<type:issues|pulls>", reqSignIn, user.Issues)
Route with Optional Segments
Macaron (Current)
// Not well supported - need multiple routes
m.Get("/wiki", repo.Wiki)
m.Get("/wiki/:page", repo.Wiki)
Flamego (Target)
// Better support for optional segments
f.Get("/wiki/?<page>", repo.Wiki)
Handler Functions
Basic Handler
Macaron (Current)
func Home(c *context.Context) {
c.Data["Title"] = "Home"
c.HTML(http.StatusOK, "home")
}
Flamego (Target)
func Home(c *context.Context, t template.Template, data template.Data) {
data["Title"] = "Home"
t.HTML(http.StatusOK, "home")
}
// Note: context.Context needs to be updated to wrap flamego.Context
Handler with Parameters
Macaron (Current)
func UserProfile(c *context.Context) {
username := c.Params(":username")
user, err := database.GetUserByName(username)
if err != nil {
c.NotFoundOrError(err, "get user")
return
}
c.Data["User"] = user
c.HTML(http.StatusOK, "user/profile")
}
Flamego (Target)
func UserProfile(c *context.Context, t template.Template, data template.Data) {
username := c.Param("username") // No colon prefix
user, err := database.GetUserByName(username)
if err != nil {
c.NotFoundOrError(err, "get user")
return
}
data["User"] = user
t.HTML(http.StatusOK, "user/profile")
}
Handler with Form Binding
Macaron (Current)
type LoginForm struct {
Username string `form:"username" binding:"Required"`
Password string `form:"password" binding:"Required"`
}
func LoginPost(c *context.Context, form LoginForm) {
if !database.ValidateUser(form.Username, form.Password) {
c.RenderWithErr("Invalid credentials", "user/login", &form)
return
}
c.Session.Set("user_id", user.ID)
c.Redirect("/")
}
Flamego (Target)
type LoginForm struct {
Username string `form:"username" validate:"required"`
Password string `form:"password" validate:"required"`
}
func LoginPost(c *context.Context, form LoginForm, t template.Template, data template.Data) {
if !database.ValidateUser(form.Username, form.Password) {
c.RenderWithErr("Invalid credentials", "user/login", &form, t, data)
return
}
c.Session().Set("user_id", user.ID)
c.Redirect("/")
}
Handler with Session
Macaron (Current)
func RequireLogin(c *context.Context, sess session.Store) {
userID := sess.Get("user_id")
if userID == nil {
c.Redirect("/login")
return
}
user, err := database.GetUserByID(userID.(int64))
if err != nil {
c.Error(err, "get user")
return
}
c.User = user
}
Flamego (Target)
func RequireLogin(c *context.Context, sess session.Session) {
userID := sess.Get("user_id")
if userID == nil {
c.Redirect("/login")
return
}
user, err := database.GetUserByID(userID.(int64))
if err != nil {
c.Error(err, "get user")
return
}
c.User = user
}
JSON API Handler
Macaron (Current)
func APIUserInfo(c *context.APIContext) {
user := c.User
c.JSON(http.StatusOK, &api.User{
ID: user.ID,
Username: user.Name,
Email: user.Email,
})
}
Flamego (Target)
import "encoding/json"
func APIUserInfo(c *context.APIContext) {
user := c.User
resp := &api.User{
ID: user.ID,
Username: user.Name,
Email: user.Email,
}
c.ResponseWriter().Header().Set("Content-Type", "application/json")
c.ResponseWriter().WriteHeader(http.StatusOK)
json.NewEncoder(c.ResponseWriter()).Encode(resp)
}
// Or create a helper method on context.APIContext
func (c *APIContext) JSON(status int, v any) {
c.ResponseWriter().Header().Set("Content-Type", "application/json")
c.ResponseWriter().WriteHeader(status)
json.NewEncoder(c.ResponseWriter()).Encode(v)
}
Context Usage
Context Wrapper Update
Macaron (Current)
// internal/context/context.go
package context
import (
"github.com/go-macaron/cache"
"github.com/go-macaron/csrf"
"github.com/go-macaron/session"
"gopkg.in/macaron.v1"
)
type Context struct {
*macaron.Context
Cache cache.Cache
csrf csrf.CSRF
Flash *session.Flash
Session session.Store
Link string
User *database.User
IsLogged bool
Repo *Repository
Org *Organization
}
// Contexter middleware
func Contexter(store Store) macaron.Handler {
return func(
ctx *macaron.Context,
l i18n.Locale,
cache cache.Cache,
sess session.Store,
f *session.Flash,
x csrf.CSRF,
) {
c := &Context{
Context: ctx,
Cache: cache,
csrf: x,
Flash: f,
Session: sess,
}
// Authentication logic...
c.User, c.IsBasicAuth, c.IsTokenAuth = authenticatedUser(store, c.Context, c.Session)
ctx.Map(c)
}
}
Flamego (Target)
// internal/context/context.go
package context
import (
"github.com/flamego/flamego"
"github.com/flamego/cache"
"github.com/flamego/csrf"
"github.com/flamego/session"
)
type Context struct {
flamego.Context // Embedded instead of pointer
cache cache.Cache
csrf csrf.CSRF
flash *session.Flash
session session.Session
Link string
User *database.User
IsLogged bool
Repo *Repository
Org *Organization
}
// Accessor methods
func (c *Context) Cache() cache.Cache { return c.cache }
func (c *Context) CSRF() csrf.CSRF { return c.csrf }
func (c *Context) Flash() *session.Flash { return c.flash }
func (c *Context) Session() session.Session { return c.session }
// Contexter middleware
func Contexter(store Store) flamego.Handler {
return func(
ctx flamego.Context,
l i18n.Locale,
cache cache.Cache,
sess session.Session,
f *session.Flash,
x csrf.CSRF,
) {
c := &Context{
Context: ctx,
cache: cache,
csrf: x,
flash: f,
session: sess,
}
// Authentication logic - note Session is now a method
c.User, c.IsBasicAuth, c.IsTokenAuth = authenticatedUser(store, c, c.session)
ctx.MapTo(c, (*Context)(nil))
}
}
Response Methods
Macaron (Current)
func (c *Context) HTML(status int, name string) {
log.Trace("Template: %s", name)
c.Context.HTML(status, name)
}
func (c *Context) JSON(status int, data any) {
c.Context.JSON(status, data)
}
Flamego (Target)
// These methods need to be updated to work with injected services
// Option 1: Require template.Template injection
func (c *Context) HTML(status int, name string, t template.Template, data template.Data) {
log.Trace("Template: %s", name)
// Copy c.Data to template.Data if needed
for k, v := range c.Data {
data[k] = v
}
t.HTML(status, name)
}
// Option 2: Store template reference in context during initialization
func (c *Context) HTML(status int, name string) {
if c.template == nil {
panic("template not initialized")
}
log.Trace("Template: %s", name)
c.template.HTML(status, name)
}
func (c *Context) JSON(status int, data any) {
c.ResponseWriter().Header().Set("Content-Type", "application/json")
c.ResponseWriter().WriteHeader(status)
json.NewEncoder(c.ResponseWriter()).Encode(data)
}
Form Binding
Form Struct Tags
Macaron (Current)
type CreateRepoForm struct {
UserID int64 `form:"user_id" binding:"Required"`
RepoName string `form:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
Private bool `form:"private"`
Description string `form:"description" binding:"MaxSize(255)"`
AutoInit bool `form:"auto_init"`
Gitignores string `form:"gitignores"`
License string `form:"license"`
Readme string `form:"readme"`
}
Flamego (Target)
type CreateRepoForm struct {
UserID int64 `form:"user_id" validate:"required"`
RepoName string `form:"repo_name" validate:"required,alphaDashDot,max=100"`
Private bool `form:"private"`
Description string `form:"description" validate:"max=255"`
AutoInit bool `form:"auto_init"`
Gitignores string `form:"gitignores"`
License string `form:"license"`
Readme string `form:"readme"`
}
// Note: Custom validators like AlphaDashDot need to be registered with Flamego's validator
Custom Validators
Macaron (Current)
import "github.com/go-macaron/binding"
const (
AlphaDashDotSlash binding.Rule = "AlphaDashDotSlash"
)
func init() {
binding.SetNameMapper(com.ToSnakeCase)
binding.AddRule(&binding.Rule{
IsMatch: func(rule string) bool {
return rule == "AlphaDashDotSlash"
},
IsValid: func(errs binding.Errors, name string, v interface{}) (bool, binding.Errors) {
str := v.(string)
if !alphaDashDotSlashPattern.MatchString(str) {
errs = append(errs, binding.Error{
FieldNames: []string{name},
Message: name + " must be valid alpha, dash, dot or slash",
})
return false, errs
}
return true, errs
},
})
}
Flamego (Target)
import (
"github.com/flamego/binding"
"github.com/go-playground/validator/v10"
)
func init() {
// Register custom validator
binding.RegisterValidation("alphaDashDotSlash", func(fl validator.FieldLevel) bool {
str := fl.Field().String()
return alphaDashDotSlashPattern.MatchString(str)
})
}
// Usage in struct
type Form struct {
Path string `form:"path" validate:"required,alphaDashDotSlash"`
}
Multipart Form
Macaron (Current)
import "github.com/go-macaron/binding"
type AvatarForm struct {
Avatar *multipart.FileHeader `form:"avatar"`
}
m.Post("/avatar", binding.MultipartForm(AvatarForm{}), handler)
Flamego (Target)
import "github.com/flamego/binding"
type AvatarForm struct {
Avatar *multipart.FileHeader `form:"avatar"`
}
f.Post("/avatar", binding.MultipartForm(AvatarForm{}), handler)
Template Rendering
Render with Data
Macaron (Current)
func ShowRepo(c *context.Context) {
c.Data["Title"] = c.Repo.Repository.Name
c.Data["Owner"] = c.Repo.Owner
c.Data["Repository"] = c.Repo.Repository
c.Data["IsRepositoryAdmin"] = c.Repo.IsAdmin()
c.HTML(http.StatusOK, "repo/home")
}
Flamego (Target)
func ShowRepo(c *context.Context, t template.Template, data template.Data) {
data["Title"] = c.Repo.Repository.Name
data["Owner"] = c.Repo.Owner
data["Repository"] = c.Repo.Repository
data["IsRepositoryAdmin"] = c.Repo.IsAdmin()
t.HTML(http.StatusOK, "repo/home")
}
// Or if context has template reference:
func ShowRepo(c *context.Context) {
c.Data["Title"] = c.Repo.Repository.Name
c.Data["Owner"] = c.Repo.Owner
c.Data["Repository"] = c.Repo.Repository
c.Data["IsRepositoryAdmin"] = c.Repo.IsAdmin()
c.HTML(http.StatusOK, "repo/home")
}
Render with Error
Macaron (Current)
func (c *Context) RenderWithErr(msg, tpl string, f any) {
if f != nil {
form.Assign(f, c.Data)
}
c.Flash.ErrorMsg = msg
c.Data["Flash"] = c.Flash
c.HTML(http.StatusOK, tpl)
}
Flamego (Target)
func (c *Context) RenderWithErr(msg, tpl string, f any, t template.Template, data template.Data) {
if f != nil {
form.Assign(f, c.Data)
// Also need to assign to data
for k, v := range c.Data {
data[k] = v
}
}
c.Flash().ErrorMsg = msg
data["Flash"] = c.Flash()
t.HTML(http.StatusOK, tpl)
}
Custom Middleware
Authentication Middleware
Macaron (Current)
func Toggle(options *ToggleOptions) macaron.Handler {
return func(c *Context) {
// Check authentication
if options.SignInRequired {
if !c.IsLogged {
c.SetCookie("redirect_to", c.Req.RequestURI, 0, conf.Server.Subpath)
c.Redirect(conf.Server.Subpath + "/user/login")
return
}
}
// Check admin
if options.AdminRequired {
if !c.User.IsAdmin {
c.Error(nil, http.StatusForbidden)
return
}
}
}
}
Flamego (Target)
func Toggle(options *ToggleOptions) flamego.Handler {
return func(c *Context) {
// Check authentication
if options.SignInRequired {
if !c.IsLogged {
c.SetCookie(http.Cookie{
Name: "redirect_to",
Value: c.Request().RequestURI,
Path: conf.Server.Subpath,
})
c.Redirect(conf.Server.Subpath + "/user/login")
return
}
}
// Check admin
if options.AdminRequired {
if !c.User.IsAdmin {
c.Error(nil, http.StatusForbidden)
return
}
}
}
}
Repository Context Middleware
Macaron (Current)
func RepoAssignment() macaron.Handler {
return func(c *Context) {
userName := c.Params(":username")
repoName := c.Params(":reponame")
owner, err := database.GetUserByName(userName)
if err != nil {
c.NotFoundOrError(err, "get user")
return
}
c.Repo.Owner = owner
repo, err := database.GetRepositoryByName(owner.ID, repoName)
if err != nil {
c.NotFoundOrError(err, "get repository")
return
}
c.Repo.Repository = repo
}
}
Flamego (Target)
func RepoAssignment() flamego.Handler {
return func(c *Context) {
userName := c.Param("username") // No colon prefix
repoName := c.Param("reponame")
owner, err := database.GetUserByName(userName)
if err != nil {
c.NotFoundOrError(err, "get user")
return
}
c.Repo.Owner = owner
repo, err := database.GetRepositoryByName(owner.ID, repoName)
if err != nil {
c.NotFoundOrError(err, "get repository")
return
}
c.Repo.Repository = repo
}
}
Complete Example
Full Route Handler Chain
Macaron (Current)
// Setup
m := macaron.New()
m.Use(macaron.Logger())
m.Use(macaron.Recovery())
m.Use(session.Sessioner())
m.Use(csrf.Csrfer())
m.Use(context.Contexter(store))
// Middleware
reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
// Routes
m.Group("/:username/:reponame", func() {
m.Get("/issues", repo.Issues)
m.Combo("/issues/new").
Get(repo.NewIssue).
Post(bindIgnErr(form.NewIssue{}), repo.NewIssuePost)
}, reqSignIn, context.RepoAssignment())
// Handler
func NewIssuePost(c *context.Context, form form.NewIssue) {
if c.HasError() {
c.RenderWithErr(c.GetErrMsg(), "repo/issue/new", &form)
return
}
issue, err := database.NewIssue(&database.Issue{
RepoID: c.Repo.Repository.ID,
Index: c.Repo.Repository.NextIssueIndex(),
Title: form.Title,
Content: form.Content,
})
if err != nil {
c.Error(err, "create issue")
return
}
c.Redirect(fmt.Sprintf("/%s/%s/issues/%d",
c.Repo.Owner.Name, c.Repo.Repository.Name, issue.Index))
}
Flamego (Target)
// Setup
f := flamego.New()
f.Use(flamego.Logger())
f.Use(flamego.Recovery())
f.Use(session.Sessioner())
f.Use(csrf.Csrfer())
f.Use(template.Templater())
f.Use(context.Contexter(store))
// Middleware
reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
// Routes - note parameter syntax change
f.Group("/<username>/<reponame>", func() {
f.Get("/issues", repo.Issues)
f.Combo("/issues/new").
Get(repo.NewIssue).
Post(binding.Form(form.NewIssue{}), repo.NewIssuePost)
}, reqSignIn, context.RepoAssignment())
// Handler - note template injection
func NewIssuePost(
c *context.Context,
form form.NewIssue,
t template.Template,
data template.Data,
) {
if c.HasError() {
c.RenderWithErr(c.GetErrMsg(), "repo/issue/new", &form, t, data)
return
}
issue, err := database.NewIssue(&database.Issue{
RepoID: c.Repo.Repository.ID,
Index: c.Repo.Repository.NextIssueIndex(),
Title: form.Title,
Content: form.Content,
})
if err != nil {
c.Error(err, "create issue")
return
}
c.Redirect(fmt.Sprintf("/%s/%s/issues/%d",
c.Repo.Owner.Name, c.Repo.Repository.Name, issue.Index))
}
Key Takeaways
- Parameter Names: Remove
:prefix when getting params (c.Param("name")vsc.Params(":name")) - Route Syntax: Use
<param>instead of:param - Interface Names:
session.Store→session.Session - Method Names:
GetToken()→Token(),Put()→Set() - Template Injection: Need to inject
template.Templateandtemplate.Dataparameters - Response Access:
c.Resp→c.ResponseWriter() - Request Access:
c.Req→c.Request() - Context Embedding: Use
flamego.Contextinterface instead of*macaron.Contextpointer
Summary
The migration from Macaron to Flamego is largely mechanical with clear patterns:
- Most middleware has direct equivalents
- Handler signatures gain template parameters
- Route parameter syntax changes
- Context access changes from fields to methods
- Overall structure and patterns remain similar
The main work is updating ~150+ files to follow these new patterns consistently.