@@ -1,6 +1,6 @@ | |||||
# koushin | |||||
# alps | |||||
[![GoDoc](https://godoc.org/git.sr.ht/~emersion/koushin?status.svg)](https://godoc.org/git.sr.ht/~emersion/koushin) | |||||
[![GoDoc](https://godoc.org/git.sr.ht/~emersion/alps?status.svg)](https://godoc.org/git.sr.ht/~emersion/alps) | |||||
A simple and extensible webmail. | A simple and extensible webmail. | ||||
@@ -8,17 +8,17 @@ A simple and extensible webmail. | |||||
Assuming SRV DNS records are properly set up (see [RFC 6186]): | Assuming SRV DNS records are properly set up (see [RFC 6186]): | ||||
go run ./cmd/koushin example.org | |||||
go run ./cmd/alps example.org | |||||
To manually specify upstream servers: | To manually specify upstream servers: | ||||
go run ./cmd/koushin imaps://mail.example.org:993 smtps://mail.example.org:465 | |||||
go run ./cmd/alps imaps://mail.example.org:993 smtps://mail.example.org:465 | |||||
Add `-theme sourcehut` to use the SourceHut theme. See `docs/cli.md` for more | Add `-theme sourcehut` to use the SourceHut theme. See `docs/cli.md` for more | ||||
information. | information. | ||||
When developing themes and plugins, the script `contrib/hotreload.sh` can be | When developing themes and plugins, the script `contrib/hotreload.sh` can be | ||||
used to automatically reload koushin on file changes. | |||||
used to automatically reload alps on file changes. | |||||
## Contributing | ## Contributing | ||||
@@ -29,6 +29,6 @@ Send patches on the [mailing list], report bugs on the [issue tracker]. | |||||
MIT | MIT | ||||
[RFC 6186]: https://tools.ietf.org/html/rfc6186 | [RFC 6186]: https://tools.ietf.org/html/rfc6186 | ||||
[Go plugin helpers]: https://godoc.org/git.sr.ht/~emersion/koushin#GoPlugin | |||||
[mailing list]: https://lists.sr.ht/~sircmpwn/koushin | |||||
[issue tracker]: https://todo.sr.ht/~sircmpwn/koushin | |||||
[Go plugin helpers]: https://godoc.org/git.sr.ht/~emersion/alps#GoPlugin | |||||
[mailing list]: https://lists.sr.ht/~sircmpwn/alps | |||||
[issue tracker]: https://todo.sr.ht/~sircmpwn/alps |
@@ -7,28 +7,28 @@ import ( | |||||
"os/signal" | "os/signal" | ||||
"syscall" | "syscall" | ||||
"git.sr.ht/~emersion/koushin" | |||||
"git.sr.ht/~emersion/alps" | |||||
"github.com/labstack/echo/v4" | "github.com/labstack/echo/v4" | ||||
"github.com/labstack/echo/v4/middleware" | "github.com/labstack/echo/v4/middleware" | ||||
"github.com/labstack/gommon/log" | "github.com/labstack/gommon/log" | ||||
_ "git.sr.ht/~emersion/koushin/plugins/base" | |||||
_ "git.sr.ht/~emersion/koushin/plugins/caldav" | |||||
_ "git.sr.ht/~emersion/koushin/plugins/carddav" | |||||
_ "git.sr.ht/~emersion/koushin/plugins/lua" | |||||
_ "git.sr.ht/~emersion/koushin/plugins/viewhtml" | |||||
_ "git.sr.ht/~emersion/koushin/plugins/viewtext" | |||||
_ "git.sr.ht/~emersion/alps/plugins/base" | |||||
_ "git.sr.ht/~emersion/alps/plugins/caldav" | |||||
_ "git.sr.ht/~emersion/alps/plugins/carddav" | |||||
_ "git.sr.ht/~emersion/alps/plugins/lua" | |||||
_ "git.sr.ht/~emersion/alps/plugins/viewhtml" | |||||
_ "git.sr.ht/~emersion/alps/plugins/viewtext" | |||||
) | ) | ||||
func main() { | func main() { | ||||
var options koushin.Options | |||||
var options alps.Options | |||||
var addr string | var addr string | ||||
flag.StringVar(&options.Theme, "theme", "", "default theme") | flag.StringVar(&options.Theme, "theme", "", "default theme") | ||||
flag.StringVar(&addr, "addr", ":1323", "listening address") | flag.StringVar(&addr, "addr", ":1323", "listening address") | ||||
flag.BoolVar(&options.Debug, "debug", false, "enable debug logs") | flag.BoolVar(&options.Debug, "debug", false, "enable debug logs") | ||||
flag.Usage = func() { | flag.Usage = func() { | ||||
fmt.Fprintf(flag.CommandLine.Output(), "usage: koushin [options...] <upstream servers...>\n") | |||||
fmt.Fprintf(flag.CommandLine.Output(), "usage: alps [options...] <upstream servers...>\n") | |||||
flag.PrintDefaults() | flag.PrintDefaults() | ||||
} | } | ||||
@@ -45,7 +45,7 @@ func main() { | |||||
if l, ok := e.Logger.(*log.Logger); ok { | if l, ok := e.Logger.(*log.Logger); ok { | ||||
l.SetHeader("${time_rfc3339} ${level}") | l.SetHeader("${time_rfc3339} ${level}") | ||||
} | } | ||||
s, err := koushin.New(e, &options) | |||||
s, err := alps.New(e, &options) | |||||
if err != nil { | if err != nil { | ||||
e.Logger.Fatal(err) | e.Logger.Fatal(err) | ||||
} | } |
@@ -1,6 +1,6 @@ | |||||
#!/bin/sh | #!/bin/sh | ||||
# Watch themes and plugins files, automatically reload koushin on change. | |||||
# Watch themes and plugins files, automatically reload alps on change. | |||||
events=modify,create,delete,move | events=modify,create,delete,move | ||||
targets="themes/ plugins/" | targets="themes/ plugins/" | ||||
@@ -8,6 +8,6 @@ targets="themes/ plugins/" | |||||
inotifywait -e "$events" -m -r $targets | while read line; do | inotifywait -e "$events" -m -r $targets | while read line; do | ||||
jobs >/dev/null # Reap status of any terminated job | jobs >/dev/null # Reap status of any terminated job | ||||
if [ -z "$(jobs)" ]; then | if [ -z "$(jobs)" ]; then | ||||
(sleep 0.5 && pkill -USR1 koushin) & | |||||
(sleep 0.5 && pkill -USR1 alps) & | |||||
fi | fi | ||||
done | done |
@@ -1,4 +1,4 @@ | |||||
package koushin | |||||
package alps | |||||
import ( | import ( | ||||
"fmt" | "fmt" | ||||
@@ -1,22 +1,22 @@ | |||||
# SYNOPSIS | # SYNOPSIS | ||||
koushin [options...] <upstream servers...> | |||||
alps [options...] <upstream servers...> | |||||
# DESCRIPTION | # DESCRIPTION | ||||
koushin is a simple and extensible webmail. It offers a web interface for IMAP, | |||||
alps is a simple and extensible webmail. It offers a web interface for IMAP, | |||||
SMTP and other upstream servers. | SMTP and other upstream servers. | ||||
At least one upstream IMAP server needs to be specified. The easiest way to do | At least one upstream IMAP server needs to be specified. The easiest way to do | ||||
so is to just specify a domain name: | so is to just specify a domain name: | ||||
koushin example.org | |||||
alps example.org | |||||
This assumes SRV DNS records are properly set up (see [RFC 6186]). | This assumes SRV DNS records are properly set up (see [RFC 6186]). | ||||
Alternatively, one or more upstream server URLs can be specified: | Alternatively, one or more upstream server URLs can be specified: | ||||
koushin imaps://mail.example.org:993 smtps://mail.example.org:465 | |||||
alps imaps://mail.example.org:993 smtps://mail.example.org:465 | |||||
The following URL schemes are supported: | The following URL schemes are supported: | ||||
@@ -1,22 +1,22 @@ | |||||
// Package exampleplugin is an example Go plugin for koushin. | |||||
// Package exampleplugin is an example Go plugin for alps. | |||||
// | // | ||||
// To enable it, import this package from cmd/koushin/main.go. | |||||
// To enable it, import this package from cmd/alps/main.go. | |||||
package exampleplugin | package exampleplugin | ||||
import ( | import ( | ||||
"fmt" | "fmt" | ||||
"net/http" | "net/http" | ||||
"git.sr.ht/~emersion/koushin" | |||||
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" | |||||
"git.sr.ht/~emersion/alps" | |||||
alpsbase "git.sr.ht/~emersion/alps/plugins/base" | |||||
) | ) | ||||
func init() { | func init() { | ||||
p := koushin.GoPlugin{Name: "example"} | |||||
p := alps.GoPlugin{Name: "example"} | |||||
// Setup a function called when the mailbox view is rendered | // Setup a function called when the mailbox view is rendered | ||||
p.Inject("mailbox.html", func(ctx *koushin.Context, kdata koushin.RenderData) error { | |||||
data := kdata.(*koushinbase.MailboxRenderData) | |||||
p.Inject("mailbox.html", func(ctx *alps.Context, kdata alps.RenderData) error { | |||||
data := kdata.(*alpsbase.MailboxRenderData) | |||||
fmt.Println("The mailbox view for " + data.Mailbox.Name + " is being rendered") | fmt.Println("The mailbox view for " + data.Mailbox.Name + " is being rendered") | ||||
// Set extra data that can be accessed from the mailbox.html template | // Set extra data that can be accessed from the mailbox.html template | ||||
data.Extra["Example"] = "Hi from Go" | data.Extra["Example"] = "Hi from Go" | ||||
@@ -24,7 +24,7 @@ func init() { | |||||
}) | }) | ||||
// Wire up a new route | // Wire up a new route | ||||
p.GET("/example", func(ctx *koushin.Context) error { | |||||
p.GET("/example", func(ctx *alps.Context) error { | |||||
return ctx.String(http.StatusOK, "This is an example page.") | return ctx.String(http.StatusOK, "This is an example page.") | ||||
}) | }) | ||||
@@ -35,5 +35,5 @@ func init() { | |||||
}, | }, | ||||
}) | }) | ||||
koushin.RegisterPluginLoader(p.Loader()) | |||||
alps.RegisterPluginLoader(p.Loader()) | |||||
} | } |
@@ -2,18 +2,18 @@ | |||||
print("Hi, this is an example Lua plugin") | print("Hi, this is an example Lua plugin") | ||||
-- Setup a function called when the mailbox view is rendered | -- Setup a function called when the mailbox view is rendered | ||||
koushin.on_render("mailbox.html", function(data) | |||||
alps.on_render("mailbox.html", function(data) | |||||
print("The mailbox view for " .. data.Mailbox.Name .. " is being rendered") | print("The mailbox view for " .. data.Mailbox.Name .. " is being rendered") | ||||
-- Set extra data that can be accessed from the mailbox.html template | -- Set extra data that can be accessed from the mailbox.html template | ||||
data.Extra.Example = "Hi from Lua" | data.Extra.Example = "Hi from Lua" | ||||
end) | end) | ||||
-- Wire up a new route | -- Wire up a new route | ||||
koushin.set_route("GET", "/example", function(ctx) | |||||
alps.set_route("GET", "/example", function(ctx) | |||||
ctx:String(200, "This is an example page.") | ctx:String(200, "This is an example page.") | ||||
end) | end) | ||||
-- Set a filter function that can be used from templates | -- Set a filter function that can be used from templates | ||||
koushin.set_filter("example_and", function(a, b) | |||||
alps.set_filter("example_and", function(a, b) | |||||
return a .. " and " .. b | return a .. " and " .. b | ||||
end) | end) |
@@ -1,21 +1,21 @@ | |||||
# Running koushin with a Google account | |||||
# Running alps with a Google account | |||||
## Create an application password | ## Create an application password | ||||
First, you'll need to obtain an application-specific password for koushin from | |||||
First, you'll need to obtain an application-specific password for alps from | |||||
the [app passwords] page on your Google account. | the [app passwords] page on your Google account. | ||||
## Run koushin | |||||
## Run alps | |||||
Start koushin with these upstream URLs: | |||||
Start alps with these upstream URLs: | |||||
koushin imaps://imap.gmail.com smtps://smtp.gmail.com \ | |||||
alps imaps://imap.gmail.com smtps://smtp.gmail.com \ | |||||
carddavs://www.googleapis.com/carddav/v1/principals/YOUREMAIL/ \ | carddavs://www.googleapis.com/carddav/v1/principals/YOUREMAIL/ \ | ||||
caldavs://www.google.com/calendar/dav | caldavs://www.google.com/calendar/dav | ||||
Replace `YOUREMAIL` with your Google account's e-mail address. | Replace `YOUREMAIL` with your Google account's e-mail address. | ||||
Once koushin is started, you can login with your e-mail address and the app | |||||
Once alps is started, you can login with your e-mail address and the app | |||||
password. | password. | ||||
[app passwords]: https://security.google.com/settings/security/apppasswords | [app passwords]: https://security.google.com/settings/security/apppasswords |
@@ -17,7 +17,7 @@ Assets in `plugins/<name>/public/assets/*` are served by the HTTP server at | |||||
## Go plugins | ## Go plugins | ||||
They can use the [Go plugin helpers] and need to be included at compile-time in | They can use the [Go plugin helpers] and need to be included at compile-time in | ||||
`cmd/koushin/main.go`. | |||||
`cmd/alps/main.go`. | |||||
## Lua plugins | ## Lua plugins | ||||
@@ -25,8 +25,8 @@ The entry point is at `plugins/<name>/main.lua`. | |||||
API: | API: | ||||
* `koushin.on_render(name, f)`: prior to rendering the template `name`, call | |||||
* `alps.on_render(name, f)`: prior to rendering the template `name`, call | |||||
`f` with the template data (the special name `*` matches all templates) | `f` with the template data (the special name `*` matches all templates) | ||||
* `koushin.set_filter(name, f)`: set a template function | |||||
* `koushin.set_route(method, path, f)`: register a new HTTP route, `f` will be | |||||
* `alps.set_filter(name, f)`: set a template function | |||||
* `alps.set_route(method, path, f)`: register a new HTTP route, `f` will be | |||||
called with the HTTP context | called with the HTTP context |
@@ -1,4 +1,4 @@ | |||||
module git.sr.ht/~emersion/koushin | |||||
module git.sr.ht/~emersion/alps | |||||
go 1.13 | go 1.13 | ||||
@@ -1,4 +1,4 @@ | |||||
package koushin | |||||
package alps | |||||
import ( | import ( | ||||
"fmt" | "fmt" | ||||
@@ -1,4 +1,4 @@ | |||||
package koushin | |||||
package alps | |||||
import ( | import ( | ||||
"html/template" | "html/template" | ||||
@@ -9,7 +9,7 @@ import ( | |||||
// PluginDir is the path to the plugins directory. | // PluginDir is the path to the plugins directory. | ||||
const PluginDir = "plugins" | const PluginDir = "plugins" | ||||
// Plugin extends koushin with additional functionality. | |||||
// Plugin extends alps with additional functionality. | |||||
type Plugin interface { | type Plugin interface { | ||||
// Name should return the plugin name. | // Name should return the plugin name. | ||||
Name() string | Name() string | ||||
@@ -1,4 +1,4 @@ | |||||
package koushin | |||||
package alps | |||||
import ( | import ( | ||||
"html/template" | "html/template" | ||||
@@ -71,7 +71,7 @@ type goPluginRoute struct { | |||||
// | // | ||||
// p := GoPlugin{Name: "my-plugin"} | // p := GoPlugin{Name: "my-plugin"} | ||||
// // Define routes, template functions, etc | // // Define routes, template functions, etc | ||||
// koushin.RegisterPluginLoader(p.Loader()) | |||||
// alps.RegisterPluginLoader(p.Loader()) | |||||
type GoPlugin struct { | type GoPlugin struct { | ||||
Name string | Name string | ||||
@@ -1,4 +1,4 @@ | |||||
package koushinbase | |||||
package alpsbase | |||||
import ( | import ( | ||||
"bufio" | "bufio" | ||||
@@ -1,14 +1,14 @@ | |||||
package koushinbase | |||||
package alpsbase | |||||
import ( | import ( | ||||
"git.sr.ht/~emersion/koushin" | |||||
"git.sr.ht/~emersion/alps" | |||||
) | ) | ||||
func init() { | func init() { | ||||
p := koushin.GoPlugin{Name: "base"} | |||||
p := alps.GoPlugin{Name: "base"} | |||||
p.TemplateFuncs(templateFuncs) | p.TemplateFuncs(templateFuncs) | ||||
registerRoutes(&p) | registerRoutes(&p) | ||||
koushin.RegisterPluginLoader(p.Loader()) | |||||
alps.RegisterPluginLoader(p.Loader()) | |||||
} | } |
@@ -1,6 +1,6 @@ | |||||
{{template "head.html"}} | {{template "head.html"}} | ||||
<h1>koushin</h1> | |||||
<h1>alps</h1> | |||||
<p> | <p> | ||||
<a href="/mailbox/INBOX">Back</a> | <a href="/mailbox/INBOX">Back</a> | ||||
@@ -2,6 +2,6 @@ | |||||
<html lang="en"> | <html lang="en"> | ||||
<head> | <head> | ||||
<meta charset="utf-8"> | <meta charset="utf-8"> | ||||
<title>koushin</title> | |||||
<title>alps</title> | |||||
</head> | </head> | ||||
<body> | <body> |
@@ -1,6 +1,6 @@ | |||||
{{template "head.html"}} | {{template "head.html"}} | ||||
<h1>koushin</h1> | |||||
<h1>alps</h1> | |||||
<form method="post" action=""> | <form method="post" action=""> | ||||
<label for="username">Username:</label> | <label for="username">Username:</label> | ||||
@@ -1,6 +1,6 @@ | |||||
{{template "head.html"}} | {{template "head.html"}} | ||||
<h1>koushin</h1> | |||||
<h1>alps</h1> | |||||
<p> | <p> | ||||
<a href="/logout">Logout</a> | <a href="/logout">Logout</a> | ||||
@@ -1,6 +1,6 @@ | |||||
{{template "head.html"}} | {{template "head.html"}} | ||||
<h1>koushin</h1> | |||||
<h1>alps</h1> | |||||
<p> | <p> | ||||
<a href="/mailbox/{{.Mailbox.Name | pathescape}}?page={{.MailboxPage}}"> | <a href="/mailbox/{{.Mailbox.Name | pathescape}}?page={{.MailboxPage}}"> | ||||
@@ -1,6 +1,6 @@ | |||||
{{template "head.html"}} | {{template "head.html"}} | ||||
<h1>koushin</h1> | |||||
<h1>alps</h1> | |||||
<p> | <p> | ||||
<a href="/mailbox/INBOX">Back</a> | <a href="/mailbox/INBOX">Back</a> | ||||
@@ -1,4 +1,4 @@ | |||||
package koushinbase | |||||
package alpsbase | |||||
import ( | import ( | ||||
"bytes" | "bytes" | ||||
@@ -11,7 +11,7 @@ import ( | |||||
"strconv" | "strconv" | ||||
"strings" | "strings" | ||||
"git.sr.ht/~emersion/koushin" | |||||
"git.sr.ht/~emersion/alps" | |||||
"github.com/emersion/go-imap" | "github.com/emersion/go-imap" | ||||
imapmove "github.com/emersion/go-imap-move" | imapmove "github.com/emersion/go-imap-move" | ||||
imapclient "github.com/emersion/go-imap/client" | imapclient "github.com/emersion/go-imap/client" | ||||
@@ -21,18 +21,18 @@ import ( | |||||
"github.com/labstack/echo/v4" | "github.com/labstack/echo/v4" | ||||
) | ) | ||||
func registerRoutes(p *koushin.GoPlugin) { | |||||
p.GET("/", func(ctx *koushin.Context) error { | |||||
func registerRoutes(p *alps.GoPlugin) { | |||||
p.GET("/", func(ctx *alps.Context) error { | |||||
return ctx.Redirect(http.StatusFound, "/mailbox/INBOX") | return ctx.Redirect(http.StatusFound, "/mailbox/INBOX") | ||||
}) | }) | ||||
p.GET("/mailbox/:mbox", handleGetMailbox) | p.GET("/mailbox/:mbox", handleGetMailbox) | ||||
p.POST("/mailbox/:mbox", handleGetMailbox) | p.POST("/mailbox/:mbox", handleGetMailbox) | ||||
p.GET("/message/:mbox/:uid", func(ctx *koushin.Context) error { | |||||
p.GET("/message/:mbox/:uid", func(ctx *alps.Context) error { | |||||
return handleGetPart(ctx, false) | return handleGetPart(ctx, false) | ||||
}) | }) | ||||
p.GET("/message/:mbox/:uid/raw", func(ctx *koushin.Context) error { | |||||
p.GET("/message/:mbox/:uid/raw", func(ctx *alps.Context) error { | |||||
return handleGetPart(ctx, true) | return handleGetPart(ctx, true) | ||||
}) | }) | ||||
@@ -64,7 +64,7 @@ func registerRoutes(p *koushin.GoPlugin) { | |||||
} | } | ||||
type MailboxRenderData struct { | type MailboxRenderData struct { | ||||
koushin.BaseRenderData | |||||
alps.BaseRenderData | |||||
Mailbox *MailboxStatus | Mailbox *MailboxStatus | ||||
Mailboxes []MailboxInfo | Mailboxes []MailboxInfo | ||||
Messages []IMAPMessage | Messages []IMAPMessage | ||||
@@ -72,7 +72,7 @@ type MailboxRenderData struct { | |||||
Query string | Query string | ||||
} | } | ||||
func handleGetMailbox(ctx *koushin.Context) error { | |||||
func handleGetMailbox(ctx *alps.Context) error { | |||||
mboxName, err := url.PathUnescape(ctx.Param("mbox")) | mboxName, err := url.PathUnescape(ctx.Param("mbox")) | ||||
if err != nil { | if err != nil { | ||||
return echo.NewHTTPError(http.StatusBadRequest, err) | return echo.NewHTTPError(http.StatusBadRequest, err) | ||||
@@ -136,7 +136,7 @@ func handleGetMailbox(ctx *koushin.Context) error { | |||||
} | } | ||||
return ctx.Render(http.StatusOK, "mailbox.html", &MailboxRenderData{ | return ctx.Render(http.StatusOK, "mailbox.html", &MailboxRenderData{ | ||||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||||
Mailbox: mbox, | Mailbox: mbox, | ||||
Mailboxes: mailboxes, | Mailboxes: mailboxes, | ||||
Messages: msgs, | Messages: msgs, | ||||
@@ -146,13 +146,13 @@ func handleGetMailbox(ctx *koushin.Context) error { | |||||
}) | }) | ||||
} | } | ||||
func handleLogin(ctx *koushin.Context) error { | |||||
func handleLogin(ctx *alps.Context) error { | |||||
username := ctx.FormValue("username") | username := ctx.FormValue("username") | ||||
password := ctx.FormValue("password") | password := ctx.FormValue("password") | ||||
if username != "" && password != "" { | if username != "" && password != "" { | ||||
s, err := ctx.Server.Sessions.Put(username, password) | s, err := ctx.Server.Sessions.Put(username, password) | ||||
if err != nil { | if err != nil { | ||||
if _, ok := err.(koushin.AuthError); ok { | |||||
if _, ok := err.(alps.AuthError); ok { | |||||
return ctx.Render(http.StatusOK, "login.html", nil) | return ctx.Render(http.StatusOK, "login.html", nil) | ||||
} | } | ||||
return fmt.Errorf("failed to put connection in pool: %v", err) | return fmt.Errorf("failed to put connection in pool: %v", err) | ||||
@@ -165,17 +165,17 @@ func handleLogin(ctx *koushin.Context) error { | |||||
return ctx.Redirect(http.StatusFound, "/mailbox/INBOX") | return ctx.Redirect(http.StatusFound, "/mailbox/INBOX") | ||||
} | } | ||||
return ctx.Render(http.StatusOK, "login.html", koushin.NewBaseRenderData(ctx)) | |||||
return ctx.Render(http.StatusOK, "login.html", alps.NewBaseRenderData(ctx)) | |||||
} | } | ||||
func handleLogout(ctx *koushin.Context) error { | |||||
func handleLogout(ctx *alps.Context) error { | |||||
ctx.Session.Close() | ctx.Session.Close() | ||||
ctx.SetSession(nil) | ctx.SetSession(nil) | ||||
return ctx.Redirect(http.StatusFound, "/login") | return ctx.Redirect(http.StatusFound, "/login") | ||||
} | } | ||||
type MessageRenderData struct { | type MessageRenderData struct { | ||||
koushin.BaseRenderData | |||||
alps.BaseRenderData | |||||
Mailboxes []MailboxInfo | Mailboxes []MailboxInfo | ||||
Mailbox *MailboxStatus | Mailbox *MailboxStatus | ||||
Message *IMAPMessage | Message *IMAPMessage | ||||
@@ -185,7 +185,7 @@ type MessageRenderData struct { | |||||
Flags map[string]bool | Flags map[string]bool | ||||
} | } | ||||
func handleGetPart(ctx *koushin.Context, raw bool) error { | |||||
func handleGetPart(ctx *alps.Context, raw bool) error { | |||||
mboxName, uid, err := parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid")) | mboxName, uid, err := parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid")) | ||||
if err != nil { | if err != nil { | ||||
return echo.NewHTTPError(http.StatusBadRequest, err) | return echo.NewHTTPError(http.StatusBadRequest, err) | ||||
@@ -271,7 +271,7 @@ func handleGetPart(ctx *koushin.Context, raw bool) error { | |||||
} | } | ||||
return ctx.Render(http.StatusOK, "message.html", &MessageRenderData{ | return ctx.Render(http.StatusOK, "message.html", &MessageRenderData{ | ||||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||||
Mailboxes: mailboxes, | Mailboxes: mailboxes, | ||||
Mailbox: mbox, | Mailbox: mbox, | ||||
Message: msg, | Message: msg, | ||||
@@ -283,7 +283,7 @@ func handleGetPart(ctx *koushin.Context, raw bool) error { | |||||
} | } | ||||
type ComposeRenderData struct { | type ComposeRenderData struct { | ||||
koushin.BaseRenderData | |||||
alps.BaseRenderData | |||||
Message *OutgoingMessage | Message *OutgoingMessage | ||||
} | } | ||||
@@ -300,12 +300,12 @@ type composeOptions struct { | |||||
// Send message, append it to the Sent mailbox, mark the original message as | // Send message, append it to the Sent mailbox, mark the original message as | ||||
// answered | // answered | ||||
func submitCompose(ctx *koushin.Context, msg *OutgoingMessage, options *composeOptions) error { | |||||
func submitCompose(ctx *alps.Context, msg *OutgoingMessage, options *composeOptions) error { | |||||
err := ctx.Session.DoSMTP(func(c *smtp.Client) error { | err := ctx.Session.DoSMTP(func(c *smtp.Client) error { | ||||
return sendMessage(c, msg) | return sendMessage(c, msg) | ||||
}) | }) | ||||
if err != nil { | if err != nil { | ||||
if _, ok := err.(koushin.AuthError); ok { | |||||
if _, ok := err.(alps.AuthError); ok { | |||||
return echo.NewHTTPError(http.StatusForbidden, err) | return echo.NewHTTPError(http.StatusForbidden, err) | ||||
} | } | ||||
return fmt.Errorf("failed to send message: %v", err) | return fmt.Errorf("failed to send message: %v", err) | ||||
@@ -338,7 +338,7 @@ func submitCompose(ctx *koushin.Context, msg *OutgoingMessage, options *composeO | |||||
return ctx.Redirect(http.StatusFound, "/mailbox/INBOX") | return ctx.Redirect(http.StatusFound, "/mailbox/INBOX") | ||||
} | } | ||||
func handleCompose(ctx *koushin.Context, msg *OutgoingMessage, options *composeOptions) error { | |||||
func handleCompose(ctx *alps.Context, msg *OutgoingMessage, options *composeOptions) error { | |||||
if msg.From == "" && strings.ContainsRune(ctx.Session.Username(), '@') { | if msg.From == "" && strings.ContainsRune(ctx.Session.Username(), '@') { | ||||
msg.From = ctx.Session.Username() | msg.From = ctx.Session.Username() | ||||
} | } | ||||
@@ -438,12 +438,12 @@ func handleCompose(ctx *koushin.Context, msg *OutgoingMessage, options *composeO | |||||
} | } | ||||
return ctx.Render(http.StatusOK, "compose.html", &ComposeRenderData{ | return ctx.Render(http.StatusOK, "compose.html", &ComposeRenderData{ | ||||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||||
Message: msg, | Message: msg, | ||||
}) | }) | ||||
} | } | ||||
func handleComposeNew(ctx *koushin.Context) error { | |||||
func handleComposeNew(ctx *alps.Context) error { | |||||
// These are common mailto URL query parameters | // These are common mailto URL query parameters | ||||
// TODO: cc, bcc | // TODO: cc, bcc | ||||
return handleCompose(ctx, &OutgoingMessage{ | return handleCompose(ctx, &OutgoingMessage{ | ||||
@@ -462,7 +462,7 @@ func unwrapIMAPAddressList(addrs []*imap.Address) []string { | |||||
return l | return l | ||||
} | } | ||||
func handleReply(ctx *koushin.Context) error { | |||||
func handleReply(ctx *alps.Context) error { | |||||
var inReplyToPath messagePath | var inReplyToPath messagePath | ||||
var err error | var err error | ||||
inReplyToPath.Mailbox, inReplyToPath.Uid, err = parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid")) | inReplyToPath.Mailbox, inReplyToPath.Uid, err = parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid")) | ||||
@@ -521,7 +521,7 @@ func handleReply(ctx *koushin.Context) error { | |||||
return handleCompose(ctx, &msg, &composeOptions{InReplyTo: &inReplyToPath}) | return handleCompose(ctx, &msg, &composeOptions{InReplyTo: &inReplyToPath}) | ||||
} | } | ||||
func handleForward(ctx *koushin.Context) error { | |||||
func handleForward(ctx *alps.Context) error { | |||||
var sourcePath messagePath | var sourcePath messagePath | ||||
var err error | var err error | ||||
sourcePath.Mailbox, sourcePath.Uid, err = parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid")) | sourcePath.Mailbox, sourcePath.Uid, err = parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid")) | ||||
@@ -585,7 +585,7 @@ func handleForward(ctx *koushin.Context) error { | |||||
return handleCompose(ctx, &msg, &composeOptions{Forward: &sourcePath}) | return handleCompose(ctx, &msg, &composeOptions{Forward: &sourcePath}) | ||||
} | } | ||||
func handleEdit(ctx *koushin.Context) error { | |||||
func handleEdit(ctx *alps.Context) error { | |||||
var sourcePath messagePath | var sourcePath messagePath | ||||
var err error | var err error | ||||
sourcePath.Mailbox, sourcePath.Uid, err = parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid")) | sourcePath.Mailbox, sourcePath.Uid, err = parseMboxAndUid(ctx.Param("mbox"), ctx.Param("uid")) | ||||
@@ -653,14 +653,14 @@ func handleEdit(ctx *koushin.Context) error { | |||||
return handleCompose(ctx, &msg, &composeOptions{Draft: &sourcePath}) | return handleCompose(ctx, &msg, &composeOptions{Draft: &sourcePath}) | ||||
} | } | ||||
func formOrQueryParam(ctx *koushin.Context, k string) string { | |||||
func formOrQueryParam(ctx *alps.Context, k string) string { | |||||
if v := ctx.FormValue(k); v != "" { | if v := ctx.FormValue(k); v != "" { | ||||
return v | return v | ||||
} | } | ||||
return ctx.QueryParam(k) | return ctx.QueryParam(k) | ||||
} | } | ||||
func handleMove(ctx *koushin.Context) error { | |||||
func handleMove(ctx *alps.Context) error { | |||||
mboxName, err := url.PathUnescape(ctx.Param("mbox")) | mboxName, err := url.PathUnescape(ctx.Param("mbox")) | ||||
if err != nil { | if err != nil { | ||||
return echo.NewHTTPError(http.StatusBadRequest, err) | return echo.NewHTTPError(http.StatusBadRequest, err) | ||||
@@ -706,7 +706,7 @@ func handleMove(ctx *koushin.Context) error { | |||||
return ctx.Redirect(http.StatusFound, fmt.Sprintf("/mailbox/%v", url.PathEscape(to))) | return ctx.Redirect(http.StatusFound, fmt.Sprintf("/mailbox/%v", url.PathEscape(to))) | ||||
} | } | ||||
func handleDelete(ctx *koushin.Context) error { | |||||
func handleDelete(ctx *alps.Context) error { | |||||
mboxName, err := url.PathUnescape(ctx.Param("mbox")) | mboxName, err := url.PathUnescape(ctx.Param("mbox")) | ||||
if err != nil { | if err != nil { | ||||
return echo.NewHTTPError(http.StatusBadRequest, err) | return echo.NewHTTPError(http.StatusBadRequest, err) | ||||
@@ -757,7 +757,7 @@ func handleDelete(ctx *koushin.Context) error { | |||||
return ctx.Redirect(http.StatusFound, fmt.Sprintf("/mailbox/%v", url.PathEscape(mboxName))) | return ctx.Redirect(http.StatusFound, fmt.Sprintf("/mailbox/%v", url.PathEscape(mboxName))) | ||||
} | } | ||||
func handleSetFlags(ctx *koushin.Context) error { | |||||
func handleSetFlags(ctx *alps.Context) error { | |||||
mboxName, err := url.PathUnescape(ctx.Param("mbox")) | mboxName, err := url.PathUnescape(ctx.Param("mbox")) | ||||
if err != nil { | if err != nil { | ||||
return echo.NewHTTPError(http.StatusBadRequest, err) | return echo.NewHTTPError(http.StatusBadRequest, err) | ||||
@@ -840,11 +840,11 @@ type Settings struct { | |||||
MessagesPerPage int | MessagesPerPage int | ||||
} | } | ||||
func loadSettings(s koushin.Store) (*Settings, error) { | |||||
func loadSettings(s alps.Store) (*Settings, error) { | |||||
settings := &Settings{ | settings := &Settings{ | ||||
MessagesPerPage: 50, | MessagesPerPage: 50, | ||||
} | } | ||||
if err := s.Get(settingsKey, settings); err != nil && err != koushin.ErrNoStoreEntry { | |||||
if err := s.Get(settingsKey, settings); err != nil && err != alps.ErrNoStoreEntry { | |||||
return nil, err | return nil, err | ||||
} | } | ||||
if err := settings.check(); err != nil { | if err := settings.check(); err != nil { | ||||
@@ -861,11 +861,11 @@ func (s *Settings) check() error { | |||||
} | } | ||||
type SettingsRenderData struct { | type SettingsRenderData struct { | ||||
koushin.BaseRenderData | |||||
alps.BaseRenderData | |||||
Settings *Settings | Settings *Settings | ||||
} | } | ||||
func handleSettings(ctx *koushin.Context) error { | |||||
func handleSettings(ctx *alps.Context) error { | |||||
settings, err := loadSettings(ctx.Session.Store()) | settings, err := loadSettings(ctx.Session.Store()) | ||||
if err != nil { | if err != nil { | ||||
return fmt.Errorf("failed to load settings: %v", err) | return fmt.Errorf("failed to load settings: %v", err) | ||||
@@ -888,7 +888,7 @@ func handleSettings(ctx *koushin.Context) error { | |||||
} | } | ||||
return ctx.Render(http.StatusOK, "settings.html", &SettingsRenderData{ | return ctx.Render(http.StatusOK, "settings.html", &SettingsRenderData{ | ||||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||||
Settings: settings, | Settings: settings, | ||||
}) | }) | ||||
} | } |
@@ -1,4 +1,4 @@ | |||||
package koushinbase | |||||
package alpsbase | |||||
import ( | import ( | ||||
"bufio" | "bufio" | ||||
@@ -1,4 +1,4 @@ | |||||
package koushinbase | |||||
package alpsbase | |||||
import ( | import ( | ||||
"fmt" | "fmt" | ||||
@@ -1,4 +1,4 @@ | |||||
package koushinbase | |||||
package alpsbase | |||||
import ( | import ( | ||||
"html/template" | "html/template" | ||||
@@ -1,9 +1,9 @@ | |||||
package koushinbase | |||||
package alpsbase | |||||
import ( | import ( | ||||
"fmt" | "fmt" | ||||
"git.sr.ht/~emersion/koushin" | |||||
"git.sr.ht/~emersion/alps" | |||||
"github.com/emersion/go-message" | "github.com/emersion/go-message" | ||||
) | ) | ||||
@@ -16,7 +16,7 @@ type Viewer interface { | |||||
// ViewMessagePart renders a message part. The returned value is displayed | // ViewMessagePart renders a message part. The returned value is displayed | ||||
// in a template. ErrViewUnsupported is returned if the message part isn't | // in a template. ErrViewUnsupported is returned if the message part isn't | ||||
// supported. | // supported. | ||||
ViewMessagePart(*koushin.Context, *IMAPMessage, *message.Entity) (interface{}, error) | |||||
ViewMessagePart(*alps.Context, *IMAPMessage, *message.Entity) (interface{}, error) | |||||
} | } | ||||
var viewers []Viewer | var viewers []Viewer | ||||
@@ -26,7 +26,7 @@ func RegisterViewer(viewer Viewer) { | |||||
viewers = append(viewers, viewer) | viewers = append(viewers, viewer) | ||||
} | } | ||||
func viewMessagePart(ctx *koushin.Context, msg *IMAPMessage, part *message.Entity) (interface{}, error) { | |||||
func viewMessagePart(ctx *alps.Context, msg *IMAPMessage, part *message.Entity) (interface{}, error) { | |||||
for _, viewer := range viewers { | for _, viewer := range viewers { | ||||
v, err := viewer.ViewMessagePart(ctx, msg, part) | v, err := viewer.ViewMessagePart(ctx, msg, part) | ||||
if err == ErrViewUnsupported { | if err == ErrViewUnsupported { | ||||
@@ -1,11 +1,11 @@ | |||||
package koushincaldav | |||||
package alpscaldav | |||||
import ( | import ( | ||||
"fmt" | "fmt" | ||||
"net/http" | "net/http" | ||||
"net/url" | "net/url" | ||||
"git.sr.ht/~emersion/koushin" | |||||
"git.sr.ht/~emersion/alps" | |||||
"github.com/emersion/go-webdav/caldav" | "github.com/emersion/go-webdav/caldav" | ||||
) | ) | ||||
@@ -13,7 +13,7 @@ var errNoCalendar = fmt.Errorf("caldav: no calendar found") | |||||
type authRoundTripper struct { | type authRoundTripper struct { | ||||
upstream http.RoundTripper | upstream http.RoundTripper | ||||
session *koushin.Session | |||||
session *alps.Session | |||||
} | } | ||||
func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { | func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { | ||||
@@ -21,7 +21,7 @@ func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) | |||||
return rt.upstream.RoundTrip(req) | return rt.upstream.RoundTrip(req) | ||||
} | } | ||||
func newClient(u *url.URL, session *koushin.Session) (*caldav.Client, error) { | |||||
func newClient(u *url.URL, session *alps.Session) (*caldav.Client, error) { | |||||
rt := authRoundTripper{ | rt := authRoundTripper{ | ||||
upstream: http.DefaultTransport, | upstream: http.DefaultTransport, | ||||
session: session, | session: session, | ||||
@@ -34,7 +34,7 @@ func newClient(u *url.URL, session *koushin.Session) (*caldav.Client, error) { | |||||
return c, nil | return c, nil | ||||
} | } | ||||
func getCalendar(u *url.URL, session *koushin.Session) (*caldav.Client, *caldav.Calendar, error) { | |||||
func getCalendar(u *url.URL, session *alps.Session) (*caldav.Client, *caldav.Calendar, error) { | |||||
c, err := newClient(u, session) | c, err := newClient(u, session) | ||||
if err != nil { | if err != nil { | ||||
return nil, nil, err | return nil, nil, err | ||||
@@ -1,11 +1,11 @@ | |||||
package koushincaldav | |||||
package alpscaldav | |||||
import ( | import ( | ||||
"fmt" | "fmt" | ||||
"net/http" | "net/http" | ||||
"net/url" | "net/url" | ||||
"git.sr.ht/~emersion/koushin" | |||||
"git.sr.ht/~emersion/alps" | |||||
) | ) | ||||
func sanityCheckURL(u *url.URL) error { | func sanityCheckURL(u *url.URL) error { | ||||
@@ -27,9 +27,9 @@ func sanityCheckURL(u *url.URL) error { | |||||
return nil | return nil | ||||
} | } | ||||
func newPlugin(srv *koushin.Server) (koushin.Plugin, error) { | |||||
func newPlugin(srv *alps.Server) (alps.Plugin, error) { | |||||
u, err := srv.Upstream("caldavs", "caldav+insecure", "https", "http+insecure") | u, err := srv.Upstream("caldavs", "caldav+insecure", "https", "http+insecure") | ||||
if _, ok := err.(*koushin.NoUpstreamError); ok { | |||||
if _, ok := err.(*alps.NoUpstreamError); ok { | |||||
return nil, nil | return nil, nil | ||||
} else if err != nil { | } else if err != nil { | ||||
return nil, fmt.Errorf("caldav: failed to parse upstream caldav server: %v", err) | return nil, fmt.Errorf("caldav: failed to parse upstream caldav server: %v", err) | ||||
@@ -53,7 +53,7 @@ func newPlugin(srv *koushin.Server) (koushin.Plugin, error) { | |||||
srv.Logger().Printf("Configured upstream CalDAV server: %v", u) | srv.Logger().Printf("Configured upstream CalDAV server: %v", u) | ||||
p := koushin.GoPlugin{Name: "caldav"} | |||||
p := alps.GoPlugin{Name: "caldav"} | |||||
registerRoutes(&p, u) | registerRoutes(&p, u) | ||||
@@ -61,7 +61,7 @@ func newPlugin(srv *koushin.Server) (koushin.Plugin, error) { | |||||
} | } | ||||
func init() { | func init() { | ||||
koushin.RegisterPluginLoader(func(s *koushin.Server) ([]koushin.Plugin, error) { | |||||
alps.RegisterPluginLoader(func(s *alps.Server) ([]alps.Plugin, error) { | |||||
p, err := newPlugin(s) | p, err := newPlugin(s) | ||||
if err != nil { | if err != nil { | ||||
return nil, err | return nil, err | ||||
@@ -69,6 +69,6 @@ func init() { | |||||
if p == nil { | if p == nil { | ||||
return nil, nil | return nil, nil | ||||
} | } | ||||
return []koushin.Plugin{p}, err | |||||
return []alps.Plugin{p}, err | |||||
}) | }) | ||||
} | } |
@@ -1,6 +1,6 @@ | |||||
{{template "head.html"}} | {{template "head.html"}} | ||||
<h1>koushin</h1> | |||||
<h1>alps</h1> | |||||
<p> | <p> | ||||
<a href="/">Back</a> | <a href="/">Back</a> | ||||
@@ -1,6 +1,6 @@ | |||||
{{template "head.html"}} | {{template "head.html"}} | ||||
<h1>koushin</h1> | |||||
<h1>alps</h1> | |||||
<p> | <p> | ||||
<a href="/calendar">Back</a> | <a href="/calendar">Back</a> | ||||
@@ -1,4 +1,4 @@ | |||||
package koushincaldav | |||||
package alpscaldav | |||||
import ( | import ( | ||||
"fmt" | "fmt" | ||||
@@ -6,12 +6,12 @@ import ( | |||||
"net/url" | "net/url" | ||||
"time" | "time" | ||||
"git.sr.ht/~emersion/koushin" | |||||
"git.sr.ht/~emersion/alps" | |||||
"github.com/emersion/go-webdav/caldav" | "github.com/emersion/go-webdav/caldav" | ||||
) | ) | ||||
type CalendarRenderData struct { | type CalendarRenderData struct { | ||||
koushin.BaseRenderData | |||||
alps.BaseRenderData | |||||
Time time.Time | Time time.Time | ||||
Calendar *caldav.Calendar | Calendar *caldav.Calendar | ||||
Events []caldav.CalendarObject | Events []caldav.CalendarObject | ||||
@@ -19,15 +19,15 @@ type CalendarRenderData struct { | |||||
} | } | ||||
type EventRenderData struct { | type EventRenderData struct { | ||||
koushin.BaseRenderData | |||||
alps.BaseRenderData | |||||
Calendar *caldav.Calendar | Calendar *caldav.Calendar | ||||
Event *caldav.CalendarObject | Event *caldav.CalendarObject | ||||
} | } | ||||
var monthPageLayout = "2006-01" | var monthPageLayout = "2006-01" | ||||
func registerRoutes(p *koushin.GoPlugin, u *url.URL) { | |||||
p.GET("/calendar", func(ctx *koushin.Context) error { | |||||
func registerRoutes(p *alps.GoPlugin, u *url.URL) { | |||||
p.GET("/calendar", func(ctx *alps.Context) error { | |||||
var start time.Time | var start time.Time | ||||
if s := ctx.QueryParam("month"); s != "" { | if s := ctx.QueryParam("month"); s != "" { | ||||
var err error | var err error | ||||
@@ -77,7 +77,7 @@ func registerRoutes(p *koushin.GoPlugin, u *url.URL) { | |||||
} | } | ||||
return ctx.Render(http.StatusOK, "calendar.html", &CalendarRenderData{ | return ctx.Render(http.StatusOK, "calendar.html", &CalendarRenderData{ | ||||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||||
Time: start, | Time: start, | ||||
Calendar: calendar, | Calendar: calendar, | ||||
Events: events, | Events: events, | ||||
@@ -86,7 +86,7 @@ func registerRoutes(p *koushin.GoPlugin, u *url.URL) { | |||||
}) | }) | ||||
}) | }) | ||||
p.GET("/calendar/:uid", func(ctx *koushin.Context) error { | |||||
p.GET("/calendar/:uid", func(ctx *alps.Context) error { | |||||
uid := ctx.Param("uid") | uid := ctx.Param("uid") | ||||
c, calendar, err := getCalendar(u, ctx.Session) | c, calendar, err := getCalendar(u, ctx.Session) | ||||
@@ -131,7 +131,7 @@ func registerRoutes(p *koushin.GoPlugin, u *url.URL) { | |||||
event := &events[0] | event := &events[0] | ||||
return ctx.Render(http.StatusOK, "event.html", &EventRenderData{ | return ctx.Render(http.StatusOK, "event.html", &EventRenderData{ | ||||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||||
Calendar: calendar, | Calendar: calendar, | ||||
Event: event, | Event: event, | ||||
}) | }) | ||||
@@ -1,11 +1,11 @@ | |||||
package koushincarddav | |||||
package alpscarddav | |||||
import ( | import ( | ||||
"fmt" | "fmt" | ||||
"net/http" | "net/http" | ||||
"net/url" | "net/url" | ||||
"git.sr.ht/~emersion/koushin" | |||||
"git.sr.ht/~emersion/alps" | |||||
"github.com/emersion/go-webdav/carddav" | "github.com/emersion/go-webdav/carddav" | ||||
) | ) | ||||
@@ -13,7 +13,7 @@ var errNoAddressBook = fmt.Errorf("carddav: no address book found") | |||||
type authRoundTripper struct { | type authRoundTripper struct { | ||||
upstream http.RoundTripper | upstream http.RoundTripper | ||||
session *koushin.Session | |||||
session *alps.Session | |||||
} | } | ||||
func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { | func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { | ||||
@@ -21,7 +21,7 @@ func (rt *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) | |||||
return rt.upstream.RoundTrip(req) | return rt.upstream.RoundTrip(req) | ||||
} | } | ||||
func newClient(u *url.URL, session *koushin.Session) (*carddav.Client, error) { | |||||
func newClient(u *url.URL, session *alps.Session) (*carddav.Client, error) { | |||||
rt := authRoundTripper{ | rt := authRoundTripper{ | ||||
upstream: http.DefaultTransport, | upstream: http.DefaultTransport, | ||||
session: session, | session: session, | ||||
@@ -1,4 +1,4 @@ | |||||
package koushincarddav | |||||
package alpscarddav | |||||
import ( | import ( | ||||
"fmt" | "fmt" | ||||
@@ -6,8 +6,8 @@ import ( | |||||
"net/url" | "net/url" | ||||
"strings" | "strings" | ||||
"git.sr.ht/~emersion/koushin" | |||||
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" | |||||
"git.sr.ht/~emersion/alps" | |||||
alpsbase "git.sr.ht/~emersion/alps/plugins/base" | |||||
"github.com/emersion/go-vcard" | "github.com/emersion/go-vcard" | ||||
"github.com/emersion/go-webdav/carddav" | "github.com/emersion/go-webdav/carddav" | ||||
) | ) | ||||
@@ -32,16 +32,16 @@ func sanityCheckURL(u *url.URL) error { | |||||
} | } | ||||
type plugin struct { | type plugin struct { | ||||
koushin.GoPlugin | |||||
alps.GoPlugin | |||||
url *url.URL | url *url.URL | ||||
homeSetCache map[string]string | homeSetCache map[string]string | ||||
} | } | ||||
func (p *plugin) client(session *koushin.Session) (*carddav.Client, error) { | |||||
func (p *plugin) client(session *alps.Session) (*carddav.Client, error) { | |||||
return newClient(p.url, session) | return newClient(p.url, session) | ||||
} | } | ||||
func (p *plugin) clientWithAddressBook(session *koushin.Session) (*carddav.Client, *carddav.AddressBook, error) { | |||||
func (p *plugin) clientWithAddressBook(session *alps.Session) (*carddav.Client, *carddav.AddressBook, error) { | |||||
c, err := newClient(p.url, session) | c, err := newClient(p.url, session) | ||||
if err != nil { | if err != nil { | ||||
return nil, nil, fmt.Errorf("failed to create CardDAV client: %v", err) | return nil, nil, fmt.Errorf("failed to create CardDAV client: %v", err) | ||||
@@ -73,9 +73,9 @@ func (p *plugin) clientWithAddressBook(session *koushin.Session) (*carddav.Clien | |||||
return c, &addressBooks[0], nil | return c, &addressBooks[0], nil | ||||
} | } | ||||
func newPlugin(srv *koushin.Server) (koushin.Plugin, error) { | |||||
func newPlugin(srv *alps.Server) (alps.Plugin, error) { | |||||
u, err := srv.Upstream("carddavs", "carddav+insecure", "https", "http+insecure") | u, err := srv.Upstream("carddavs", "carddav+insecure", "https", "http+insecure") | ||||
if _, ok := err.(*koushin.NoUpstreamError); ok { | |||||
if _, ok := err.(*alps.NoUpstreamError); ok { | |||||
return nil, nil | return nil, nil | ||||
} else if err != nil { | } else if err != nil { | ||||
return nil, fmt.Errorf("carddav: failed to parse upstream CardDAV server: %v", err) | return nil, fmt.Errorf("carddav: failed to parse upstream CardDAV server: %v", err) | ||||
@@ -105,7 +105,7 @@ func newPlugin(srv *koushin.Server) (koushin.Plugin, error) { | |||||
srv.Logger().Printf("Configured upstream CardDAV server: %v", u) | srv.Logger().Printf("Configured upstream CardDAV server: %v", u) | ||||
p := &plugin{ | p := &plugin{ | ||||
GoPlugin: koushin.GoPlugin{Name: "carddav"}, | |||||
GoPlugin: alps.GoPlugin{Name: "carddav"}, | |||||
url: u, | url: u, | ||||
homeSetCache: make(map[string]string), | homeSetCache: make(map[string]string), | ||||
} | } | ||||
@@ -118,8 +118,8 @@ func newPlugin(srv *koushin.Server) (koushin.Plugin, error) { | |||||
}, | }, | ||||
}) | }) | ||||
p.Inject("compose.html", func(ctx *koushin.Context, _data koushin.RenderData) error { | |||||
data := _data.(*koushinbase.ComposeRenderData) | |||||
p.Inject("compose.html", func(ctx *alps.Context, _data alps.RenderData) error { | |||||
data := _data.(*alpsbase.ComposeRenderData) | |||||
c, addressBook, err := p.clientWithAddressBook(ctx.Session) | c, addressBook, err := p.clientWithAddressBook(ctx.Session) | ||||
if err == errNoAddressBook { | if err == errNoAddressBook { | ||||
@@ -156,7 +156,7 @@ func newPlugin(srv *koushin.Server) (koushin.Plugin, error) { | |||||
} | } | ||||
func init() { | func init() { | ||||
koushin.RegisterPluginLoader(func(s *koushin.Server) ([]koushin.Plugin, error) { | |||||
alps.RegisterPluginLoader(func(s *alps.Server) ([]alps.Plugin, error) { | |||||
p, err := newPlugin(s) | p, err := newPlugin(s) | ||||
if err != nil { | if err != nil { | ||||
return nil, err | return nil, err | ||||
@@ -164,6 +164,6 @@ func init() { | |||||
if p == nil { | if p == nil { | ||||
return nil, nil | return nil, nil | ||||
} | } | ||||
return []koushin.Plugin{p}, err | |||||
return []alps.Plugin{p}, err | |||||
}) | }) | ||||
} | } |
@@ -1,6 +1,6 @@ | |||||
{{template "head.html"}} | {{template "head.html"}} | ||||
<h1>koushin</h1> | |||||
<h1>alps</h1> | |||||
<p> | <p> | ||||
<a href="/">Back</a> · <a href="/contacts/create">Create new contact</a> | <a href="/">Back</a> · <a href="/contacts/create">Create new contact</a> | ||||
@@ -1,6 +1,6 @@ | |||||
{{template "head.html"}} | {{template "head.html"}} | ||||
<h1>koushin</h1> | |||||
<h1>alps</h1> | |||||
<p> | <p> | ||||
<a href="/contacts">Back</a> | <a href="/contacts">Back</a> | ||||
@@ -1,6 +1,6 @@ | |||||
{{template "head.html"}} | {{template "head.html"}} | ||||
<h1>koushin</h1> | |||||
<h1>alps</h1> | |||||
<p> | <p> | ||||
<a href="/contacts">Back</a> | <a href="/contacts">Back</a> | ||||
@@ -1,4 +1,4 @@ | |||||
package koushincarddav | |||||
package alpscarddav | |||||
import ( | import ( | ||||
"fmt" | "fmt" | ||||
@@ -7,7 +7,7 @@ import ( | |||||
"path" | "path" | ||||
"strings" | "strings" | ||||
"git.sr.ht/~emersion/koushin" | |||||
"git.sr.ht/~emersion/alps" | |||||
"github.com/emersion/go-vcard" | "github.com/emersion/go-vcard" | ||||
"github.com/emersion/go-webdav/carddav" | "github.com/emersion/go-webdav/carddav" | ||||
"github.com/google/uuid" | "github.com/google/uuid" | ||||
@@ -15,19 +15,19 @@ import ( | |||||
) | ) | ||||
type AddressBookRenderData struct { | type AddressBookRenderData struct { | ||||
koushin.BaseRenderData | |||||
alps.BaseRenderData | |||||
AddressBook *carddav.AddressBook | AddressBook *carddav.AddressBook | ||||
AddressObjects []AddressObject | AddressObjects []AddressObject | ||||
Query string | Query string | ||||
} | } | ||||
type AddressObjectRenderData struct { | type AddressObjectRenderData struct { | ||||
koushin.BaseRenderData | |||||
alps.BaseRenderData | |||||
AddressObject AddressObject | AddressObject AddressObject | ||||
} | } | ||||
type UpdateAddressObjectRenderData struct { | type UpdateAddressObjectRenderData struct { | ||||
koushin.BaseRenderData | |||||
alps.BaseRenderData | |||||
AddressObject *carddav.AddressObject // nil if creating a new contact | AddressObject *carddav.AddressObject // nil if creating a new contact | ||||
Card vcard.Card | Card vcard.Card | ||||
} | } | ||||
@@ -42,7 +42,7 @@ func parseObjectPath(s string) (string, error) { | |||||
} | } | ||||
func registerRoutes(p *plugin) { | func registerRoutes(p *plugin) { | ||||
p.GET("/contacts", func(ctx *koushin.Context) error { | |||||
p.GET("/contacts", func(ctx *alps.Context) error { | |||||
queryText := ctx.QueryParam("query") | queryText := ctx.QueryParam("query") | ||||
c, addressBook, err := p.clientWithAddressBook(ctx.Session) | c, addressBook, err := p.clientWithAddressBook(ctx.Session) | ||||
@@ -82,14 +82,14 @@ func registerRoutes(p *plugin) { | |||||
} | } | ||||
return ctx.Render(http.StatusOK, "address-book.html", &AddressBookRenderData{ | return ctx.Render(http.StatusOK, "address-book.html", &AddressBookRenderData{ | ||||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||||
AddressBook: addressBook, | AddressBook: addressBook, | ||||
AddressObjects: newAddressObjectList(aos), | AddressObjects: newAddressObjectList(aos), | ||||
Query: queryText, | Query: queryText, | ||||
}) | }) | ||||
}) | }) | ||||
p.GET("/contacts/:path", func(ctx *koushin.Context) error { | |||||
p.GET("/contacts/:path", func(ctx *alps.Context) error { | |||||
path, err := parseObjectPath(ctx.Param("path")) | path, err := parseObjectPath(ctx.Param("path")) | ||||
if err != nil { | if err != nil { | ||||
return err | return err | ||||
@@ -119,12 +119,12 @@ func registerRoutes(p *plugin) { | |||||
ao := &aos[0] | ao := &aos[0] | ||||
return ctx.Render(http.StatusOK, "address-object.html", &AddressObjectRenderData{ | return ctx.Render(http.StatusOK, "address-object.html", &AddressObjectRenderData{ | ||||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||||
AddressObject: AddressObject{ao}, | AddressObject: AddressObject{ao}, | ||||
}) | }) | ||||
}) | }) | ||||
updateContact := func(ctx *koushin.Context) error { | |||||
updateContact := func(ctx *alps.Context) error { | |||||
addressObjectPath, err := parseObjectPath(ctx.Param("path")) | addressObjectPath, err := parseObjectPath(ctx.Param("path")) | ||||
if err != nil { | if err != nil { | ||||
return err | return err | ||||
@@ -200,7 +200,7 @@ func registerRoutes(p *plugin) { | |||||
} | } | ||||
return ctx.Render(http.StatusOK, "update-address-object.html", &UpdateAddressObjectRenderData{ | return ctx.Render(http.StatusOK, "update-address-object.html", &UpdateAddressObjectRenderData{ | ||||
BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||||
BaseRenderData: *alps.NewBaseRenderData(ctx), | |||||
AddressObject: ao, | AddressObject: ao, | ||||
Card: card, | Card: card, | ||||
}) | }) | ||||
@@ -1,11 +1,11 @@ | |||||
package koushinlua | |||||
package alpslua | |||||
import ( | import ( | ||||
"fmt" | "fmt" | ||||
"html/template" | "html/template" | ||||
"path/filepath" | "path/filepath" | ||||
"git.sr.ht/~emersion/koushin" | |||||
"git.sr.ht/~emersion/alps" | |||||
"github.com/labstack/echo/v4" | "github.com/labstack/echo/v4" | ||||
"github.com/yuin/gopher-lua" | "github.com/yuin/gopher-lua" | ||||
"layeh.com/gopher-luar" | "layeh.com/gopher-luar" | ||||
@@ -69,7 +69,7 @@ func (p *luaPlugin) setRoute(l *lua.LState) int { | |||||
return 0 | return 0 | ||||
} | } | ||||
func (p *luaPlugin) inject(name string, data koushin.RenderData) error { | |||||
func (p *luaPlugin) inject(name string, data alps.RenderData) error { | |||||
f, ok := p.renderCallbacks[name] | f, ok := p.renderCallbacks[name] | ||||
if !ok { | if !ok { | ||||
return nil | return nil | ||||
@@ -87,7 +87,7 @@ func (p *luaPlugin) inject(name string, data koushin.RenderData) error { | |||||
return nil | return nil | ||||
} | } | ||||
func (p *luaPlugin) Inject(ctx *koushin.Context, name string, data koushin.RenderData) error { | |||||
func (p *luaPlugin) Inject(ctx *alps.Context, name string, data alps.RenderData) error { | |||||
if err := p.inject("*", data); err != nil { | if err := p.inject("*", data); err != nil { | ||||
return err | return err | ||||
} | } | ||||
@@ -144,8 +144,8 @@ func loadLuaPlugin(filename string) (*luaPlugin, error) { | |||||
filters: make(template.FuncMap), | filters: make(template.FuncMap), | ||||
} | } | ||||
mt := l.NewTypeMetatable("koushin") | |||||
l.SetGlobal("koushin", mt) | |||||
mt := l.NewTypeMetatable("alps") | |||||
l.SetGlobal("alps", mt) | |||||
l.SetField(mt, "on_render", l.NewFunction(p.onRender)) | l.SetField(mt, "on_render", l.NewFunction(p.onRender)) | ||||
l.SetField(mt, "set_filter", l.NewFunction(p.setFilter)) | l.SetField(mt, "set_filter", l.NewFunction(p.setFilter)) | ||||
l.SetField(mt, "set_route", l.NewFunction(p.setRoute)) | l.SetField(mt, "set_route", l.NewFunction(p.setRoute)) | ||||
@@ -158,15 +158,15 @@ func loadLuaPlugin(filename string) (*luaPlugin, error) { | |||||
return p, nil | return p, nil | ||||
} | } | ||||
func loadAllLuaPlugins(s *koushin.Server) ([]koushin.Plugin, error) { | |||||
func loadAllLuaPlugins(s *alps.Server) ([]alps.Plugin, error) { | |||||
log := s.Logger() | log := s.Logger() | ||||
filenames, err := filepath.Glob(koushin.PluginDir + "/*/main.lua") | |||||
filenames, err := filepath.Glob(alps.PluginDir + "/*/main.lua") | |||||
if err != nil { | if err != nil { | ||||
return nil, fmt.Errorf("filepath.Glob failed: %v", err) | return nil, fmt.Errorf("filepath.Glob failed: %v", err) | ||||
} | } | ||||
plugins := make([]koushin.Plugin, 0, len(filenames)) | |||||
plugins := make([]alps.Plugin, 0, len(filenames)) | |||||
for _, filename := range filenames { | for _, filename := range filenames { | ||||
log.Printf("Loading Lua plugin %q", filename) | log.Printf("Loading Lua plugin %q", filename) | ||||
@@ -1,9 +1,9 @@ | |||||
package koushinlua | |||||
package alpslua | |||||
import ( | import ( | ||||
"git.sr.ht/~emersion/koushin" | |||||
"git.sr.ht/~emersion/alps" | |||||
) | ) | ||||
func init() { | func init() { | ||||
koushin.RegisterPluginLoader(loadAllLuaPlugins) | |||||
alps.RegisterPluginLoader(loadAllLuaPlugins) | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
package koushinviewhtml | |||||
package alpsviewhtml | |||||
import ( | import ( | ||||
"io" | "io" | ||||
@@ -8,8 +8,8 @@ import ( | |||||
"strconv" | "strconv" | ||||
"strings" | "strings" | ||||
"git.sr.ht/~emersion/koushin" | |||||
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" | |||||
"git.sr.ht/~emersion/alps" | |||||
alpsbase "git.sr.ht/~emersion/alps/plugins/base" | |||||
"github.com/labstack/echo/v4" | "github.com/labstack/echo/v4" | ||||
) | ) | ||||
@@ -19,10 +19,10 @@ var ( | |||||
) | ) | ||||
func init() { | func init() { | ||||
p := koushin.GoPlugin{Name: "viewhtml"} | |||||
p := alps.GoPlugin{Name: "viewhtml"} | |||||
p.Inject("message.html", func(ctx *koushin.Context, _data koushin.RenderData) error { | |||||
data := _data.(*koushinbase.MessageRenderData) | |||||
p.Inject("message.html", func(ctx *alps.Context, _data alps.RenderData) error { | |||||
data := _data.(*alpsbase.MessageRenderData) | |||||
data.Extra["RemoteResourcesAllowed"] = ctx.QueryParam("allow-remote-resources") == "1" | data.Extra["RemoteResourcesAllowed"] = ctx.QueryParam("allow-remote-resources") == "1" | ||||
hasRemoteResources := false | hasRemoteResources := false | ||||
if v := ctx.Get("viewhtml.hasRemoteResources"); v != nil { | if v := ctx.Get("viewhtml.hasRemoteResources"); v != nil { | ||||
@@ -32,7 +32,7 @@ func init() { | |||||
return nil | return nil | ||||
}) | }) | ||||
p.GET("/proxy", func(ctx *koushin.Context) error { | |||||
p.GET("/proxy", func(ctx *alps.Context) error { | |||||
if !proxyEnabled { | if !proxyEnabled { | ||||
return echo.NewHTTPError(http.StatusForbidden, "proxy disabled") | return echo.NewHTTPError(http.StatusForbidden, "proxy disabled") | ||||
} | } | ||||
@@ -67,5 +67,5 @@ func init() { | |||||
return ctx.Stream(http.StatusOK, mediaType, &lr) | return ctx.Stream(http.StatusOK, mediaType, &lr) | ||||
}) | }) | ||||
koushin.RegisterPluginLoader(p.Loader()) | |||||
alps.RegisterPluginLoader(p.Loader()) | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
package koushinviewhtml | |||||
package alpsviewhtml | |||||
import ( | import ( | ||||
"bytes" | "bytes" | ||||
@@ -7,7 +7,7 @@ import ( | |||||
"regexp" | "regexp" | ||||
"strings" | "strings" | ||||
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" | |||||
alpsbase "git.sr.ht/~emersion/alps/plugins/base" | |||||
"github.com/aymerick/douceur/css" | "github.com/aymerick/douceur/css" | ||||
cssparser "github.com/chris-ramon/douceur/parser" | cssparser "github.com/chris-ramon/douceur/parser" | ||||
"github.com/microcosm-cc/bluemonday" | "github.com/microcosm-cc/bluemonday" | ||||
@@ -71,7 +71,7 @@ var allowedStyles = map[string]bool{ | |||||
} | } | ||||
type sanitizer struct { | type sanitizer struct { | ||||
msg *koushinbase.IMAPMessage | |||||
msg *alpsbase.IMAPMessage | |||||
allowRemoteResources bool | allowRemoteResources bool | ||||
hasRemoteResources bool | hasRemoteResources bool | ||||
} | } | ||||
@@ -1,4 +1,4 @@ | |||||
package koushinviewhtml | |||||
package alpsviewhtml | |||||
import ( | import ( | ||||
"bytes" | "bytes" | ||||
@@ -7,8 +7,8 @@ import ( | |||||
"io/ioutil" | "io/ioutil" | ||||
"strings" | "strings" | ||||
"git.sr.ht/~emersion/koushin" | |||||
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" | |||||
"git.sr.ht/~emersion/alps" | |||||
alpsbase "git.sr.ht/~emersion/alps/plugins/base" | |||||
"github.com/emersion/go-message" | "github.com/emersion/go-message" | ||||
) | ) | ||||
@@ -24,7 +24,7 @@ var tpl = template.Must(template.New("view-html.html").Parse(tplSrc)) | |||||
type viewer struct{} | type viewer struct{} | ||||
func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage, part *message.Entity) (interface{}, error) { | |||||
func (viewer) ViewMessagePart(ctx *alps.Context, msg *alpsbase.IMAPMessage, part *message.Entity) (interface{}, error) { | |||||
allowRemoteResources := ctx.QueryParam("allow-remote-resources") == "1" | allowRemoteResources := ctx.QueryParam("allow-remote-resources") == "1" | ||||
mimeType, _, err := part.Header.ContentType() | mimeType, _, err := part.Header.ContentType() | ||||
@@ -32,7 +32,7 @@ func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage | |||||
return nil, err | return nil, err | ||||
} | } | ||||
if !strings.EqualFold(mimeType, "text/html") { | if !strings.EqualFold(mimeType, "text/html") { | ||||
return nil, koushinbase.ErrViewUnsupported | |||||
return nil, alpsbase.ErrViewUnsupported | |||||
} | } | ||||
body, err := ioutil.ReadAll(part.Body) | body, err := ioutil.ReadAll(part.Body) | ||||
@@ -61,5 +61,5 @@ func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage | |||||
} | } | ||||
func init() { | func init() { | ||||
koushinbase.RegisterViewer(viewer{}) | |||||
alpsbase.RegisterViewer(viewer{}) | |||||
} | } |
@@ -1,10 +1,10 @@ | |||||
package koushinviewtext | |||||
package alpsviewtext | |||||
import ( | import ( | ||||
"git.sr.ht/~emersion/koushin" | |||||
"git.sr.ht/~emersion/alps" | |||||
) | ) | ||||
func init() { | func init() { | ||||
p := koushin.GoPlugin{Name: "viewtext"} | |||||
koushin.RegisterPluginLoader(p.Loader()) | |||||
p := alps.GoPlugin{Name: "viewtext"} | |||||
alps.RegisterPluginLoader(p.Loader()) | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
package koushinviewtext | |||||
package alpsviewtext | |||||
import ( | import ( | ||||
"bufio" | "bufio" | ||||
@@ -7,8 +7,8 @@ import ( | |||||
"net/url" | "net/url" | ||||
"strings" | "strings" | ||||
"git.sr.ht/~emersion/koushin" | |||||
koushinbase "git.sr.ht/~emersion/koushin/plugins/base" | |||||
"git.sr.ht/~emersion/alps" | |||||
alpsbase "git.sr.ht/~emersion/alps/plugins/base" | |||||
"github.com/emersion/go-message" | "github.com/emersion/go-message" | ||||
"gitlab.com/golang-commonmark/linkify" | "gitlab.com/golang-commonmark/linkify" | ||||
) | ) | ||||
@@ -53,13 +53,13 @@ func executeTemplate(name string, data interface{}) (template.HTML, error) { | |||||
type viewer struct{} | type viewer struct{} | ||||
func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage, part *message.Entity) (interface{}, error) { | |||||
func (viewer) ViewMessagePart(ctx *alps.Context, msg *alpsbase.IMAPMessage, part *message.Entity) (interface{}, error) { | |||||
mimeType, _, err := part.Header.ContentType() | mimeType, _, err := part.Header.ContentType() | ||||
if err != nil { | if err != nil { | ||||
return nil, err | return nil, err | ||||
} | } | ||||
if !strings.EqualFold(mimeType, "text/plain") { | if !strings.EqualFold(mimeType, "text/plain") { | ||||
return nil, koushinbase.ErrViewUnsupported | |||||
return nil, alpsbase.ErrViewUnsupported | |||||
} | } | ||||
var tokens []interface{} | var tokens []interface{} | ||||
@@ -114,5 +114,5 @@ func (viewer) ViewMessagePart(ctx *koushin.Context, msg *koushinbase.IMAPMessage | |||||
} | } | ||||
func init() { | func init() { | ||||
koushinbase.RegisterViewer(viewer{}) | |||||
alpsbase.RegisterViewer(viewer{}) | |||||
} | } |
@@ -1,4 +1,4 @@ | |||||
package koushin | |||||
package alps | |||||
import ( | import ( | ||||
"fmt" | "fmt" | ||||
@@ -60,7 +60,7 @@ type RenderData interface { | |||||
// } | // } | ||||
// | // | ||||
// data := &MyRenderData{ | // data := &MyRenderData{ | ||||
// BaseRenderData: *koushin.NewBaseRenderData(ctx), | |||||
// BaseRenderData: *alps.NewBaseRenderData(ctx), | |||||
// // other fields... | // // other fields... | ||||
// } | // } | ||||
func NewBaseRenderData(ctx *Context) *BaseRenderData { | func NewBaseRenderData(ctx *Context) *BaseRenderData { | ||||
@@ -1,4 +1,4 @@ | |||||
package koushin | |||||
package alps | |||||
import ( | import ( | ||||
"fmt" | "fmt" | ||||
@@ -11,9 +11,9 @@ import ( | |||||
"github.com/labstack/echo/v4" | "github.com/labstack/echo/v4" | ||||
) | ) | ||||
const cookieName = "koushin_session" | |||||
const cookieName = "alps_session" | |||||
// Server holds all the koushin server state. | |||||
// Server holds all the alps server state. | |||||
type Server struct { | type Server struct { | ||||
e *echo.Echo | e *echo.Echo | ||||
Sessions *SessionManager | Sessions *SessionManager | ||||
@@ -237,7 +237,7 @@ func (s *Server) Logger() echo.Logger { | |||||
// | // | ||||
// Use a type assertion to get it from a echo.Context: | // Use a type assertion to get it from a echo.Context: | ||||
// | // | ||||
// ctx := ectx.(*koushin.Context) | |||||
// ctx := ectx.(*alps.Context) | |||||
type Context struct { | type Context struct { | ||||
echo.Context | echo.Context | ||||
Server *Server | Server *Server | ||||
@@ -1,4 +1,4 @@ | |||||
package koushin | |||||
package alps | |||||
import ( | import ( | ||||
"crypto/rand" | "crypto/rand" | ||||
@@ -1,4 +1,4 @@ | |||||
package koushin | |||||
package alps | |||||
import ( | import ( | ||||
"fmt" | "fmt" | ||||
@@ -1,4 +1,4 @@ | |||||
package koushin | |||||
package alps | |||||
import ( | import ( | ||||
"encoding/json" | "encoding/json" | ||||
@@ -12,7 +12,7 @@ import ( | |||||
) | ) | ||||
// ErrNoStoreEntry is returned by Store.Get when the entry doesn't exist. | // ErrNoStoreEntry is returned by Store.Get when the entry doesn't exist. | ||||
var ErrNoStoreEntry = fmt.Errorf("koushin: no such entry in store") | |||||
var ErrNoStoreEntry = fmt.Errorf("alps: no such entry in store") | |||||
// Store allows storing per-user persistent data. | // Store allows storing per-user persistent data. | ||||
// | // | ||||
@@ -72,14 +72,14 @@ type imapStore struct { | |||||
cache *memoryStore | cache *memoryStore | ||||
} | } | ||||
var errIMAPMetadataUnsupported = fmt.Errorf("koushin: IMAP server doesn't support METADATA extension") | |||||
var errIMAPMetadataUnsupported = fmt.Errorf("alps: IMAP server doesn't support METADATA extension") | |||||
func newIMAPStore(session *Session) (*imapStore, error) { | func newIMAPStore(session *Session) (*imapStore, error) { | ||||
err := session.DoIMAP(func(c *imapclient.Client) error { | err := session.DoIMAP(func(c *imapclient.Client) error { | ||||
mc := imapmetadata.NewClient(c) | mc := imapmetadata.NewClient(c) | ||||
ok, err := mc.SupportMetadata() | ok, err := mc.SupportMetadata() | ||||
if err != nil { | if err != nil { | ||||
return fmt.Errorf("koushin: failed to check for IMAP METADATA support: %v", err) | |||||
return fmt.Errorf("alps: failed to check for IMAP METADATA support: %v", err) | |||||
} | } | ||||
if !ok { | if !ok { | ||||
return errIMAPMetadataUnsupported | return errIMAPMetadataUnsupported | ||||
@@ -93,7 +93,7 @@ func newIMAPStore(session *Session) (*imapStore, error) { | |||||
} | } | ||||
func (s *imapStore) key(key string) string { | func (s *imapStore) key(key string) string { | ||||
return "/private/vendor/koushin/" + key | |||||
return "/private/vendor/alps/" + key | |||||
} | } | ||||
func (s *imapStore) Get(key string, out interface{}) error { | func (s *imapStore) Get(key string, out interface{}) error { | ||||
@@ -109,14 +109,14 @@ func (s *imapStore) Get(key string, out interface{}) error { | |||||
return err | return err | ||||
}) | }) | ||||
if err != nil { | if err != nil { | ||||
return fmt.Errorf("koushin: failed to fetch IMAP store entry %q: %v", key, err) | |||||
return fmt.Errorf("alps: failed to fetch IMAP store entry %q: %v", key, err) | |||||
} | } | ||||
v, ok := entries[s.key(key)] | v, ok := entries[s.key(key)] | ||||
if !ok { | if !ok { | ||||
return ErrNoStoreEntry | return ErrNoStoreEntry | ||||
} | } | ||||
if err := json.Unmarshal([]byte(v), out); err != nil { | if err := json.Unmarshal([]byte(v), out); err != nil { | ||||
return fmt.Errorf("koushin: failed to unmarshal IMAP store entry %q: %v", key, err) | |||||
return fmt.Errorf("alps: failed to unmarshal IMAP store entry %q: %v", key, err) | |||||
} | } | ||||
return s.cache.Put(key, out) | return s.cache.Put(key, out) | ||||
} | } | ||||
@@ -124,7 +124,7 @@ func (s *imapStore) Get(key string, out interface{}) error { | |||||
func (s *imapStore) Put(key string, v interface{}) error { | func (s *imapStore) Put(key string, v interface{}) error { | ||||
b, err := json.Marshal(v) | b, err := json.Marshal(v) | ||||
if err != nil { | if err != nil { | ||||
return fmt.Errorf("koushin: failed to marshal IMAP store entry %q: %v", key, err) | |||||
return fmt.Errorf("alps: failed to marshal IMAP store entry %q: %v", key, err) | |||||
} | } | ||||
entries := map[string]string{ | entries := map[string]string{ | ||||
s.key(key): string(b), | s.key(key): string(b), | ||||
@@ -134,7 +134,7 @@ func (s *imapStore) Put(key string, v interface{}) error { | |||||
return mc.SetMetadata("", entries) | return mc.SetMetadata("", entries) | ||||
}) | }) | ||||
if err != nil { | if err != nil { | ||||
return fmt.Errorf("koushin: failed to put IMAP store entry %q: %v", key, err) | |||||
return fmt.Errorf("alps: failed to put IMAP store entry %q: %v", key, err) | |||||
} | } | ||||
return s.cache.Put(key, v) | return s.cache.Put(key, v) | ||||
@@ -1,5 +1,5 @@ | |||||
{{template "head.html"}} | {{template "head.html"}} | ||||
<h1>koushin webmail</h1> | |||||
<h1>alps webmail</h1> | |||||
<form method="post" action="/login"> | <form method="post" action="/login"> | ||||
<p> | <p> | ||||
@@ -4,7 +4,7 @@ | |||||
<meta charset="utf-8"> | <meta charset="utf-8"> | ||||
<meta name="viewport" content="width=device-width, initial-scale=1"> | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
<meta name="theme-color" content="#ffffff"> | <meta name="theme-color" content="#ffffff"> | ||||
<title>koushin webmail</title> | |||||
<title>alps webmail</title> | |||||
<link rel="stylesheet" href="/themes/sourcehut/assets/style.css"> | <link rel="stylesheet" href="/themes/sourcehut/assets/style.css"> | ||||
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QIGCC8n92KyhQAAAj1QTFRFAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////anIwUQAAAL50Uk5TAAECAwQFBgcICQoLDA4PEBESExQVFhcYGRobHB0eHyAhIyQmJygpKistLzAzNDU2Nzg5Ozw9QEFDREZHSElLTE1OT1BRVFdYWVpbXF1eX2BhZGZnaGltbnBxdHV3eHp7fn+AgYKDhIWGh4iJio2TlJucnqGio6Smp6ipqqusrbCxsrO0tre4ury9vr/Cw8TFxsfIycrMzc7P0dLT1dbY2dvf4OLj5OXm5+jq6+zt7u/w8fL09fb3+Pn6+/z9/gNzyOkAAAABYktHRL6k3IPDAAAFwUlEQVQYGe3B+VtUVQAG4G9i0TQZZyA1S0JxydzDNFTUqXBfcylzS8UE21TMyjAQUQnFEi0BHQU3cAc0UGbm+9v65Zw7y70zc++dc3qenof3xZAhQ4b8T+V/uGn/kdrm1psdHTdbm2uP7Ns434//yLD5e+u7aOH+6T0ludBszGf1L5jC87rNBdDm9XXnw0wrfG71cOhQ9N0z2vTk20Ko9t5PYToQOj4FKr17IkKHwtUToMobVQN0of/rkVDik7t06c5yZC7/V1rqajq6f9vKQFlZYNW2A0cvdNPSL35kaPEDmgxerFiSjwQFZQebQzTpKkUmsg4xUX/Nijwk4V15aoAJIhVZcM13ngnatuQhJe/WNiY464VLE28xXsMCD9LyLDzLeMEiuDLzIeM0zIRNs88xTvcMuFDSx1htC+FA6XXG6pkLx0qeM8aLHdlwJGfnP4zRNw8OzexjjOaJcGzSn4zR8z4cmfiQUZF9WXAhuyLCqO4iOOC7xahni+BSWQ+jgl7YlnWeUR3FcG1qJ6MasmDXIUZdexMZGNvGqAOwaTGjrnqREd/fNERKYUvBAxqueZEhXzsNXX7Y8SsNHW8iY+M6afgZNnxKw7NiKDCtl4YA0nrjHqXIIiixjIY7I5FOFQ37oEglDZVIo+glpeYsKJJ9mdJAIVI7QenFRChT3E+pGim9F6G0AwrtohSeglR+otSWDYVygpSqkUJRmNJCKLWEUmgCkvuOUgPU8vxO6TCSev0ZpZlQ7ANKj4YhmXWUGqBcI6UVSOY8pQVQbhGlM0hiTJhCmwfKeW5QCPlh7TNKW6DBdkobYa2eQn8eNPC9pPAbLA17QaEGWtRS6MuBlfmUVkCLtZTmwspeCoN50MIXprATVuopXIQmLRROwUoXhQpoUkXhNizkU1oCTZZRyoPZh5Tyock4SnNgtolCF3TxPKKwBmb7KTRBm0sUdsPsCIWj0OY4hR9gVkthP7SppHASZs0UtkGbLyg0wqyVwkpos4FCC8xuUghAm3IK7TDroFAGbQIUgjDroFAGbQIUgjC7SSEAbcoptMOslcIqaLOBQgvMmilsgzY7KDTCrJbCAWhTReEkzI5QOAptfqTwPcz2UbgAbS5R2AWzjRS6oYvnMYXVMJtPqQCavEVpNsz8lMqgyXJKo2DhPoWD0OQwhU5YOU2hGZpcoVADK3sohLzQwh+h8CWslFBaCS3WUZoDK7nPKZyCFnUUerNh6TSFAS808L+iUANrmylthQafU1oPawVhCm0eKOcJUhgcjSTOUVoI5RZTqkMyqymdhXJNlMqRzPAnlGZDsRJKD3OR1LeUzkEtTxOlSiRXGKL0EZRaSmlwPFI4Tul6DhTKvUXpGFKZEqa0EwrtoRQqRkrVlP6ZBGUmD1A6htQm9FP6IxuK5Fyl1P8O0viahgoo8g0NB5HOyDuUImVQ4mMabo9AWgEaeqZCgel9NCyFDb/Q0DkWGXv7Lg0nYIe/i4Y2HzKUf4OGe6NhS2mEhr98yEh+Kw3hBbCpglHt45CB8TcY9RXsyjrLqM5pcG36XUadfg22eYOM6l0Glz7uY9T1UXCg6AFjHMqGCznfMMb9Qjgyo4cxLhfDsclXGePpdDg0r48x+nflwJHcPQOM0TsLjs3rYazgEtjnWXqLsZ7Oggszuhnn9w9gU0kT49yfDleKgozXuMiDtDyLmxjveiFc8jYwwY3tPqTk/zzIBPWj4FpWRYQJXtau9SEJ/7q6V0wQ/uo1ZKK0iybhlqplYxHP89byw1ciNLm3ABny/0xLjy4dr/xiQ3kgUL5hR9WPlx7T0onRyFzgDl26vRRKjKwcoAv9B0dAlcLqMB0KH3sHKk2pDtGBwWPFUG3C4Ue06WHleOgwbMWZENMarCvPhTb+jb/1MYXemvWjoVnO3J2nbtNCZ82Xc7LxH8mbs2b3DycbW9qDwfaWxpPf71o9exSGDBky5P/pX9F6dsCMuJp+AAAAAElFTkSuQmCC" /> | <link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QIGCC8n92KyhQAAAj1QTFRFAAAA////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////anIwUQAAAL50Uk5TAAECAwQFBgcICQoLDA4PEBESExQVFhcYGRobHB0eHyAhIyQmJygpKistLzAzNDU2Nzg5Ozw9QEFDREZHSElLTE1OT1BRVFdYWVpbXF1eX2BhZGZnaGltbnBxdHV3eHp7fn+AgYKDhIWGh4iJio2TlJucnqGio6Smp6ipqqusrbCxsrO0tre4ury9vr/Cw8TFxsfIycrMzc7P0dLT1dbY2dvf4OLj5OXm5+jq6+zt7u/w8fL09fb3+Pn6+/z9/gNzyOkAAAABYktHRL6k3IPDAAAFwUlEQVQYGe3B+VtUVQAG4G9i0TQZZyA1S0JxydzDNFTUqXBfcylzS8UE21TMyjAQUQnFEi0BHQU3cAc0UGbm+9v65Zw7y70zc++dc3qenof3xZAhQ4b8T+V/uGn/kdrm1psdHTdbm2uP7Ns434//yLD5e+u7aOH+6T0ludBszGf1L5jC87rNBdDm9XXnw0wrfG71cOhQ9N0z2vTk20Ko9t5PYToQOj4FKr17IkKHwtUToMobVQN0of/rkVDik7t06c5yZC7/V1rqajq6f9vKQFlZYNW2A0cvdNPSL35kaPEDmgxerFiSjwQFZQebQzTpKkUmsg4xUX/Nijwk4V15aoAJIhVZcM13ngnatuQhJe/WNiY464VLE28xXsMCD9LyLDzLeMEiuDLzIeM0zIRNs88xTvcMuFDSx1htC+FA6XXG6pkLx0qeM8aLHdlwJGfnP4zRNw8OzexjjOaJcGzSn4zR8z4cmfiQUZF9WXAhuyLCqO4iOOC7xahni+BSWQ+jgl7YlnWeUR3FcG1qJ6MasmDXIUZdexMZGNvGqAOwaTGjrnqREd/fNERKYUvBAxqueZEhXzsNXX7Y8SsNHW8iY+M6afgZNnxKw7NiKDCtl4YA0nrjHqXIIiixjIY7I5FOFQ37oEglDZVIo+glpeYsKJJ9mdJAIVI7QenFRChT3E+pGim9F6G0AwrtohSeglR+otSWDYVygpSqkUJRmNJCKLWEUmgCkvuOUgPU8vxO6TCSev0ZpZlQ7ANKj4YhmXWUGqBcI6UVSOY8pQVQbhGlM0hiTJhCmwfKeW5QCPlh7TNKW6DBdkobYa2eQn8eNPC9pPAbLA17QaEGWtRS6MuBlfmUVkCLtZTmwspeCoN50MIXprATVuopXIQmLRROwUoXhQpoUkXhNizkU1oCTZZRyoPZh5Tyock4SnNgtolCF3TxPKKwBmb7KTRBm0sUdsPsCIWj0OY4hR9gVkthP7SppHASZs0UtkGbLyg0wqyVwkpos4FCC8xuUghAm3IK7TDroFAGbQIUgjDroFAGbQIUgjC7SSEAbcoptMOslcIqaLOBQgvMmilsgzY7KDTCrJbCAWhTReEkzI5QOAptfqTwPcz2UbgAbS5R2AWzjRS6oYvnMYXVMJtPqQCavEVpNsz8lMqgyXJKo2DhPoWD0OQwhU5YOU2hGZpcoVADK3sohLzQwh+h8CWslFBaCS3WUZoDK7nPKZyCFnUUerNh6TSFAS808L+iUANrmylthQafU1oPawVhCm0eKOcJUhgcjSTOUVoI5RZTqkMyqymdhXJNlMqRzPAnlGZDsRJKD3OR1LeUzkEtTxOlSiRXGKL0EZRaSmlwPFI4Tul6DhTKvUXpGFKZEqa0EwrtoRQqRkrVlP6ZBGUmD1A6htQm9FP6IxuK5Fyl1P8O0viahgoo8g0NB5HOyDuUImVQ4mMabo9AWgEaeqZCgel9NCyFDb/Q0DkWGXv7Lg0nYIe/i4Y2HzKUf4OGe6NhS2mEhr98yEh+Kw3hBbCpglHt45CB8TcY9RXsyjrLqM5pcG36XUadfg22eYOM6l0Glz7uY9T1UXCg6AFjHMqGCznfMMb9Qjgyo4cxLhfDsclXGePpdDg0r48x+nflwJHcPQOM0TsLjs3rYazgEtjnWXqLsZ7Oggszuhnn9w9gU0kT49yfDleKgozXuMiDtDyLmxjveiFc8jYwwY3tPqTk/zzIBPWj4FpWRYQJXtau9SEJ/7q6V0wQ/uo1ZKK0iybhlqplYxHP89byw1ciNLm3ABny/0xLjy4dr/xiQ3kgUL5hR9WPlx7T0onRyFzgDl26vRRKjKwcoAv9B0dAlcLqMB0KH3sHKk2pDtGBwWPFUG3C4Ue06WHleOgwbMWZENMarCvPhTb+jb/1MYXemvWjoVnO3J2nbtNCZ82Xc7LxH8mbs2b3DycbW9qDwfaWxpPf71o9exSGDBky5P/pX9F6dsCMuJp+AAAAAElFTkSuQmCC" /> | ||||
</head> | </head> | ||||
@@ -1,7 +1,7 @@ | |||||
<nav class="container-fluid navbar navbar-light navbar-expand-sm"> | <nav class="container-fluid navbar navbar-light navbar-expand-sm"> | ||||
<!-- TODO: show active plugin name --> | <!-- TODO: show active plugin name --> | ||||
<a class="navbar-brand" href="/"> | <a class="navbar-brand" href="/"> | ||||
koushin | |||||
alps | |||||
<span class="text-danger">mail</span> | <span class="text-danger">mail</span> | ||||
</a> | </a> | ||||
{{if .LoggedIn}} | {{if .LoggedIn}} | ||||