mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-02-12 16:12:44 +00:00
Initial open source release
This commit is contained in:
33
backend/database/allowDeny.go
Normal file
33
backend/database/allowDeny.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
ALLOW_DENY_TABLE = "allow_denies"
|
||||
)
|
||||
|
||||
// AllowDeny is a gorm data model for allow deny listing
|
||||
type AllowDeny struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index"`
|
||||
CompanyID *uuid.UUID `gorm:"uniqueIndex:idx_allow_denies_unique_name_and_company_id;type:uuid"`
|
||||
Name string `gorm:"not null;uniqueIndex:idx_allow_denies_unique_name_and_company_id;"`
|
||||
Cidrs string `gorm:"not null;"`
|
||||
Allowed bool `gorm:"not null;"`
|
||||
}
|
||||
|
||||
func (AllowDeny) TableName() string {
|
||||
return ALLOW_DENY_TABLE
|
||||
}
|
||||
|
||||
func (e *AllowDeny) Migrate(db *gorm.DB) error {
|
||||
// SQLITE
|
||||
// ensure name + company id is unique
|
||||
return UniqueIndexNameAndNullCompanyID(db, "allow_denies")
|
||||
}
|
||||
48
backend/database/apiSender.go
Normal file
48
backend/database/apiSender.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
API_SENDER_TABLE = "api_senders"
|
||||
)
|
||||
|
||||
type APISender struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index"`
|
||||
Name string `gorm:"not null;uniqueIndex:idx_api_senders_name_company_id;"`
|
||||
CompanyID *uuid.UUID `gorm:"uniqueIndex:idx_api_senders_name_company_id;type:uuid"`
|
||||
|
||||
// Extra fields
|
||||
APIKey string
|
||||
CustomField1 string
|
||||
CustomField2 string
|
||||
CustomField3 string
|
||||
CustomField4 string
|
||||
|
||||
// Request fields
|
||||
RequestMethod string
|
||||
RequestURL string
|
||||
RequestHeaders string
|
||||
RequestBody string
|
||||
|
||||
// Response fields
|
||||
ExpectedResponseStatusCode int
|
||||
ExpectedResponseHeaders string
|
||||
ExpectedResponseBody string
|
||||
}
|
||||
|
||||
func (e *APISender) Migrate(db *gorm.DB) error {
|
||||
// SQLITE
|
||||
// ensure name + null company id is unique
|
||||
return UniqueIndexNameAndNullCompanyID(db, "api_senders")
|
||||
}
|
||||
|
||||
func (APISender) TableName() string {
|
||||
return API_SENDER_TABLE
|
||||
}
|
||||
26
backend/database/apiSenderHeader.go
Normal file
26
backend/database/apiSenderHeader.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type APISenderHeader struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index"`
|
||||
|
||||
Key string `gorm:"not null;"`
|
||||
Value string `gorm:"not null;"`
|
||||
// IsRequestHeader is true if the header is a request header
|
||||
// and false if it is a expected response header
|
||||
IsRequestHeader bool `gorm:"not null;"`
|
||||
|
||||
// belongs to
|
||||
APISenderID *uuid.UUID `gorm:"index;not null;type:uuid"`
|
||||
}
|
||||
|
||||
func (APISenderHeader) TableName() string {
|
||||
return "api_sender_headers"
|
||||
}
|
||||
33
backend/database/asset.go
Normal file
33
backend/database/asset.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
ASSET_TABLE = "assets"
|
||||
)
|
||||
|
||||
// Asset is gorm data model
|
||||
type Asset struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index;"`
|
||||
|
||||
// has one
|
||||
DomainID *uuid.UUID `gorm:"index;type:uuid;"`
|
||||
DomainName string
|
||||
|
||||
// can has one
|
||||
CompanyID *uuid.UUID `gorm:"index;type:uuid;"`
|
||||
|
||||
Name string `gorm:";index"`
|
||||
Description string `gorm:";"`
|
||||
Path string `gorm:"not null;index"`
|
||||
}
|
||||
|
||||
func (Asset) TableName() string {
|
||||
return ASSET_TABLE
|
||||
}
|
||||
33
backend/database/attachment.go
Normal file
33
backend/database/attachment.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
ATTACHMENT_TABLE = "attachments"
|
||||
)
|
||||
|
||||
// Attachment is gorm data model
|
||||
type Attachment struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index;"`
|
||||
|
||||
// can has one
|
||||
CompanyID *uuid.UUID `gorm:"index;type:uuid;"`
|
||||
|
||||
// many to many
|
||||
Mails []Email `gorm:"many2many:message_attachments;"`
|
||||
|
||||
Name string `gorm:";index"`
|
||||
Description string `gorm:";"`
|
||||
Filename string `gorm:"not null;index"`
|
||||
EmbeddedContent bool `gorm:"not null;default:false;index"`
|
||||
}
|
||||
|
||||
func (Attachment) TableName() string {
|
||||
return ATTACHMENT_TABLE
|
||||
}
|
||||
76
backend/database/campaign.go
Normal file
76
backend/database/campaign.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
CAMPAIGN_TABLE = "campaigns"
|
||||
)
|
||||
|
||||
// Campaign is gorm data model
|
||||
type Campaign struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index;"`
|
||||
|
||||
CloseAt *time.Time `gorm:"index;"`
|
||||
ClosedAt *time.Time `gorm:"index;"`
|
||||
AnonymizeAt *time.Time `gorm:"index;"`
|
||||
AnonymizedAt *time.Time `gorm:"index;"`
|
||||
SortField string `gorm:";"`
|
||||
SortOrder string `gorm:";"` // 'asc,desc,random'
|
||||
SendStartAt *time.Time `gorm:"index;"`
|
||||
SendEndAt *time.Time `gorm:"index;"`
|
||||
|
||||
// ConstraintWeekDays is a binary format.
|
||||
// 0b00000001 = 1 = sunday
|
||||
// 0b00000010 = 2 = monday
|
||||
// 0b00000100 = 4 = tuesday
|
||||
// 0b00001000 = 8 = ...
|
||||
// 0b00010000 = 16 =
|
||||
// 0b00100000 = 32 =
|
||||
// 0b01000000 = 64 =
|
||||
ConstraintWeekDays *int `gorm:";"`
|
||||
// hh:mm
|
||||
ConstraintStartTime *string `gorm:"index;"`
|
||||
// hh:mm
|
||||
ConstraintEndTime *string `gorm:"index;"`
|
||||
SaveSubmittedData bool `gorm:"not null;default:false"`
|
||||
IsAnonymous bool `gorm:"not null;default:false"`
|
||||
IsTest bool `gorm:"not null;default:false"`
|
||||
|
||||
// has one
|
||||
CampaignTemplateID *uuid.UUID `gorm:"index;type:uuid;"`
|
||||
CampaignTemplate *CampaignTemplate
|
||||
|
||||
// can has one
|
||||
CompanyID *uuid.UUID `gorm:"index;type:uuid;index;uniqueIndex:idx_campaigns_unique_name_and_company_id;"`
|
||||
Company *Company
|
||||
DenyPageID *uuid.UUID `gorm:"type:uuid;index;"`
|
||||
DenyPage *Page `gorm:"foreignKey:DenyPageID;references:ID"`
|
||||
// NotableEventID notable event for this campaign
|
||||
NotableEvent *Event `gorm:"foreignKey:NotableEventID;references:ID"`
|
||||
NotableEventID *uuid.UUID `gorm:"type:uuid;index"`
|
||||
|
||||
WebhookID *uuid.UUID `gorm:"type:uuid;index;"`
|
||||
|
||||
// has many-to-many
|
||||
RecipientGroups []*RecipientGroup `gorm:"many2many:campaign_recipient_groups"`
|
||||
AllowDeny []*AllowDeny `gorm:"many2many:campaign_allow_denies"`
|
||||
|
||||
Name string `gorm:"not null;uniqueIndex:idx_campaigns_unique_name_and_company_id"`
|
||||
}
|
||||
|
||||
func (c *Campaign) Migrate(db *gorm.DB) error {
|
||||
// SQLITE
|
||||
// ensure name + company id is unique
|
||||
return UniqueIndexNameAndNullCompanyID(db, "campaigns")
|
||||
}
|
||||
|
||||
func (Campaign) TableName() string {
|
||||
return CAMPAIGN_TABLE
|
||||
}
|
||||
22
backend/database/campaignAllowDeny.go
Normal file
22
backend/database/campaignAllowDeny.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
CAMPAIGN_ALLOW_DENY_TABLE = "campaign_allow_denies"
|
||||
)
|
||||
|
||||
// CampaignAllowDeny is a gorm data model
|
||||
// is a table of those allow deny lists that belong to a campaign
|
||||
type CampaignAllowDeny struct {
|
||||
CampaignID *uuid.UUID `gorm:"not null;index;type:uuid;uniqueIndex:idx_campaign_allow_denies;"`
|
||||
Campaign *Campaign
|
||||
AllowDenyID *uuid.UUID `gorm:"not null;index;type:uuid;uniqueIndex:idx_campaign_allow_denies;"`
|
||||
AllowDeny *AllowDeny
|
||||
}
|
||||
|
||||
func (CampaignAllowDeny) TableName() string {
|
||||
return CAMPAIGN_ALLOW_DENY_TABLE
|
||||
}
|
||||
52
backend/database/campaignEvent.go
Normal file
52
backend/database/campaignEvent.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
CAMPAIGN_EVENT_TABLE = "campaign_events"
|
||||
)
|
||||
|
||||
// Campaign is gorm data model
|
||||
type CampaignEvent struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;"`
|
||||
|
||||
// arbitrary data
|
||||
Data string `gorm:"not null;"`
|
||||
|
||||
// has one
|
||||
CampaignID *uuid.UUID `gorm:"not null;index;type:uuid;"`
|
||||
EventID *uuid.UUID `gorm:"not null;index;type:uuid;"`
|
||||
|
||||
// can has one
|
||||
UserAgent string `gorm:";"`
|
||||
IPAddress string `gorm:";"`
|
||||
|
||||
// AnonymizedID is set when the recipient has been anonymized
|
||||
AnonymizedID *uuid.UUID `gorm:"type:uuid;index;"`
|
||||
// if null either the event has no recipient or the recipient has been anonymized
|
||||
RecipientID *uuid.UUID `gorm:"index;type:uuid;"`
|
||||
Recipient *Recipient
|
||||
|
||||
CompanyID *uuid.UUID `gorm:"index;type:uuid;index;"`
|
||||
}
|
||||
|
||||
// RecipientCampaignEvent is a aggregated read-only model
|
||||
type RecipientCampaignEvent struct {
|
||||
CampaignEvent
|
||||
|
||||
Name string // event name
|
||||
CampaignName string
|
||||
}
|
||||
|
||||
func (CampaignEvent) TableName() string {
|
||||
return CAMPAIGN_EVENT_TABLE
|
||||
}
|
||||
|
||||
var _ = reflect.TypeOf(RecipientCampaignEvent{})
|
||||
52
backend/database/campaignRecipient.go
Normal file
52
backend/database/campaignRecipient.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
CAMPAIGN_RECIPIENT_TABLE_NAME = "campaign_recipients"
|
||||
)
|
||||
|
||||
// CampaigReciever is gorm data model
|
||||
// this model/table is primarily used to keep track of who and when should recieve a campaign
|
||||
type CampaignRecipient struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index;"`
|
||||
|
||||
Campaign *Campaign
|
||||
CampaignID *uuid.UUID `gorm:"not null;type:uuid;uniqueIndex:idx_campaign_recipients_campaign_id_recipient_id;"`
|
||||
|
||||
// CancelledAt *time.Time `gorm:"index;"`
|
||||
CancelledAt *time.Time `gorm:"index;"`
|
||||
|
||||
// when it should be send
|
||||
SendAt *time.Time `gorm:"index;"`
|
||||
|
||||
// when it was last attempted send
|
||||
LastAttemptAt *time.Time `gorm:"index;"`
|
||||
|
||||
// when it was sent
|
||||
SentAt *time.Time `gorm:"index;"`
|
||||
|
||||
// self-managed
|
||||
SelfManaged bool `gorm:"not null;default:false;"`
|
||||
|
||||
// AnonymizedID is set when the recipient has been anonymized
|
||||
AnonymizedID *uuid.UUID `gorm:"type:uuid;"`
|
||||
Recipient *Recipient
|
||||
// A null recipientID means that the data has been anonymized
|
||||
RecipientID *uuid.UUID `gorm:"type:uuid;index;uniqueIndex:idx_campaign_recipients_campaign_id_recipient_id;"`
|
||||
|
||||
// NotableEventID is the most notable event for this recipient
|
||||
NotableEvent *Event `gorm:"foreignKey:NotableEventID;references:ID"`
|
||||
NotableEventID *uuid.UUID `gorm:"type:uuid;index"`
|
||||
}
|
||||
|
||||
func (CampaignRecipient) TableName() string {
|
||||
return CAMPAIGN_RECIPIENT_TABLE_NAME
|
||||
}
|
||||
19
backend/database/campaignRecipientGroup.go
Normal file
19
backend/database/campaignRecipientGroup.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// CampaignRecipientGroup is gorm data model
|
||||
// is a table of those recipient groups that belong to a campaign
|
||||
type CampaignRecipientGroup struct {
|
||||
CampaignID *uuid.UUID `gorm:"not null;index;type:uuid;uniqueIndex:idx_campaign_recipient_group;"`
|
||||
Campaign *Campaign
|
||||
|
||||
RecipientGroupID *uuid.UUID `gorm:"not null;index;type:uuid;uniqueIndex:idx_campaign_recipient_group;"`
|
||||
RecipientGroup *RecipientGroup
|
||||
}
|
||||
|
||||
func (CampaignRecipientGroup) TableName() string {
|
||||
return "campaign_recipient_groups"
|
||||
}
|
||||
51
backend/database/campaignStats.go
Normal file
51
backend/database/campaignStats.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
CAMPAIGN_STATS_TABLE = "campaign_stats"
|
||||
)
|
||||
|
||||
// CampaignStats is gorm data model for aggregated campaign statistics
|
||||
type CampaignStats struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid" json:"id"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;" json:"createdAt"`
|
||||
UpdatedAt *time.Time `gorm:"not null;" json:"updatedAt"`
|
||||
|
||||
// Campaign reference
|
||||
CampaignID *uuid.UUID `gorm:"not null;unique;index;type:uuid;" json:"campaignId"`
|
||||
CampaignName string `gorm:"not null;" json:"campaignName"`
|
||||
CompanyID *uuid.UUID `gorm:"index;type:uuid;" json:"companyId"` // nullable for global campaigns
|
||||
|
||||
// Time metrics
|
||||
CampaignStartDate *time.Time `gorm:"index;" json:"campaignStartDate"`
|
||||
CampaignEndDate *time.Time `gorm:"index;" json:"campaignEndDate"`
|
||||
CampaignClosedAt *time.Time `gorm:"index;" json:"campaignClosedAt"`
|
||||
|
||||
// Volume metrics
|
||||
TotalRecipients int `gorm:"not null;default:0" json:"totalRecipients"`
|
||||
TotalEvents int `gorm:"not null;default:0" json:"totalEvents"`
|
||||
|
||||
// Event type breakdowns
|
||||
EmailsSent int `gorm:"not null;default:0" json:"emailsSent"`
|
||||
TrackingPixelLoaded int `gorm:"not null;default:0" json:"trackingPixelLoaded"` // Email opens
|
||||
WebsiteVisits int `gorm:"not null;default:0" json:"websiteVisits"` // Link clicks
|
||||
DataSubmissions int `gorm:"not null;default:0" json:"dataSubmissions"` // Form submissions
|
||||
|
||||
// Success rates (as percentages for quick display)
|
||||
OpenRate float64 `gorm:"not null;default:0" json:"openRate"`
|
||||
ClickRate float64 `gorm:"not null;default:0" json:"clickRate"`
|
||||
SubmissionRate float64 `gorm:"not null;default:0" json:"submissionRate"`
|
||||
|
||||
// Campaign metadata
|
||||
TemplateName string `gorm:"" json:"templateName"`
|
||||
CampaignType string `gorm:"" json:"campaignType"` // 'scheduled', 'self-managed'
|
||||
}
|
||||
|
||||
func (CampaignStats) TableName() string {
|
||||
return CAMPAIGN_STATS_TABLE
|
||||
}
|
||||
72
backend/database/campaignTemplate.go
Normal file
72
backend/database/campaignTemplate.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
CAMPAIGN_TEMPLATE_TABLE = "campaign_templates"
|
||||
)
|
||||
|
||||
// CampaignTemplate is gorm data model
|
||||
type CampaignTemplate struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index;"`
|
||||
|
||||
Name string `gorm:"not null;index;uniqueIndex:idx_campaign_templates_unique_name_and_company_id;"`
|
||||
|
||||
URLPath string `gorm:"not null;default:'';index"`
|
||||
|
||||
// IsUsable indicates if a template is usable based on if it has all the required
|
||||
// data such as domainID, landingPage and etc to be used in a campaign
|
||||
IsUsable bool `gorm:"not null;default:false;index"`
|
||||
|
||||
// has-a
|
||||
LandingPageID *uuid.UUID `gorm:"type:uuid;index;"`
|
||||
LandingPage *Page `gorm:"references:LandingPage;foreignKey:LandingPageID;references:ID;"`
|
||||
|
||||
DomainID *uuid.UUID `gorm:"type:uuid;index;"`
|
||||
Domain *Domain `gorm:"foreignKey:DomainID"`
|
||||
|
||||
URLIdentifierID *uuid.UUID `gorm:"not null;type:uuid;index"`
|
||||
URLIdentifier *Identifier `gorm:"references:foreignKey:URLIdentifierID;references:ID"`
|
||||
|
||||
StateIdentifierID *uuid.UUID `gorm:"type:uuid;index"`
|
||||
StateIdentifier *Identifier `gorm:"references:foreignKey:StateIdentifierID;references:ID"`
|
||||
|
||||
// has-a optional
|
||||
BeforeLandingPageID *uuid.UUID `gorm:"type:uuid;index"`
|
||||
BeforeLandingPage *Page `gorm:"foreignkey:BeforeLandingPageID;references:ID"`
|
||||
|
||||
AfterLandingPageID *uuid.UUID `gorm:"type:uuid;index"`
|
||||
AfterLandingPage *Page `gorm:"foreignKey:AfterLandingPageID;references:ID"`
|
||||
|
||||
AfterLandingPageRedirectURL string `gorm:"not null;"`
|
||||
|
||||
EmailID *uuid.UUID `gorm:"type:uuid;index;"`
|
||||
Email *Email `gorm:"foreignKey:EmailID;references:ID;"`
|
||||
|
||||
SMTPConfigurationID *uuid.UUID `gorm:"type:uuid;index;"`
|
||||
SMTPConfiguration *SMTPConfiguration `gorm:"foreignKey:SMTPConfigurationID"`
|
||||
|
||||
APISenderID *uuid.UUID `gorm:"type:uuid;index;"`
|
||||
APISender *APISender `gorm:"foreignKey:APISenderID"`
|
||||
|
||||
// can belong-to
|
||||
CompanyID *uuid.UUID `gorm:"type:uuid;index;uniqueIndex:idx_campaign_templates_unique_name_and_company_id"`
|
||||
Company *Company `gorm:"foreignKey:CompanyID"`
|
||||
}
|
||||
|
||||
func (e *CampaignTemplate) Migrate(db *gorm.DB) error {
|
||||
// SQLITE
|
||||
// ensure name + company id is unique
|
||||
return UniqueIndexNameAndNullCompanyID(db, "campaign_templates")
|
||||
}
|
||||
|
||||
func (CampaignTemplate) TableName() string {
|
||||
return CAMPAIGN_TEMPLATE_TABLE
|
||||
}
|
||||
26
backend/database/company.go
Normal file
26
backend/database/company.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
COMPANY_TABLE = "companies"
|
||||
)
|
||||
|
||||
type Company struct {
|
||||
ID uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index"`
|
||||
Name string `gorm:"not null;unique;index"`
|
||||
|
||||
// backref: many-to-one
|
||||
Users []*User //`gorm:"foreignKey:CompanyID;"`
|
||||
RecipientGroups []*RecipientGroup //`gorm:"foreignKey:CompanyID;"`
|
||||
}
|
||||
|
||||
func (Company) TableName() string {
|
||||
return COMPANY_TABLE
|
||||
}
|
||||
32
backend/database/domain.go
Normal file
32
backend/database/domain.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
DOMAIN_TABLE = "domains"
|
||||
)
|
||||
|
||||
// Domain is gorm data model
|
||||
type Domain struct {
|
||||
ID uuid.UUID `gorm:"primary_key;not null;unique;type:uuid;"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index;"`
|
||||
CompanyID *uuid.UUID `gorm:"index;type:uuid;"`
|
||||
Name string `gorm:"not null;unique;"`
|
||||
ManagedTLSCerts bool `gorm:"not null;index;default:false"`
|
||||
OwnManagedTLS bool `gorm:"not null;index;default:false"`
|
||||
HostWebsite bool `gorm:"not null;"`
|
||||
PageContent string
|
||||
PageNotFoundContent string
|
||||
RedirectURL string
|
||||
// could has-one
|
||||
Company *Company
|
||||
}
|
||||
|
||||
func (Domain) TableName() string {
|
||||
return DOMAIN_TABLE
|
||||
}
|
||||
47
backend/database/email.go
Normal file
47
backend/database/email.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
EMAIL_TABLE = "emails"
|
||||
)
|
||||
|
||||
// Email is a gorm data model
|
||||
type Email struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index"`
|
||||
Name string `gorm:"not null;index;uniqueIndex:idx_emails_name_company_id;"`
|
||||
Content string `gorm:"not null;"`
|
||||
|
||||
AddTrackingPixel bool `gorm:"not null;"`
|
||||
|
||||
// mail fields
|
||||
// Envelope header - Bounce / Return-Path
|
||||
MailFrom string `gorm:"not null;"`
|
||||
// Mail header
|
||||
Subject string `gorm:"not null;"`
|
||||
From string `gorm:"not null;"`
|
||||
|
||||
// many to many
|
||||
Attachments []*Attachment `gorm:"many2many:email_attachments;"`
|
||||
|
||||
// can belong to
|
||||
CompanyID *uuid.UUID `gorm:"index;type:uuid;uniqueIndex:idx_emails_name_company_id;"`
|
||||
Company *Company
|
||||
}
|
||||
|
||||
func (e *Email) Migrate(db *gorm.DB) error {
|
||||
// SQLITE
|
||||
// ensure name + null company id is unique
|
||||
return UniqueIndexNameAndNullCompanyID(db, "emails")
|
||||
}
|
||||
|
||||
func (Email) TableName() string {
|
||||
return EMAIL_TABLE
|
||||
}
|
||||
16
backend/database/emailAttachment.go
Normal file
16
backend/database/emailAttachment.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// EmailAttachment is a gorm data model
|
||||
// it is a many to many relationship between messages and attachments
|
||||
type EmailAttachment struct {
|
||||
EmailID *uuid.UUID `gorm:"primary_key;not null;index;type:uuid;unique_index:idx_message_attachment;"`
|
||||
AttachmentID *uuid.UUID `gorm:"primary_key;not null;index;type:uuid;unique_index:idx_message_attachment;"`
|
||||
}
|
||||
|
||||
func (EmailAttachment) TableName() string {
|
||||
return "email_attachments"
|
||||
}
|
||||
21
backend/database/events.go
Normal file
21
backend/database/events.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
EVENT_TABLE = "events"
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
Name string `gorm:"not null;index;"`
|
||||
}
|
||||
|
||||
func (Event) TableName() string {
|
||||
return EVENT_TABLE
|
||||
}
|
||||
43
backend/database/factory.go
Normal file
43
backend/database/factory.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/phishingclub/phishingclub/config"
|
||||
"github.com/phishingclub/phishingclub/errs"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
// FromConfig database factory from config
|
||||
func FromConfig(conf config.Config) (*gorm.DB, error) {
|
||||
var db *gorm.DB
|
||||
switch conf.Database().Engine {
|
||||
case config.DefaultAdministrationUseSqlite:
|
||||
var err error
|
||||
dsn := fmt.Sprintf(
|
||||
"%s?_journal_mode=WAL&_busy_timeout=5000&_synchronous=NORMAL&_foreign_keys=ON",
|
||||
conf.Database().DSN,
|
||||
)
|
||||
db, err = gorm.Open(sqlite.Open(dsn), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
// SetMaxOpenConns sets the maximum number of open connections to the database.
|
||||
// without this, gorutines doing simultaneous db operations will cause
|
||||
// "database is locked" error when using sqlite with a high concurrency
|
||||
// this is because sqlite only allows one write operation at a time
|
||||
// and locks the whole database for the duration any write operation
|
||||
innerDB, err := db.DB()
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
innerDB.SetMaxIdleConns(1)
|
||||
default:
|
||||
return nil, config.ErrInvalidDatabase
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
18
backend/database/identifiers.go
Normal file
18
backend/database/identifiers.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
IDENTIFIER_TABLE = "identifiers"
|
||||
)
|
||||
|
||||
type Identifier struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
Name string `gorm:"not null;uniqueIndex"`
|
||||
}
|
||||
|
||||
func (Identifier) TableName() string {
|
||||
return IDENTIFIER_TABLE
|
||||
}
|
||||
16
backend/database/option.go
Normal file
16
backend/database/option.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Option is a database option (options stored in the database)
|
||||
type Option struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
Key string `gorm:"not null;unique;index"`
|
||||
Value string `gorm:"not null;"`
|
||||
}
|
||||
|
||||
func (Option) TableName() string {
|
||||
return "options"
|
||||
}
|
||||
35
backend/database/page.go
Normal file
35
backend/database/page.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
PAGE_TABLE = "pages"
|
||||
)
|
||||
|
||||
// Page is a gorm data model
|
||||
type Page struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index"`
|
||||
CompanyID *uuid.UUID `gorm:"index;uniqueIndex:idx_pages_unique_name_and_company_id;type:uuid"`
|
||||
Name string `gorm:"not null;index;uniqueIndex:idx_pages_unique_name_and_company_id;"`
|
||||
Content string `gorm:"not null;"`
|
||||
|
||||
// could has-one
|
||||
Company *Company
|
||||
}
|
||||
|
||||
func (e *Page) Migrate(db *gorm.DB) error {
|
||||
// SQLITE
|
||||
// ensure name + company id is unique
|
||||
return UniqueIndexNameAndNullCompanyID(db, "pages")
|
||||
}
|
||||
|
||||
func (Page) TableName() string {
|
||||
return PAGE_TABLE
|
||||
}
|
||||
42
backend/database/recipient.go
Normal file
42
backend/database/recipient.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
RECIPIENT_TABLE = "recipients"
|
||||
)
|
||||
|
||||
// Recipient is a gorm data model
|
||||
type Recipient struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index"`
|
||||
DeletedAt *time.Time `gorm:"index;"`
|
||||
|
||||
Email *string `gorm:";uniqueIndex"`
|
||||
Phone *string `gorm:";index"`
|
||||
ExtraIdentifier *string `gorm:";index"`
|
||||
|
||||
FirstName string `gorm:";"`
|
||||
LastName string `gorm:";"`
|
||||
Position string `gorm:";"`
|
||||
Department string `gorm:";"`
|
||||
City string `gorm:";"`
|
||||
Country string `gorm:";"`
|
||||
Misc string `gorm:";"`
|
||||
|
||||
// can belong to
|
||||
CompanyID *uuid.UUID `gorm:"type:uuid;index;"`
|
||||
Company *Company
|
||||
|
||||
// many-to-many
|
||||
Groups []RecipientGroup `gorm:"many2many:recipient_group_recipients;"`
|
||||
}
|
||||
|
||||
func (Recipient) TableName() string {
|
||||
return RECIPIENT_TABLE
|
||||
}
|
||||
9
backend/database/recipientCampaignEventView.go
Normal file
9
backend/database/recipientCampaignEventView.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package database
|
||||
|
||||
// RecipientCampaignEventView is a view read-only model
|
||||
type RecipientCampaignEventView struct {
|
||||
CampaignEvent
|
||||
|
||||
Name string // event name
|
||||
CampaignName string
|
||||
}
|
||||
38
backend/database/recipientGroup.go
Normal file
38
backend/database/recipientGroup.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
RECIPIENT_GROUP_TABLE = "recipient_groups"
|
||||
)
|
||||
|
||||
// RecipientGroup is a grouping of recipient
|
||||
type RecipientGroup struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index;"`
|
||||
|
||||
Name string `gorm:"not null;index;uniqueIndex:idx_recipient_groups_unique_name_and_company_id;"`
|
||||
|
||||
// can belong-to
|
||||
CompanyID *uuid.UUID `gorm:"type:uuid;index;uniqueIndex:idx_recipient_groups_unique_name_and_company_id"`
|
||||
Company *Company
|
||||
|
||||
// many-to-many
|
||||
Recipients []Recipient `gorm:"many2many:recipient_group_recipients;"`
|
||||
}
|
||||
|
||||
func (e *RecipientGroup) Migrate(db *gorm.DB) error {
|
||||
// SQLITE
|
||||
// ensure name + company id is unique
|
||||
return UniqueIndexNameAndNullCompanyID(db, "recipient_groups")
|
||||
}
|
||||
|
||||
func (RecipientGroup) TableName() string {
|
||||
return RECIPIENT_GROUP_TABLE
|
||||
}
|
||||
22
backend/database/recipientGroupRecipient.go
Normal file
22
backend/database/recipientGroupRecipient.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
RECIPIENT_GROUP_RECIPIENT_TABLE = "recipient_group_recipients"
|
||||
)
|
||||
|
||||
// RecipientGroupRecipient is a grouping of recipients and recipient groups
|
||||
type RecipientGroupRecipient struct {
|
||||
Recipient *Recipient
|
||||
RecipientID *uuid.UUID `gorm:"not null;uniqueIndex:idx_recipient_group"`
|
||||
|
||||
RecipientGroup *RecipientGroup
|
||||
RecipientGroupID *uuid.UUID `gorm:"not null;uniqueIndex:idx_recipient_group"`
|
||||
}
|
||||
|
||||
func (RecipientGroupRecipient) TableName() string {
|
||||
return RECIPIENT_GROUP_RECIPIENT_TABLE
|
||||
}
|
||||
18
backend/database/role.go
Normal file
18
backend/database/role.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Role is a role
|
||||
type Role struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
Name string `gorm:"not null;index;unique;"`
|
||||
|
||||
// one-to-many
|
||||
Users []*User
|
||||
}
|
||||
|
||||
func (Role) TableName() string {
|
||||
return "roles"
|
||||
}
|
||||
32
backend/database/session.go
Normal file
32
backend/database/session.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
SESSION_TABLE = "sessions"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index"`
|
||||
// IP address of the user when the session was created
|
||||
IPAddress string `gorm:"not null;index;default:''"`
|
||||
// the expiresAt is the time when the session will expire, nomatter the maxAgeAt
|
||||
ExpiresAt *time.Time `gorm:"not null;index"`
|
||||
// the maxAgeAt is the time when the session will expire, nomatter the expiresAt
|
||||
MaxAgeAt *time.Time `gorm:"not null;index"`
|
||||
// has-one
|
||||
//
|
||||
// belongs to
|
||||
UserID string `gorm:";type:uuid;"`
|
||||
User *User
|
||||
}
|
||||
|
||||
func (Session) TableName() string {
|
||||
return SESSION_TABLE
|
||||
}
|
||||
43
backend/database/smtpConfiguration.go
Normal file
43
backend/database/smtpConfiguration.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
SMTP_CONFIGURATION_TABLE = "smtp_configurations"
|
||||
)
|
||||
|
||||
// SMTPConfiguration is a page gorm data model
|
||||
// Simple Mail Transfer Protocol
|
||||
type SMTPConfiguration struct {
|
||||
ID uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index;"`
|
||||
Name string `gorm:"not null;uniqueIndex:idx_smtp_configurations_unique_name_and_company_id;"`
|
||||
Host string `gorm:"not null;"`
|
||||
Port uint16 `gorm:"not null;"`
|
||||
Username string `gorm:"not null;"`
|
||||
Password string `gorm:"not null;"`
|
||||
IgnoreCertErrors bool `gorm:"not null;"`
|
||||
|
||||
// back-reference
|
||||
Headers []*SMTPHeader
|
||||
|
||||
// can belong-to
|
||||
CompanyID *uuid.UUID `gorm:"uniqueIndex:idx_smtp_configurations_unique_name_and_company_id;"`
|
||||
Company *Company `gorm:"foreignkey:CompanyID;"`
|
||||
}
|
||||
|
||||
func (e *SMTPConfiguration) Migrate(db *gorm.DB) error {
|
||||
// SQLITE
|
||||
// ensure name + company id is unique
|
||||
return UniqueIndexNameAndNullCompanyID(db, "smtp_configurations")
|
||||
}
|
||||
|
||||
func (SMTPConfiguration) TableName() string {
|
||||
return SMTP_CONFIGURATION_TABLE
|
||||
}
|
||||
24
backend/database/smtpHeader.go
Normal file
24
backend/database/smtpHeader.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// SMTPHeader is headers sent with specific SMTP configurations
|
||||
type SMTPHeader struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index;"`
|
||||
Key string `gorm:"not null;"`
|
||||
Value string `gorm:"not null;"`
|
||||
|
||||
// belongs to
|
||||
SMTPConfigurationID *uuid.UUID `gorm:"index;not null;type:uuid"`
|
||||
SMTP *SMTPConfiguration `gorm:"foreignKey:SMTPConfigurationID"`
|
||||
}
|
||||
|
||||
func (SMTPHeader) TableName() string {
|
||||
return "smtp_headers"
|
||||
}
|
||||
49
backend/database/user.go
Normal file
49
backend/database/user.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
USER_TABLE = "users"
|
||||
)
|
||||
|
||||
// User is a database user
|
||||
type User struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index;"`
|
||||
|
||||
Name string `gorm:"not null;"`
|
||||
Username string `gorm:"not null;unique;"`
|
||||
Email string `gorm:"unique;"`
|
||||
PasswordHash string `gorm:"type:varchar(255);"`
|
||||
RequirePasswordRenew bool `gorm:"default:false;"`
|
||||
|
||||
// MFA
|
||||
TOTPEnabled bool `gorm:"default:false;"`
|
||||
TOTPSecret string
|
||||
TOTPAuthURL string
|
||||
// TODO rename to MFARecoveryCode
|
||||
TOTPRecoveryCode string `gorm:"type:varchar(64);"`
|
||||
|
||||
// SSO id
|
||||
SSOID string
|
||||
|
||||
// maybe has one
|
||||
CompanyID *uuid.UUID `gorm:"type:uuid;index;"`
|
||||
Company *Company
|
||||
// has one
|
||||
RoleID *uuid.UUID `gorm:"not null;type:uuid;index"`
|
||||
Role *Role
|
||||
// APIKey
|
||||
APIKey string `gorm:"index"`
|
||||
}
|
||||
|
||||
func (User) TableName() string {
|
||||
return USER_TABLE
|
||||
}
|
||||
23
backend/database/utils.go
Normal file
23
backend/database/utils.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Migrater interface {
|
||||
Migrate(db *gorm.DB) error
|
||||
}
|
||||
|
||||
func UniqueIndexNameAndNullCompanyID(db *gorm.DB, tableName string) error {
|
||||
// SQLITE / POSTGRES
|
||||
// ensure name + null company id is unique
|
||||
idx := fmt.Sprintf("CREATE UNIQUE INDEX IF NOT EXISTS idx_%s_name_null_company_id ON %s (name) WHERE (company_id IS NULL)", tableName, tableName)
|
||||
res := db.Exec(idx)
|
||||
if res.Error != nil {
|
||||
return fmt.Errorf("error creating index: %v on table %s", res.Error, tableName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
33
backend/database/webhook.go
Normal file
33
backend/database/webhook.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const (
|
||||
WEBHOOK_TABLE = "webhooks"
|
||||
)
|
||||
|
||||
// Webhook is a gorm data model for webhooks
|
||||
type Webhook struct {
|
||||
ID *uuid.UUID `gorm:"primary_key;not null;unique;type:uuid"`
|
||||
CreatedAt *time.Time `gorm:"not null;index;"`
|
||||
UpdatedAt *time.Time `gorm:"not null;index;"`
|
||||
CompanyID *uuid.UUID `gorm:"uniqueIndex:idx_webhooks_unique_name_and_company_id;type:uuid"`
|
||||
Name string `gorm:"not null;uniqueIndex:idx_webhooks_unique_name_and_company_id;"`
|
||||
URL string `gorm:"not null;"`
|
||||
Secret string
|
||||
}
|
||||
|
||||
func (e *Webhook) Migrate(db *gorm.DB) error {
|
||||
// SQLITE
|
||||
// ensure name + company id is unique
|
||||
return UniqueIndexNameAndNullCompanyID(db, "webhooks")
|
||||
}
|
||||
|
||||
func (Webhook) TableName() string {
|
||||
return WEBHOOK_TABLE
|
||||
}
|
||||
Reference in New Issue
Block a user