Skip to content

Commit

Permalink
feature: Implemented cron system
Browse files Browse the repository at this point in the history
  • Loading branch information
zekiahmetbayar committed Nov 6, 2023
1 parent 3bd51fe commit 8053fb8
Show file tree
Hide file tree
Showing 5 changed files with 272 additions and 0 deletions.
28 changes: 28 additions & 0 deletions app/handlers/cron_jobs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package handlers

import (
"github.com/gofiber/fiber/v2"
"github.com/limanmys/render-engine/app/models"
"github.com/limanmys/render-engine/internal/database"
"github.com/limanmys/render-engine/pkg/cron_jobs"
)

func Create(c *fiber.Ctx) error {
// Parse payload
var payload models.CronJob
if err := c.BodyParser(&payload); err != nil {
return err
}

// Create cronjob rule on db
if err := database.Connection().Model(&models.CronJob{}).Create(&payload).Error; err != nil {
return err
}

// Register and run cronjob
if err := cron_jobs.RegisterAndRun(&payload); err != nil {
return err
}

return c.JSON("Cronjob registered successfully.")
}
59 changes: 59 additions & 0 deletions app/models/cron_jobs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package models

import (
"time"

"github.com/google/uuid"
"github.com/limanmys/render-engine/internal/database"
"gorm.io/gorm"
)

type CronJob struct {
ID *uuid.UUID `json:"id" gorm:"primary_key,type:uuid"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`

ExtensionID *uuid.UUID `json:"extension_id"`
UserID *uuid.UUID `json:"user_id"`
ServerID *uuid.UUID `json:"server_id"`
BaseURL string `json:"base_url"`

Payload string `json:"payload"`
Day int `json:"day"`
Time string `json:"time"`

Message string `json:"message"` // Last run message
Status Status `json:"status"` // Last run status
}

func NewCronJob() *CronJob {
id := uuid.New()

return &CronJob{
ID: &id,
Message: "Pending.",
Status: StatusPending,
}
}

func (cj *CronJob) UpdateAsFailed(message string) {
cj.Status = StatusFailed
cj.Message = message

database.Connection().Model(cj).Save(cj)
}

func (cj *CronJob) UpdateAsProcessing() {
cj.Status = StatusProcessing
cj.Message = "Cronjob processing."

database.Connection().Model(cj).Save(cj)
}

func (cj *CronJob) UpdateAsDone() {
cj.Status = StatusPending
cj.Message = "CronJob completed successfully. Waiting for next run."

database.Connection().Model(cj).Save(cj)
}
12 changes: 12 additions & 0 deletions internal/constants/index.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package constants

import (
"time"

"github.com/go-co-op/gocron"
)

const (
LIMAN_PATH = "/liman"
CORE_PATH = LIMAN_PATH + "/server"
Expand All @@ -12,3 +18,9 @@ const (
KEYS_PATH = LIMAN_PATH + "/keys"
CERT_PATH = LIMAN_PATH + "/certs"
)

var (
location, _ = time.LoadLocation("Europe/Istanbul")

GLOBAL_SCHEDULER = gocron.NewScheduler(location)
)
63 changes: 63 additions & 0 deletions internal/user_token/user_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package user_token

import (
"time"

"github.com/google/uuid"
"github.com/limanmys/render-engine/app/models"
"github.com/limanmys/render-engine/internal/database"
gorandom "github.com/zekiahmetbayar/go-random"
)

// Create a new token or retrieve old one
func Create(user_id string) (string, error) {
// Search token on database
var token models.Token
database.Connection().Model(&models.Token{}).Where("user_id = ?", user_id).First(&token)

// If token does not exists, create token
if token.ID == "" {
// Create new id for token
uid := uuid.New()
// Generate token
token := generate()
// Create token on database
if err := database.Connection().Model(&models.Token{}).Create(models.Token{
ID: uid.String(),
CreatedAt: time.Now().Format(time.RFC3339),
UpdatedAt: time.Now().Format(time.RFC3339),
UserID: user_id,
Token: token,
}).Error; err != nil {
return "", err
}

return token, nil
}
// Get token update date
updateDate, err := time.Parse("02-01-2006 15:04:05", token.UpdatedAt)
if err != nil {
return "", err
}
// If token updated after 6 hours
if time.Since(updateDate).Hours() > 6 {
// TODO: Update token
token_str := generate()
if err := database.Connection().Model(&token).Update("token", token_str).Error; err != nil {
return "", err
}
return token_str, nil
}

return token.Token, nil
}

// Generate a new token
func generate() string {
token, err := gorandom.String(false, true, false, 32)
if err != nil {
return ""
}

return token
}
110 changes: 110 additions & 0 deletions pkg/cron_jobs/cron_jobs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package cron_jobs

import (
"encoding/json"
"time"

"github.com/google/uuid"
"github.com/limanmys/render-engine/app/models"
"github.com/limanmys/render-engine/internal/constants"
"github.com/limanmys/render-engine/internal/liman"
"github.com/limanmys/render-engine/internal/sandbox"
"github.com/limanmys/render-engine/internal/user_token"
"github.com/limanmys/render-engine/pkg/helpers"
"github.com/limanmys/render-engine/pkg/linux"
)

func RegisterAndRun(cj *models.CronJob) error {
_, err := constants.GLOBAL_SCHEDULER.Tag(cj.ID.String()).Every(1).Week().Weekday(time.Weekday(cj.Day)).At(cj.Time).Do(func() {
// Update cronjob as processing
cj.UpdateAsProcessing()
// Get extension
extension, err := liman.GetExtension(&models.Extension{
ID: cj.ExtensionID.String(),
})
if err != nil {
// Update job as failed
cj.UpdateAsFailed(err.Error())
return
}
// Check extension status
if extension.Status == "0" {
// Update job as failed
cj.UpdateAsFailed("extension is unavailable")
return
}

// Get credentials
credentials := &models.Credentials{}
if extension.RequireKey == "true" {
credentials, err = liman.GetCredentials(
&models.User{
ID: cj.UserID.String(),
},
&models.Server{
ID: cj.ServerID.String(),
},
)
// Check errors and username is valid
if err != nil || len(credentials.Username) < 1 {
// Update job as failed
cj.UpdateAsFailed("you need a key to use this extension")
return
}
}

// Parse payload
marshalledPayload, err := json.Marshal(cj.Payload)
if err != nil {
cj.UpdateAsFailed(err.Error())
return
}
// Set form values as map
formValues := make(map[string]string)
// Unmarshal payload to map
if err := json.Unmarshal(marshalledPayload, &formValues); err != nil {
cj.UpdateAsFailed(err.Error())
return
}

// Generate token for user
token, err := user_token.Create(cj.UserID.String())
if err != nil {
cj.UpdateAsFailed(err.Error())
return
}

// Generate new id for logs
log_id := uuid.New()

// Generate command
command, err := sandbox.GenerateCommand(
extension,
credentials,
&models.CommandParams{
TargetFunction: "",
Locale: helpers.Env("APP_LANG", "tr"),
Extension: cj.ExtensionID.String(),
Server: cj.ServerID.String(),
User: cj.UserID.String(),
LogID: log_id.String(),
RequestData: formValues,
BaseURL: cj.BaseURL,
Token: token,
},
)
if err != nil {
// Update job as failed
cj.UpdateAsFailed(err.Error())

return
}

linux.Execute(command)
})
if err != nil {
return err
}

return nil
}

0 comments on commit 8053fb8

Please sign in to comment.