Skip to content
This repository was archived by the owner on May 30, 2024. It is now read-only.

Refactoring the event API #415

Merged
merged 6 commits into from
Apr 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 46 additions & 78 deletions internal/server/api/v1/stream/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@
// Use of this source code is governed by the Gitploy Non-Commercial License
// that can be found in the LICENSE file.

// +build !oss
//go:build !oss

package stream

import (
"context"
"fmt"
"math/rand"
"time"

"github.com/gin-contrib/sse"
Expand All @@ -19,7 +16,6 @@ import (
gb "github.com/gitploy-io/gitploy/internal/server/global"
"github.com/gitploy-io/gitploy/model/ent"
"github.com/gitploy-io/gitploy/model/ent/event"
"github.com/gitploy-io/gitploy/pkg/e"
)

// GetEvents streams events of deployment, or review.
Expand All @@ -29,30 +25,56 @@ func (s *Stream) GetEvents(c *gin.Context) {
v, _ := c.Get(gb.KeyUser)
u, _ := v.(*ent.User)

debugID := randstr()

events := make(chan *ent.Event, 10)
events := make(chan *sse.Event, 10)

// Subscribe events
// it'll unsubscribe after the connection is closed.
sub := func(e *ent.Event) {

// Deleted type is always propagated to all.
if e.Type == event.TypeDeleted {
events <- e
return
}

if ok, err := s.hasPermForEvent(ctx, u, e); err != nil {
s.log.Error("It has failed to check the perm.", zap.Error(err))
return
} else if !ok {
s.log.Debug("Skip the event. The user has not the perm.")
return
switch e.Kind {
case event.KindDeployment:
d, err := s.i.FindDeploymentByID(ctx, e.DeploymentID)
if err != nil {
s.log.Error("Failed to find the deployment.", zap.Error(err))
return
}

if _, err := s.i.FindPermOfRepo(ctx, d.Edges.Repo, u); err != nil {
s.log.Debug("Skip the event. The permission is denied.")
return
}

s.log.Debug("Dispatch a deployment event.", zap.Int("id", d.ID))
events <- &sse.Event{
Event: "deployment",
Data: d,
}

case event.KindReview:
r, err := s.i.FindReviewByID(ctx, e.ReviewID)
if err != nil {
s.log.Error("Failed to find the review.", zap.Error(err))
return
}

d, err := s.i.FindDeploymentByID(ctx, r.DeploymentID)
if err != nil {
s.log.Error("Failed to find the deployment.", zap.Error(err))
return
}

if _, err := s.i.FindPermOfRepo(ctx, d.Edges.Repo, u); err != nil {
s.log.Debug("Skip the event. The permission is denied.")
return
}

s.log.Debug("Dispatch a review event.", zap.Int("id", r.ID))
events <- &sse.Event{
Event: "review",
Data: r,
}
}

events <- e
}

if err := s.i.SubscribeEvent(sub); err != nil {
s.log.Check(gb.GetZapLogLevel(err), "Failed to subscribe notification events").Write(zap.Error(err))
gb.ResponseWithError(c, err)
Expand Down Expand Up @@ -83,62 +105,8 @@ L:
})
w.Flush()
case e := <-events:
c.Render(-1, sse.Event{
Event: "event",
Data: e,
})
c.Render(-1, e)
w.Flush()
s.log.Debug("server sent event.", zap.Int("event_id", e.ID), zap.String("debug_id", debugID))
}
}
}

// hasPermForEvent checks the user has permission for the event.
func (s *Stream) hasPermForEvent(ctx context.Context, u *ent.User, evt *ent.Event) (bool, error) {
if evt.Kind == event.KindDeployment {
d, err := s.i.FindDeploymentByID(ctx, evt.DeploymentID)
if err != nil {
return false, err
}

if _, err = s.i.FindPermOfRepo(ctx, d.Edges.Repo, u); e.HasErrorCode(err, e.ErrorCodeEntityNotFound) {
return false, nil
} else if err != nil {
return false, err
}

return true, nil
}

if evt.Kind == event.KindReview {
rv, err := s.i.FindReviewByID(ctx, evt.ReviewID)
if err != nil {
return false, err
}

d, err := s.i.FindDeploymentByID(ctx, rv.DeploymentID)
if err != nil {
return false, err
}

if _, err = s.i.FindPermOfRepo(ctx, d.Edges.Repo, u); e.HasErrorCode(err, e.ErrorCodeEntityNotFound) {
return false, nil
} else if err != nil {
return false, err
}

return true, nil
}

return false, fmt.Errorf("The type of event is not \"deployment\" or \"review\".")
}

func randstr() string {
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

b := make([]rune, 4)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}
40 changes: 6 additions & 34 deletions openapi/v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1436,41 +1436,13 @@ paths:
type: integer
event:
type: string
enum:
- deployment
- review
data:
type: object
format: json
required:
- id
- kind
- type
- created_at
- edges
properties:
id:
type: integer
kind:
type: string
enum:
- deployment
- review
type:
type: string
enum:
- created
- updated
- deleted
created_at:
type: string
deleted_id:
type: integer
description: The ID of deleted resource.
edges:
type: object
properties:
deployment:
$ref: '#/components/schemas/Deployment'
review:
$ref: '#/components/schemas/Review'
oneOf:
- $ref: '#/components/schemas/Deployment'
- $ref: '#/components/schemas/Review'
/sync:
post:
tags:
Expand Down
80 changes: 18 additions & 62 deletions ui/src/apis/events.ts
Original file line number Diff line number Diff line change
@@ -1,80 +1,36 @@
import { instance } from './setting'

import { DeploymentData, mapDataToDeployment } from "./deployment"
import { ReviewData, mapDataToReview } from "./review"
import { Deployment, Review, Event, EventKindEnum, EventTypeEnum } from "../models"
import { mapDataToDeployment } from "./deployment"
import { mapDataToReview } from "./review"
import { Deployment, Review } from "../models"

interface EventData {
id: number
kind: string
type: string
deleted_id: number
edges: {
deployment?: DeploymentData
review?: ReviewData
}
}

const mapDataToEvent = (data: EventData): Event => {
let kind: EventKindEnum
let type: EventTypeEnum
let deployment: Deployment | undefined
let review: Review | undefined

switch (data.kind) {
case "deployment":
kind = EventKindEnum.Deployment
break
case "review":
kind = EventKindEnum.Review
break
default:
kind = EventKindEnum.Deployment
}

switch (data.type) {
case "created":
type = EventTypeEnum.Created
break
case "updated":
type = EventTypeEnum.Updated
break
case "deleted":
type = EventTypeEnum.Deleted
break
default:
type = EventTypeEnum.Created
}
export const subscribeDeploymentEvents = (cb: (deployment: Deployment) => void): EventSource => {
const sse = new EventSource(`${instance}/api/v1/stream/events`, {
withCredentials: true,
})

if (data.edges.deployment) {
deployment = mapDataToDeployment(data.edges.deployment)
}
sse.addEventListener("deployment", (e: any) => {
const data = JSON.parse(e.data)
const deployment = mapDataToDeployment(data)

if (data.edges.review) {
review = mapDataToReview(data.edges.review)
}
cb(deployment)
})

return {
id: data.id,
kind,
type,
deletedId: data.deleted_id,
deployment,
review
}
return sse
}

export const subscribeEvents = (cb: (event: Event) => void): EventSource => {
export const subscribeReviewEvents = (cb: (review: Review) => void): EventSource => {
const sse = new EventSource(`${instance}/api/v1/stream/events`, {
withCredentials: true,
})

sse.addEventListener("event", (e: any) => {
sse.addEventListener("review", (e: any) => {
const data = JSON.parse(e.data)
const event = mapDataToEvent(data)
const review = mapDataToReview(data)

cb(event)
cb(review)
})

return sse
}
}
Loading