2020-02-07 17:27:45 +01:00
// Vikunja is a to-do list application to facilitate your life.
2021-02-02 20:19:13 +01:00
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
2018-11-26 21:17:33 +01:00
//
2019-12-04 20:39:56 +01:00
// This program is free software: you can redistribute it and/or modify
2020-12-23 16:41:52 +01:00
// it under the terms of the GNU Affero General Public Licensee as published by
2019-12-04 20:39:56 +01:00
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
2018-11-26 21:17:33 +01:00
//
2019-12-04 20:39:56 +01:00
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2020-12-23 16:41:52 +01:00
// GNU Affero General Public Licensee for more details.
2018-11-26 21:17:33 +01:00
//
2020-12-23 16:41:52 +01:00
// You should have received a copy of the GNU Affero General Public Licensee
2019-12-04 20:39:56 +01:00
// along with this program. If not, see <https://www.gnu.org/licenses/>.
2018-11-26 21:17:33 +01:00
2018-11-12 16:46:35 +01:00
// @title Vikunja API
2020-02-07 17:27:45 +01:00
// @description This is the documentation for the [Vikunja](http://vikunja.io) API. Vikunja is a cross-plattform To-do-application with a lot of features, such as sharing lists with users or teams. <!-- ReDoc-Inject: <security-definitions> -->
2019-10-23 23:11:40 +02:00
// @description # Pagination
// @description Every endpoint capable of pagination will return two headers:
// @description * `x-pagination-total-pages`: The total number of available pages for this request
// @description * `x-pagination-result-count`: The number of items returned for this request.
2020-08-10 14:11:43 +02:00
// @description # Rights
// @description All endpoints which return a single item (list, task, namespace, etc.) - no array - will also return a `x-max-right` header with the max right the user has on this item as an int where `0` is `Read Only`, `1` is `Read & Write` and `2` is `Admin`.
// @description This can be used to show or hide ui elements based on the rights the user has.
2019-01-03 23:22:06 +01:00
// @description # Authorization
2019-10-16 22:52:29 +02:00
// @description **JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer <jwt-token>`-header to authenticate successfully.
2019-01-03 23:22:06 +01:00
// @description
// @description **BasicAuth:** Only used when requesting tasks via caldav.
// @description <!-- ReDoc-Inject: <security-definitions> -->
2018-11-12 16:46:35 +01:00
// @BasePath /api/v1
2019-01-03 23:22:06 +01:00
// @license.url http://code.vikunja.io/api/src/branch/master/LICENSE
// @license.name GPLv3
// @contact.url http://vikunja.io/en/contact/
// @contact.name General Vikunja contact
// @contact.email hello@vikunja.io
2018-11-12 16:46:35 +01:00
// @securityDefinitions.basic BasicAuth
2019-01-03 23:22:06 +01:00
// @securityDefinitions.apikey JWTKeyAuth
2018-11-12 16:46:35 +01:00
// @in header
// @name Authorization
2018-06-10 11:11:41 +02:00
2018-08-29 15:22:17 +02:00
package routes
2018-06-10 11:11:41 +02:00
import (
2021-08-03 23:43:18 +02:00
"errors"
"fmt"
2020-10-11 22:10:03 +02:00
"strings"
"time"
2021-11-14 20:42:33 +01:00
"github.com/ulule/limiter/v3"
2021-09-04 21:26:31 +02:00
vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file"
2019-07-06 22:12:26 +02:00
"code.vikunja.io/api/pkg/config"
2020-12-23 16:32:28 +01:00
"code.vikunja.io/api/pkg/db"
2018-12-01 00:26:56 +01:00
"code.vikunja.io/api/pkg/log"
2018-11-17 00:17:37 +01:00
"code.vikunja.io/api/pkg/models"
2020-11-21 17:38:58 +01:00
"code.vikunja.io/api/pkg/modules/auth"
"code.vikunja.io/api/pkg/modules/auth/openid"
2020-05-26 22:07:55 +02:00
"code.vikunja.io/api/pkg/modules/background"
backgroundHandler "code.vikunja.io/api/pkg/modules/background/handler"
"code.vikunja.io/api/pkg/modules/background/unsplash"
2020-06-11 19:31:37 +02:00
"code.vikunja.io/api/pkg/modules/background/upload"
2020-01-19 17:52:16 +01:00
"code.vikunja.io/api/pkg/modules/migration"
migrationHandler "code.vikunja.io/api/pkg/modules/migration/handler"
2020-12-23 16:32:28 +01:00
microsofttodo "code.vikunja.io/api/pkg/modules/migration/microsoft-todo"
2020-05-23 22:50:54 +02:00
"code.vikunja.io/api/pkg/modules/migration/todoist"
2020-12-23 16:32:28 +01:00
"code.vikunja.io/api/pkg/modules/migration/trello"
2020-01-19 17:52:16 +01:00
"code.vikunja.io/api/pkg/modules/migration/wunderlist"
2018-10-31 13:42:38 +01:00
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
2019-05-22 19:48:48 +02:00
"code.vikunja.io/api/pkg/routes/caldav"
2019-02-17 20:53:04 +01:00
_ "code.vikunja.io/api/pkg/swagger" // To generate swagger docs
2020-01-26 18:08:06 +01:00
"code.vikunja.io/api/pkg/user"
2020-06-19 20:47:15 +02:00
"code.vikunja.io/api/pkg/version"
2018-12-01 00:26:56 +01:00
"code.vikunja.io/web"
"code.vikunja.io/web/handler"
2021-08-03 23:43:18 +02:00
2018-11-17 00:17:37 +01:00
"github.com/asaskevich/govalidator"
2020-06-19 20:47:15 +02:00
"github.com/getsentry/sentry-go"
sentryecho "github.com/getsentry/sentry-go/echo"
2021-08-03 23:43:18 +02:00
"github.com/golang-jwt/jwt/v4"
2019-05-07 21:42:24 +02:00
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
2019-01-25 12:40:54 +01:00
elog "github.com/labstack/gommon/log"
2018-06-10 11:11:41 +02:00
)
2018-11-17 00:17:37 +01:00
// CustomValidator is a dummy struct to use govalidator with echo
type CustomValidator struct { }
// Validate validates stuff
func ( cv * CustomValidator ) Validate ( i interface { } ) error {
if _ , err := govalidator . ValidateStruct ( i ) ; err != nil {
var errs [ ] string
for field , e := range govalidator . ErrorsByField ( err ) {
errs = append ( errs , field + ": " + e )
}
httperr := models . ValidationHTTPError {
2019-08-31 22:56:41 +02:00
HTTPError : web . HTTPError {
2018-11-17 00:17:37 +01:00
Code : models . ErrCodeInvalidData ,
Message : "Invalid Data" ,
} ,
2019-08-31 22:56:41 +02:00
InvalidFields : errs ,
2018-11-17 00:17:37 +01:00
}
return httperr
}
return nil
}
2018-06-10 11:11:41 +02:00
// NewEcho registers a new Echo instance
func NewEcho ( ) * echo . Echo {
e := echo . New ( )
2018-11-03 16:05:45 +01:00
e . HideBanner = true
2019-01-25 12:40:54 +01:00
if l , ok := e . Logger . ( * elog . Logger ) ; ok {
2019-07-06 22:12:26 +02:00
if config . LogEcho . GetString ( ) == "off" {
2019-01-25 12:40:54 +01:00
l . SetLevel ( elog . OFF )
}
l . EnableColor ( )
l . SetHeader ( log . ErrFmt )
l . SetOutput ( log . GetLogWriter ( "echo" ) )
}
2018-06-10 11:11:41 +02:00
// Logger
2019-07-06 22:12:26 +02:00
if config . LogHTTP . GetString ( ) != "off" {
2019-01-25 12:40:54 +01:00
e . Use ( middleware . LoggerWithConfig ( middleware . LoggerConfig {
Format : log . WebFmt + "\n" ,
Output : log . GetLogWriter ( "http" ) ,
} ) )
}
2018-06-10 11:11:41 +02:00
2020-06-19 20:47:15 +02:00
// panic recover
e . Use ( middleware . Recover ( ) )
if config . ServiceSentryDsn . GetString ( ) != "" {
if err := sentry . Init ( sentry . ClientOptions {
Dsn : config . ServiceSentryDsn . GetString ( ) ,
AttachStacktrace : true ,
Release : version . Version ,
} ) ; err != nil {
log . Criticalf ( "Sentry init failed: %s" , err )
}
defer sentry . Flush ( 5 * time . Second )
e . Use ( sentryecho . New ( sentryecho . Options {
Repanic : true ,
} ) )
e . HTTPErrorHandler = func ( err error , c echo . Context ) {
// Only capture errors not already handled by echo
2022-03-27 17:52:33 +02:00
var herr * echo . HTTPError
if errors . As ( err , & herr ) {
2020-06-19 20:47:15 +02:00
hub := sentryecho . GetHubFromContext ( c )
if hub != nil {
hub . WithScope ( func ( scope * sentry . Scope ) {
scope . SetExtra ( "url" , c . Request ( ) . URL )
hub . CaptureException ( err )
} )
} else {
sentry . CaptureException ( err )
log . Debugf ( "Could not add context for sending error '%s' to sentry" , err . Error ( ) )
}
log . Debugf ( "Error '%s' sent to sentry" , err . Error ( ) )
}
e . DefaultHTTPErrorHandler ( err , c )
}
}
2018-11-17 00:17:37 +01:00
// Validation
e . Validator = & CustomValidator { }
2019-03-24 10:13:40 +01:00
// Handler config
handler . SetAuthProvider ( & web . Auths {
2020-11-21 17:38:58 +01:00
AuthObject : auth . GetAuthFromClaims ,
2019-03-24 10:13:40 +01:00
} )
2019-07-20 20:12:10 +02:00
handler . SetLoggingProvider ( log . GetLogger ( ) )
2019-10-23 23:11:40 +02:00
handler . SetMaxItemsPerPage ( config . ServiceMaxItemsPerPage . GetInt ( ) )
2020-12-23 16:32:28 +01:00
handler . SetSessionFactory ( db . NewSession )
2019-03-24 10:13:40 +01:00
2018-06-10 11:11:41 +02:00
return e
}
// RegisterRoutes registers all routes for the application
func RegisterRoutes ( e * echo . Echo ) {
2019-07-06 22:12:26 +02:00
if config . ServiceEnableCaldav . GetBool ( ) {
2019-05-22 19:48:48 +02:00
// Caldav routes
wkg := e . Group ( "/.well-known" )
wkg . Use ( middleware . BasicAuth ( caldavBasicAuth ) )
wkg . Any ( "/caldav" , caldav . PrincipalHandler )
wkg . Any ( "/caldav/" , caldav . PrincipalHandler )
c := e . Group ( "/dav" )
registerCalDavRoutes ( c )
}
2021-10-03 20:37:02 +02:00
// healthcheck
e . GET ( "/health" , HealthcheckHandler )
2018-09-07 22:49:16 +02:00
// CORS_SHIT
2020-01-26 20:09:54 +01:00
if config . CorsEnable . GetBool ( ) {
e . Use ( middleware . CORSWithConfig ( middleware . CORSConfig {
AllowOrigins : config . CorsOrigins . GetStringSlice ( ) ,
MaxAge : config . CorsMaxAge . GetInt ( ) ,
Skipper : func ( context echo . Context ) bool {
// Since it is not possible to register this middleware just for the api group,
// we just disable it when for caldav requests.
// Caldav requires OPTIONS requests to be answered in a specific manner,
// not doing this would break the caldav implementation
return strings . HasPrefix ( context . Path ( ) , "/dav" )
} ,
} ) )
}
2018-06-10 11:11:41 +02:00
// API Routes
a := e . Group ( "/api/v1" )
2019-05-22 19:48:48 +02:00
registerAPIRoutes ( a )
}
func registerAPIRoutes ( a * echo . Group ) {
2018-06-10 11:11:41 +02:00
2020-01-26 20:53:47 +01:00
// This is the group with no auth
// It is its own group to be able to rate limit this based on different heuristics
n := a . Group ( "" )
setupRateLimit ( n , "ip" )
2019-01-03 23:22:06 +01:00
// Docs
2020-01-26 20:53:47 +01:00
n . GET ( "/docs.json" , apiv1 . DocsJSON )
n . GET ( "/docs" , apiv1 . RedocUI )
2018-09-17 18:34:46 +02:00
2018-12-12 23:50:35 +01:00
// Prometheus endpoint
2020-01-26 20:53:47 +01:00
setupMetrics ( n )
2018-12-12 23:50:35 +01:00
2021-11-14 20:42:33 +01:00
// Separate route for unauthenticated routes to enable rate limits for it
ur := a . Group ( "" )
rate := limiter . Rate {
Period : 60 * time . Second ,
Limit : 10 ,
}
rateLimiter := createRateLimiter ( rate )
ur . Use ( RateLimit ( rateLimiter , "ip" ) )
2020-11-21 17:38:58 +01:00
if config . AuthLocalEnabled . GetBool ( ) {
// User stuff
2021-11-14 20:42:33 +01:00
ur . POST ( "/login" , apiv1 . Login )
ur . POST ( "/register" , apiv1 . RegisterUser )
ur . POST ( "/user/password/token" , apiv1 . UserRequestResetPasswordToken )
ur . POST ( "/user/password/reset" , apiv1 . UserResetPassword )
ur . POST ( "/user/confirm" , apiv1 . UserConfirmEmail )
2020-11-21 17:38:58 +01:00
}
if config . AuthOpenIDEnabled . GetBool ( ) {
2021-11-14 20:42:33 +01:00
ur . POST ( "/auth/openid/:provider/callback" , openid . HandleCallback )
2020-11-21 17:38:58 +01:00
}
2018-06-10 11:11:41 +02:00
2020-11-29 00:08:30 +01:00
// Testing
if config . ServiceTestingtoken . GetString ( ) != "" {
n . PATCH ( "/test/:table" , apiv1 . HandleTesting )
}
2019-07-16 00:54:38 +02:00
// Info endpoint
2020-01-26 20:53:47 +01:00
n . GET ( "/info" , apiv1 . Info )
2019-07-16 00:54:38 +02:00
2020-03-01 21:30:37 +01:00
// Avatar endpoint
2020-12-18 23:11:11 +01:00
n . GET ( "/avatar/:username" , apiv1 . GetAvatar )
2020-03-01 21:30:37 +01:00
2019-08-31 22:56:41 +02:00
// Link share auth
if config . ServiceEnableLinkSharing . GetBool ( ) {
2021-11-14 20:42:33 +01:00
ur . POST ( "/shares/:share/auth" , apiv1 . AuthenticateLinkShare )
2019-08-31 22:56:41 +02:00
}
2019-07-21 23:27:30 +02:00
// ===== Routes with Authetication =====
2018-06-10 11:11:41 +02:00
// Authetification
2021-08-03 23:43:18 +02:00
a . Use ( middleware . JWTWithConfig ( middleware . JWTConfig {
// Custom parse function to make the middleware work with the github.com/golang-jwt/jwt/v4 package.
// See https://github.com/labstack/echo/pull/1916#issuecomment-878046299
ParseTokenFunc : func ( auth string , c echo . Context ) ( interface { } , error ) {
keyFunc := func ( t * jwt . Token ) ( interface { } , error ) {
if t . Method . Alg ( ) != "HS256" {
return nil , fmt . Errorf ( "unexpected jwt signing method=%v" , t . Header [ "alg" ] )
}
return [ ] byte ( config . ServiceJWTSecret . GetString ( ) ) , nil
}
token , err := jwt . Parse ( auth , keyFunc )
if err != nil {
return nil , err
}
if ! token . Valid {
return nil , errors . New ( "invalid token" )
}
return token , nil
} ,
} ) )
2018-12-01 00:26:56 +01:00
2019-07-21 23:27:30 +02:00
// Rate limit
2020-01-26 20:53:47 +01:00
setupRateLimit ( a , config . RateLimitKind . GetString ( ) )
2019-07-21 23:27:30 +02:00
2018-12-12 23:50:35 +01:00
// Middleware to collect metrics
2019-07-21 23:44:12 +02:00
setupMetricsMiddleware ( a )
2018-12-12 23:50:35 +01:00
2018-06-10 11:11:41 +02:00
a . POST ( "/tokenTest" , apiv1 . CheckToken )
2018-06-10 14:14:10 +02:00
2018-09-20 19:42:01 +02:00
// User stuff
2020-04-17 21:25:35 +02:00
u := a . Group ( "/user" )
u . GET ( "" , apiv1 . UserShow )
u . POST ( "/password" , apiv1 . UserChangePassword )
u . GET ( "s" , apiv1 . UserList )
u . POST ( "/token" , apiv1 . RenewToken )
u . POST ( "/settings/email" , apiv1 . UpdateUserEmail )
2020-08-02 19:16:58 +02:00
u . GET ( "/settings/avatar" , apiv1 . GetUserAvatarProvider )
u . POST ( "/settings/avatar" , apiv1 . ChangeUserAvatarProvider )
u . PUT ( "/settings/avatar/upload" , apiv1 . UploadAvatar )
2020-12-19 00:21:17 +01:00
u . POST ( "/settings/general" , apiv1 . UpdateGeneralUserSettings )
2021-09-04 21:26:31 +02:00
u . POST ( "/export/request" , apiv1 . RequestUserDataExport )
u . POST ( "/export/download" , apiv1 . DownloadUserDataExport )
2022-01-16 12:05:56 +01:00
u . GET ( "/timezones" , apiv1 . GetAvailableTimezones )
2020-05-29 17:15:59 +02:00
if config . ServiceEnableTotp . GetBool ( ) {
u . GET ( "/settings/totp" , apiv1 . UserTOTP )
u . POST ( "/settings/totp/enroll" , apiv1 . UserTOTPEnroll )
u . POST ( "/settings/totp/enable" , apiv1 . UserTOTPEnable )
u . POST ( "/settings/totp/disable" , apiv1 . UserTOTPDisable )
u . GET ( "/settings/totp/qrcode" , apiv1 . UserTOTPQrCode )
}
2018-09-20 19:42:01 +02:00
2021-08-11 21:08:10 +02:00
// User deletion
if config . ServiceEnableUserDeletion . GetBool ( ) {
u . POST ( "/deletion/request" , apiv1 . UserRequestDeletion )
u . POST ( "/deletion/confirm" , apiv1 . UserConfirmDeletion )
u . POST ( "/deletion/cancel" , apiv1 . UserCancelDeletion )
}
2018-12-01 00:26:56 +01:00
listHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2018-10-11 17:53:59 +02:00
return & models . List { }
} ,
2018-07-07 14:19:34 +02:00
}
2018-07-08 22:50:01 +02:00
a . GET ( "/lists" , listHandler . ReadAllWeb )
2018-07-21 15:08:46 +02:00
a . GET ( "/lists/:list" , listHandler . ReadOneWeb )
a . POST ( "/lists/:list" , listHandler . UpdateWeb )
a . DELETE ( "/lists/:list" , listHandler . DeleteWeb )
a . PUT ( "/namespaces/:namespace/lists" , listHandler . CreateWeb )
2019-07-18 18:38:21 +02:00
a . GET ( "/lists/:list/listusers" , apiv1 . ListUsersForList )
2018-06-12 18:07:47 +02:00
2019-08-31 22:56:41 +02:00
if config . ServiceEnableLinkSharing . GetBool ( ) {
listSharingHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . LinkSharing { }
} ,
}
a . PUT ( "/lists/:list/shares" , listSharingHandler . CreateWeb )
a . GET ( "/lists/:list/shares" , listSharingHandler . ReadAllWeb )
a . GET ( "/lists/:list/shares/:share" , listSharingHandler . ReadOneWeb )
a . DELETE ( "/lists/:list/shares/:share" , listSharingHandler . DeleteWeb )
}
2019-12-01 14:38:11 +01:00
taskCollectionHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . TaskCollection { }
} ,
}
a . GET ( "/lists/:list/tasks" , taskCollectionHandler . ReadAllWeb )
2020-04-19 09:27:28 +02:00
kanbanBucketHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . Bucket { }
} ,
}
a . GET ( "/lists/:list/buckets" , kanbanBucketHandler . ReadAllWeb )
a . PUT ( "/lists/:list/buckets" , kanbanBucketHandler . CreateWeb )
a . POST ( "/lists/:list/buckets/:bucket" , kanbanBucketHandler . UpdateWeb )
a . DELETE ( "/lists/:list/buckets/:bucket" , kanbanBucketHandler . DeleteWeb )
2020-06-30 22:53:14 +02:00
listDuplicateHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . ListDuplicate { }
} ,
}
a . PUT ( "/lists/:listid/duplicate" , listDuplicateHandler . CreateWeb )
2018-12-01 00:26:56 +01:00
taskHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2019-08-14 22:19:04 +02:00
return & models . Task { }
2018-10-11 17:53:59 +02:00
} ,
2018-07-11 02:13:53 +02:00
}
2018-08-30 08:09:17 +02:00
a . PUT ( "/lists/:list" , taskHandler . CreateWeb )
2019-11-02 21:33:18 +01:00
a . GET ( "/tasks/:listtask" , taskHandler . ReadOneWeb )
2019-12-01 14:38:11 +01:00
a . GET ( "/tasks/all" , taskCollectionHandler . ReadAllWeb )
2018-08-30 08:09:17 +02:00
a . DELETE ( "/tasks/:listtask" , taskHandler . DeleteWeb )
a . POST ( "/tasks/:listtask" , taskHandler . UpdateWeb )
2018-07-02 08:40:24 +02:00
2018-12-28 22:49:46 +01:00
bulkTaskHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . BulkTask { }
} ,
}
a . POST ( "/tasks/bulk" , bulkTaskHandler . UpdateWeb )
2019-01-08 20:13:07 +01:00
assigneeTaskHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2019-08-14 22:19:04 +02:00
return & models . TaskAssginee { }
2019-01-08 20:13:07 +01:00
} ,
}
a . PUT ( "/tasks/:listtask/assignees" , assigneeTaskHandler . CreateWeb )
a . DELETE ( "/tasks/:listtask/assignees/:user" , assigneeTaskHandler . DeleteWeb )
a . GET ( "/tasks/:listtask/assignees" , assigneeTaskHandler . ReadAllWeb )
bulkAssigneeHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . BulkAssignees { }
} ,
}
2019-01-10 00:08:12 +01:00
a . POST ( "/tasks/:listtask/assignees/bulk" , bulkAssigneeHandler . CreateWeb )
2019-01-08 20:13:07 +01:00
2018-12-31 02:18:41 +01:00
labelTaskHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . LabelTask { }
} ,
}
a . PUT ( "/tasks/:listtask/labels" , labelTaskHandler . CreateWeb )
a . DELETE ( "/tasks/:listtask/labels/:label" , labelTaskHandler . DeleteWeb )
a . GET ( "/tasks/:listtask/labels" , labelTaskHandler . ReadAllWeb )
2019-01-10 00:08:12 +01:00
bulkLabelTaskHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . LabelTaskBulk { }
} ,
}
a . POST ( "/tasks/:listtask/labels/bulk" , bulkLabelTaskHandler . CreateWeb )
2019-09-25 20:44:41 +02:00
taskRelationHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . TaskRelation { }
} ,
}
a . PUT ( "/tasks/:task/relations" , taskRelationHandler . CreateWeb )
2021-02-18 23:35:20 +01:00
a . DELETE ( "/tasks/:task/relations/:relationKind/:otherTask" , taskRelationHandler . DeleteWeb )
2019-09-25 20:44:41 +02:00
2020-01-26 20:10:31 +01:00
if config . ServiceEnableTaskAttachments . GetBool ( ) {
taskAttachmentHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . TaskAttachment { }
} ,
}
a . GET ( "/tasks/:task/attachments" , taskAttachmentHandler . ReadAllWeb )
a . DELETE ( "/tasks/:task/attachments/:attachment" , taskAttachmentHandler . DeleteWeb )
a . PUT ( "/tasks/:task/attachments" , apiv1 . UploadTaskAttachment )
a . GET ( "/tasks/:task/attachments/:attachment" , apiv1 . GetTaskAttachment )
2019-10-16 22:52:29 +02:00
}
2020-02-19 22:57:56 +01:00
if config . ServiceEnableTaskComments . GetBool ( ) {
taskCommentHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . TaskComment { }
} ,
}
a . GET ( "/tasks/:task/comments" , taskCommentHandler . ReadAllWeb )
a . PUT ( "/tasks/:task/comments" , taskCommentHandler . CreateWeb )
a . DELETE ( "/tasks/:task/comments/:commentid" , taskCommentHandler . DeleteWeb )
a . POST ( "/tasks/:task/comments/:commentid" , taskCommentHandler . UpdateWeb )
a . GET ( "/tasks/:task/comments/:commentid" , taskCommentHandler . ReadOneWeb )
}
2018-12-31 02:18:41 +01:00
labelHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . Label { }
} ,
}
a . GET ( "/labels" , labelHandler . ReadAllWeb )
a . GET ( "/labels/:label" , labelHandler . ReadOneWeb )
a . PUT ( "/labels" , labelHandler . CreateWeb )
a . DELETE ( "/labels/:label" , labelHandler . DeleteWeb )
a . POST ( "/labels/:label" , labelHandler . UpdateWeb )
2018-12-01 00:26:56 +01:00
listTeamHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2018-10-11 17:53:59 +02:00
return & models . TeamList { }
} ,
2018-07-24 17:29:13 +02:00
}
a . GET ( "/lists/:list/teams" , listTeamHandler . ReadAllWeb )
a . PUT ( "/lists/:list/teams" , listTeamHandler . CreateWeb )
a . DELETE ( "/lists/:list/teams/:team" , listTeamHandler . DeleteWeb )
2018-09-19 08:08:41 +02:00
a . POST ( "/lists/:list/teams/:team" , listTeamHandler . UpdateWeb )
2018-07-24 17:29:13 +02:00
2018-12-01 00:26:56 +01:00
listUserHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2018-10-11 17:53:59 +02:00
return & models . ListUser { }
} ,
2018-08-30 08:58:09 +02:00
}
a . GET ( "/lists/:list/users" , listUserHandler . ReadAllWeb )
a . PUT ( "/lists/:list/users" , listUserHandler . CreateWeb )
a . DELETE ( "/lists/:list/users/:user" , listUserHandler . DeleteWeb )
2018-09-19 07:54:47 +02:00
a . POST ( "/lists/:list/users/:user" , listUserHandler . UpdateWeb )
2018-08-30 08:58:09 +02:00
2020-09-26 23:02:17 +02:00
savedFiltersHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . SavedFilter { }
} ,
}
a . GET ( "/filters/:filter" , savedFiltersHandler . ReadOneWeb )
a . PUT ( "/filters" , savedFiltersHandler . CreateWeb )
a . DELETE ( "/filters/:filter" , savedFiltersHandler . DeleteWeb )
a . POST ( "/filters/:filter" , savedFiltersHandler . UpdateWeb )
2018-12-01 00:26:56 +01:00
namespaceHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2018-10-11 17:53:59 +02:00
return & models . Namespace { }
} ,
2018-07-11 13:00:00 +02:00
}
a . GET ( "/namespaces" , namespaceHandler . ReadAllWeb )
2018-07-12 00:09:16 +02:00
a . PUT ( "/namespaces" , namespaceHandler . CreateWeb )
2018-07-21 15:08:46 +02:00
a . GET ( "/namespaces/:namespace" , namespaceHandler . ReadOneWeb )
a . POST ( "/namespaces/:namespace" , namespaceHandler . UpdateWeb )
a . DELETE ( "/namespaces/:namespace" , namespaceHandler . DeleteWeb )
a . GET ( "/namespaces/:namespace/lists" , apiv1 . GetListsByNamespaceID )
2018-07-14 17:34:59 +02:00
2018-12-01 00:26:56 +01:00
namespaceTeamHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2018-10-11 17:53:59 +02:00
return & models . TeamNamespace { }
} ,
2018-07-17 08:44:21 +02:00
}
2018-07-18 21:54:04 +02:00
a . GET ( "/namespaces/:namespace/teams" , namespaceTeamHandler . ReadAllWeb )
a . PUT ( "/namespaces/:namespace/teams" , namespaceTeamHandler . CreateWeb )
a . DELETE ( "/namespaces/:namespace/teams/:team" , namespaceTeamHandler . DeleteWeb )
2018-09-19 08:16:04 +02:00
a . POST ( "/namespaces/:namespace/teams/:team" , namespaceTeamHandler . UpdateWeb )
2018-07-17 08:44:21 +02:00
2018-12-01 00:26:56 +01:00
namespaceUserHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2018-10-11 17:53:59 +02:00
return & models . NamespaceUser { }
} ,
2018-09-04 20:15:24 +02:00
}
a . GET ( "/namespaces/:namespace/users" , namespaceUserHandler . ReadAllWeb )
a . PUT ( "/namespaces/:namespace/users" , namespaceUserHandler . CreateWeb )
a . DELETE ( "/namespaces/:namespace/users/:user" , namespaceUserHandler . DeleteWeb )
2018-09-19 08:03:39 +02:00
a . POST ( "/namespaces/:namespace/users/:user" , namespaceUserHandler . UpdateWeb )
2018-09-04 20:15:24 +02:00
2018-12-01 00:26:56 +01:00
teamHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2018-10-11 17:53:59 +02:00
return & models . Team { }
} ,
2018-07-14 17:34:59 +02:00
}
a . GET ( "/teams" , teamHandler . ReadAllWeb )
2018-07-21 15:08:46 +02:00
a . GET ( "/teams/:team" , teamHandler . ReadOneWeb )
2018-07-14 17:34:59 +02:00
a . PUT ( "/teams" , teamHandler . CreateWeb )
2018-07-21 15:08:46 +02:00
a . POST ( "/teams/:team" , teamHandler . UpdateWeb )
a . DELETE ( "/teams/:team" , teamHandler . DeleteWeb )
2018-07-26 09:53:32 +02:00
2018-12-01 00:26:56 +01:00
teamMemberHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
2018-10-11 17:53:59 +02:00
return & models . TeamMember { }
} ,
2018-07-26 09:53:32 +02:00
}
a . PUT ( "/teams/:team/members" , teamMemberHandler . CreateWeb )
a . DELETE ( "/teams/:team/members/:user" , teamMemberHandler . DeleteWeb )
2020-08-05 17:21:17 +02:00
a . POST ( "/teams/:team/members/:user/admin" , teamMemberHandler . UpdateWeb )
2020-01-19 17:52:16 +01:00
2021-02-14 20:18:14 +01:00
// Subscriptions
subscriptionHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . Subscription { }
} ,
}
a . PUT ( "/subscriptions/:entity/:entityID" , subscriptionHandler . CreateWeb )
a . DELETE ( "/subscriptions/:entity/:entityID" , subscriptionHandler . DeleteWeb )
2021-02-21 15:50:34 +01:00
// Notifications
notificationHandler := & handler . WebHandler {
EmptyStruct : func ( ) handler . CObject {
return & models . DatabaseNotifications { }
} ,
}
a . GET ( "/notifications" , notificationHandler . ReadAllWeb )
a . POST ( "/notifications/:notificationid" , notificationHandler . UpdateWeb )
2020-01-19 17:52:16 +01:00
// Migrations
m := a . Group ( "/migration" )
2021-09-04 21:26:31 +02:00
registerMigrations ( m )
2020-01-19 17:52:16 +01:00
2021-09-04 21:26:31 +02:00
// List Backgrounds
if config . BackgroundsEnabled . GetBool ( ) {
a . GET ( "/lists/:list/background" , backgroundHandler . GetListBackground )
a . DELETE ( "/lists/:list/background" , backgroundHandler . RemoveListBackground )
if config . BackgroundsUploadEnabled . GetBool ( ) {
uploadBackgroundProvider := & backgroundHandler . BackgroundProvider {
Provider : func ( ) background . Provider {
return & upload . Provider { }
} ,
}
a . PUT ( "/lists/:list/backgrounds/upload" , uploadBackgroundProvider . UploadBackground )
}
if config . BackgroundsUnsplashEnabled . GetBool ( ) {
unsplashBackgroundProvider := & backgroundHandler . BackgroundProvider {
Provider : func ( ) background . Provider {
return & unsplash . Provider { }
} ,
}
a . GET ( "/backgrounds/unsplash/search" , unsplashBackgroundProvider . SearchBackgrounds )
a . POST ( "/lists/:list/backgrounds/unsplash" , unsplashBackgroundProvider . SetBackground )
a . GET ( "/backgrounds/unsplash/images/:image/thumb" , unsplash . ProxyUnsplashThumb )
a . GET ( "/backgrounds/unsplash/images/:image" , unsplash . ProxyUnsplashImage )
}
}
}
func registerMigrations ( m * echo . Group ) {
2020-01-19 17:52:16 +01:00
// Wunderlist
if config . MigrationWunderlistEnable . GetBool ( ) {
wunderlistMigrationHandler := & migrationHandler . MigrationWeb {
MigrationStruct : func ( ) migration . Migrator {
return & wunderlist . Migration { }
} ,
}
2020-01-20 20:48:46 +01:00
wunderlistMigrationHandler . RegisterRoutes ( m )
2020-01-19 17:52:16 +01:00
}
2020-05-23 22:50:54 +02:00
// Todoist
if config . MigrationTodoistEnable . GetBool ( ) {
todoistMigrationHandler := & migrationHandler . MigrationWeb {
MigrationStruct : func ( ) migration . Migrator {
return & todoist . Migration { }
} ,
}
todoistMigrationHandler . RegisterRoutes ( m )
}
2020-05-26 22:07:55 +02:00
2020-12-17 14:44:04 +01:00
// Trello
if config . MigrationTrelloEnable . GetBool ( ) {
trelloMigrationHandler := & migrationHandler . MigrationWeb {
MigrationStruct : func ( ) migration . Migrator {
return & trello . Migration { }
} ,
}
trelloMigrationHandler . RegisterRoutes ( m )
}
2020-12-18 12:12:05 +01:00
// Microsoft Todo
if config . MigrationMicrosoftTodoEnable . GetBool ( ) {
microsoftTodoMigrationHandler := & migrationHandler . MigrationWeb {
MigrationStruct : func ( ) migration . Migrator {
return & microsofttodo . Migration { }
} ,
}
microsoftTodoMigrationHandler . RegisterRoutes ( m )
}
2021-09-04 21:26:31 +02:00
vikunjaFileMigrationHandler := & migrationHandler . FileMigratorWeb {
MigrationStruct : func ( ) migration . FileMigrator {
return & vikunja_file . FileMigrator { }
} ,
2020-05-26 22:07:55 +02:00
}
2021-09-04 21:26:31 +02:00
vikunjaFileMigrationHandler . RegisterRoutes ( m )
2018-06-10 11:11:41 +02:00
}
2019-05-22 19:48:48 +02:00
func registerCalDavRoutes ( c * echo . Group ) {
// Basic auth middleware
c . Use ( middleware . BasicAuth ( caldavBasicAuth ) )
// THIS is the entry point for caldav clients, otherwise lists will show up double
c . Any ( "" , caldav . EntryHandler )
c . Any ( "/" , caldav . EntryHandler )
c . Any ( "/principals/*/" , caldav . PrincipalHandler )
c . Any ( "/lists" , caldav . ListHandler )
c . Any ( "/lists/" , caldav . ListHandler )
c . Any ( "/lists/:list" , caldav . ListHandler )
c . Any ( "/lists/:list/" , caldav . ListHandler )
c . Any ( "/lists/:list/:task" , caldav . TaskHandler ) // Mostly used for editing
}
func caldavBasicAuth ( username , password string , c echo . Context ) ( bool , error ) {
2020-01-26 18:08:06 +01:00
creds := & user . Login {
2019-05-22 19:48:48 +02:00
Username : username ,
Password : password ,
}
2020-12-23 16:32:28 +01:00
s := db . NewSession ( )
defer s . Close ( )
u , err := user . CheckUserCredentials ( s , creds )
2019-05-22 19:48:48 +02:00
if err != nil {
2020-12-23 16:32:28 +01:00
_ = s . Rollback ( )
2019-07-20 20:12:10 +02:00
log . Errorf ( "Error during basic auth for caldav: %v" , err )
2019-05-22 19:48:48 +02:00
return false , nil
}
2020-12-23 16:32:28 +01:00
if err := s . Commit ( ) ; err != nil {
return false , err
}
2019-05-22 19:48:48 +02:00
// Save the user in echo context for later use
c . Set ( "userBasicAuth" , u )
return true , nil
}