You are an expert in writing code in many different languages. Your goal is to perform code completion for the following code, keeping in mind the rest of the code and the file meta data.
    Metadata details:
    filename: session_oauth.go
    path of file: ./repos/photoprism/internal/api
    
    the code of the file until where you have to start completion: package api

import (
	"net/http"

	"github.com/dustin/go-humanize/english"
	"github.com/gin-gonic/gin"

	"github.com/photoprism/photoprism/internal/entity"
	"github.com/photoprism/photoprism/internal/event"
	"github.com/photoprism/photoprism/internal/form"
	"github.com/photoprism/photoprism/internal/get"
	"github.com/photoprism/photoprism/internal/i18n"
	"github.com/photoprism/photoprism/internal/server/limiter"
	"github.com/photoprism/photoprism/pkg/authn"
	"github.com/photoprism/photoprism/pkg/clean"
	"github.com/photoprism/photoprism/pkg/header"
	"github.com/photoprism/photoprism/pkg/rnd"
)

// CreateOAuthToken creates a new access token for clients that
// authenticate with valid OAuth2 client credentials.
//
// POST /api/v1/oauth/token
func CreateOAuthToken(router *gin.RouterGroup) {
	router.POST("/oauth/token", func(c *gin.Context) {
		// Prevent CDNs from caching this endpoint.
		if header.IsCdn(c.Request) {
			AbortNotFound(c)
			return
		}

		// Get client IP address for logs and rate limiting checks.
		clientIp := ClientIP(c)

		if get.Config().Public() {
			// Abort if running in public mode.
			event.AuditErr([]string{clientIp, "client", "create 
    
func RevokeOAuthToken(router *gin.RouterGroup) {
	router.POST("/oauth/revoke", func(c *gin.Context) {
		// Prevent CDNs from caching this endpoint.
		if header.IsCdn(c.Request) {
			AbortNotFound(c)
			return
		}

		// Get client IP address for logs and rate limiting checks.
		clientIp := ClientIP(c)

		// Abort if running in public mode.
		if get.Config().Public() {
			event.AuditErr([]string{clientIp, "client", "delete session", "oauth2", "disabled in public mode"})
			Abort(c, http.StatusForbidden, i18n.ErrForbidden)
			return
		}

		var err error

		// Token revocation request data.
		var f form.ClientToken

		authToken := AuthToken(c)

		// Get the auth token to be revoked from the submitted form values or the request header.
		if err = c.ShouldBind(&f); err != nil && authToken == "" {
			event.AuditWarn([]string{clientIp, "client", "delete session", "oauth2", "%s"}, err)
			AbortBadRequest(c)
			return
		} else if f.Empty() {
			f.AuthToken = authToken
			f.TypeHint = form.ClientAccessToken
		}

		// Check the token form values.
		if err = f.Validate(); err != nil {
			event.AuditWarn([]string{clientIp, "client", "delete session", "oauth2", "%s"}, err)
			AbortBadRequest(c)
			return
		}

		// Disable caching of responses.
		c.Header(header.CacheControl, header.CacheControlNoStore)

		// Find session based on auth token.
		sess, err := entity.FindSession(rnd.SessionID(f.AuthToken))

		if err != nil {
			event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", "%s"}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String(), err.Error())
			c.AbortWithStatusJSON(http.StatusUnauthorized, i18n.NewResponse(http.StatusUnauthorized, i18n.ErrUnauthorized))
			return
		} else if sess == nil {
			event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", "denied"}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String())
			c.AbortWithStatusJSON(http.StatusUnauthorized, i18n.NewResponse(http.StatusUnauthorized, i18n.ErrUnauthorized))
			return
		} else if sess.Abort(c) {
			event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", "denied"}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String())
			return
		} else if !sess.IsClient() {
			event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", "denied"}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String())
			c.AbortWithStatusJSON(http.StatusForbidden, i18n.NewResponse(http.StatusForbidden, i18n.ErrForbidden))
			return
		} else {
			event.AuditInfo([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", "granted"}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String())
		}

		// Delete session cache and database record.
		if err = sess.Delete(); err != nil {
			// Log error.
			event.AuditErr([]string{clientIp, "client %s", "session %s", "delete session as %s", "oauth2", "%s"}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID), sess.ClientRole().String(), err)

			// Return JSON error.
			c.AbortWithStatusJSON(http.StatusNotFound, i18n.NewResponse(http.StatusNotFound, i18n.ErrNotFound))
			return
		}

		// Log event.
		event.AuditInfo([]string{clientIp, "client %s", "session %s", "oauth2", "deleted"}, clean.Log(sess.ClientInfo()), clean.Log(sess.RefID))

		// Return JSON response for confirmation.
		c.JSON(http.StatusOK, DeleteSessionResponse(sess.ID))
	})
}