Skip to content
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Watch tower provides a lite weight approach to grouping information by product w
| Group repositories by Product | User github topics to group repos by products |
| Dashboard view | Quickly view the overall health of a group of products by viewing open PRs, security vulnerabilities and repositories |
| Product view | Focused view for open PRs, security vulnerabilities and repositories |
| Notification on new PR / Issue | Get notifications when new PRs or Issues are created in the watched repositories accross all orgs | |
| Local only | Data will never leave your device, settings page includes a kill switch to remove all data from the device. GITHUB PAT token only required read access |

## Roadmap
Expand Down
6 changes: 1 addition & 5 deletions frontend/src/lib/wailsjs/go/watchtower/Service.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ export function CreateOrganisation(arg1:string,arg2:string,arg3:string,arg4:stri

export function CreateProduct(arg1:string,arg2:string,arg3:Array<string>,arg4:number):Promise<products.ProductDTO>;

export function CreateUnreadNotification():Promise<void>;

export function CreateUnreadPRNotification():Promise<number>;

export function CreateUnreadSecurityNotification():Promise<number>;
export function CreateUnreadNotification():Promise<number>;

export function DeleteAllOrgs():Promise<void>;

Expand Down
8 changes: 0 additions & 8 deletions frontend/src/lib/wailsjs/go/watchtower/Service.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,6 @@ export function CreateUnreadNotification() {
return window['go']['watchtower']['Service']['CreateUnreadNotification']();
}

export function CreateUnreadPRNotification() {
return window['go']['watchtower']['Service']['CreateUnreadPRNotification']();
}

export function CreateUnreadSecurityNotification() {
return window['go']['watchtower']['Service']['CreateUnreadSecurityNotification']();
}

export function DeleteAllOrgs() {
return window['go']['watchtower']['Service']['DeleteAllOrgs']();
}
Expand Down
6 changes: 6 additions & 0 deletions internal/database/database.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package database provides functionality for interacting with the SQLite database, including migrations and query initialization.
package database

import (
Expand All @@ -14,14 +15,17 @@ var ddl string

const dbName = "watchtower.db"

// Migrator handles database schema initialization and migrations.
type Migrator struct {
db *sql.DB
}

// NewMigrator creates a new Migrator instance with the provided database connection.
func NewMigrator(db *sql.DB) *Migrator {
return &Migrator{db: db}
}

// Init initializes the database schema by executing the DDL.
func (m *Migrator) Init() error {
_, err := m.db.Exec(ddl)

Expand All @@ -41,6 +45,7 @@ func NewDBFromProvider(filePath string) (*Queries, *sql.DB, error) {
return New(db), db, nil
}

// resolveDBPath resolves the database file path, handling the special ":memory:" case.
func resolveDBPath(filePath string) string {
if filePath == ":memory:" {
return filePath
Expand All @@ -49,6 +54,7 @@ func resolveDBPath(filePath string) string {
return path.Join(filePath, dbName, "?_busy_timeout=5000")
}

// IsErrUniqueConstraint checks if the error is a SQLite unique constraint violation.
func IsErrUniqueConstraint(err error) bool {
return err != nil && strings.Contains(err.Error(), "constraint failed: UNIQUE constraint failed")
}
5 changes: 4 additions & 1 deletion internal/logging/logging.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
// Package logging provides structured logging using slog and context-based logger propagation.
package logging

import (
"log/slog"
"os"
)

// LoggerType defines the available logger output formats.
type LoggerType string

const (
// LoggerText represents a text-based logger output format.
LoggerText LoggerType = "Text"
)

Expand All @@ -18,7 +21,7 @@ func init() {
baseLogger = New(slog.LevelInfo)
}

// New logger
// New initializes and returns a new text-based logger with the specified level.
func New(level slog.Level) *slog.Logger {
baseLogger = textHandler(level)
return baseLogger
Expand Down
4 changes: 4 additions & 0 deletions internal/notifications/service.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package notifications provides a service for managing and retrieving organization notifications.
package notifications

import (
Expand All @@ -8,6 +9,7 @@ import (
"watchtower/internal/logging"
)

// New creates a new Service instance with the provided store and transaction database.
func New(db Store, txnDB *sql.DB, txnFunc func(tx *sql.Tx) Store) *Service {
return &Service{
store: db,
Expand All @@ -16,6 +18,7 @@ func New(db Store, txnDB *sql.DB, txnFunc func(tx *sql.Tx) Store) *Service {
}
}

// CreateNotificationParams holds the parameters for creating a new notification.
type CreateNotificationParams struct {
OrgID int64
ExternalID string
Expand Down Expand Up @@ -80,6 +83,7 @@ func (s *Service) GetUnreadNotifications(ctx context.Context) ([]Notification, e
return fromNotificationModels(models), nil
}

// GetNotificationByExternalID fetches a single notification by its external ID.
func (s *Service) GetNotificationByExternalID(ctx context.Context, externalID string) (Notification, error) {
logger := logging.FromContext(ctx).With("externalID", externalID, "service", "notifications")
logger.Debug("Fetching notification by external ID")
Expand Down
5 changes: 4 additions & 1 deletion internal/organisations/service.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package organisations provides a service for managing and retrieving GitHub organizations and their configurations.
package organisations

import (
Expand All @@ -8,12 +9,14 @@ import (
"watchtower/internal/logging"
)

// Service handles organization-related business logic.
type Service struct {
store OrgStore
txnDB *sql.DB
txnFunc func(tx *sql.Tx) OrgStore
}

// New creates a new Service instance with the provided store, transaction database and transaction function.
func New(store OrgStore, txnDB *sql.DB, txnFunc func(tx *sql.Tx) OrgStore) *Service {
return &Service{
store: store,
Expand All @@ -22,7 +25,7 @@ func New(store OrgStore, txnDB *sql.DB, txnFunc func(tx *sql.Tx) OrgStore) *Serv
}
}

// Create creates a new organisation in the database with the given parameters and returns its DTO representation.
// Create creates a new organization with the given parameters and returns the created organization or an error.
func (s Service) Create(ctx context.Context, params CreateOrgParams) (OrganisationDTO, error) {
logger := logging.FromContext(ctx).With("service", "organisations")

Expand Down
4 changes: 4 additions & 0 deletions internal/products/service.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package products provides a service for managing products, repositories, pull requests, and security vulnerabilities.
package products

import (
Expand All @@ -10,16 +11,19 @@ import (
"watchtower/internal/logging"
)

// Service handles product-related business logic.
type Service struct {
store ProductStore
}

// New creates a new Service instance with the provided product store.
func New(store ProductStore) *Service {
return &Service{
store: store,
}
}

// Create creates a new product with the given parameters and returns the created product or an error.
func (s *Service) Create(ctx context.Context, params CreateProductParams) (ProductDTO, error) {
logger := logging.FromContext(ctx).With("service", "products")

Expand Down
1 change: 1 addition & 0 deletions internal/watchtower/notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ func (s *Service) MarkNotificationAsRead(notificationID int64) error {
return s.notificationSvc.MarkNotificationAsRead(s.ctx, notificationID)
}

// DeleteOldNotifications deletes notifications that are older than 90 days.
func (s *Service) DeleteOldNotifications() error {
nintyDaysAgo := time.Now().AddDate(0, 0, -90)
return s.notificationSvc.DeleteNotificationsByDate(s.ctx, nintyDaysAgo)
Expand Down
3 changes: 3 additions & 0 deletions internal/watchtower/organisations.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ func (s *Service) GetDefaultOrganisation() (organisations.OrganisationDTO, error
return s.orgSvc.GetDefault(s.ctx)
}

// SetDefaultOrg marks the organization with the specified ID as the default organization.
func (s *Service) SetDefaultOrg(id int64) (organisations.OrganisationDTO, error) {
return s.orgSvc.SetDefault(s.ctx, id)
}

// GetOrganisationByID retrieves an organization's details by its ID.
func (s *Service) GetOrganisationByID(id int64) (organisations.OrganisationDTO, error) {
return s.orgSvc.Get(s.ctx, id)
}
Expand All @@ -31,6 +33,7 @@ func (s *Service) GetAllOrganisations() ([]organisations.OrganisationDTO, error)
return s.orgSvc.GetAll(s.ctx)
}

// DeleteAllOrgs deletes all organizations and their associated products.
func (s *Service) DeleteAllOrgs() error {
list, err := s.orgSvc.GetAll(s.ctx)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/watchtower/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func (s *Service) SyncOrg(orgId int64) error {
return nil
}

// SyncProduct synchronizes a product with the given ID by retrieving its details and associated organization data.
// SyncProduct synchronizes a single product by its ID.
func (s *Service) SyncProduct(id int64) error {
logger := logging.FromContext(s.ctx)

Expand Down
6 changes: 6 additions & 0 deletions internal/watchtower/worker.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package watchtower provides the main application logic, including the service layer and background workers.
package watchtower

import (
Expand All @@ -12,13 +13,15 @@ import (
"github.com/wailsapp/wails/v2/pkg/runtime"
)

// Workers manages background jobs for syncing data and cleaning up old records.
type Workers struct {
ctx context.Context
watchTower *Service
cron gocron.Scheduler
logger *slog.Logger
}

// NewWorkers initializes and returns a new Workers instance with the provided service.
func NewWorkers(wt *Service) (*Workers, error) {
logger := logging.FromContext(context.Background()).With("service", "workers")
s, err := gocron.NewScheduler()
Expand All @@ -34,6 +37,7 @@ func NewWorkers(wt *Service) (*Workers, error) {
}, nil
}

// AddJobs registers the background jobs to the scheduler.
func (w *Workers) AddJobs() error {

if _, err := w.cron.NewJob(
Expand All @@ -54,6 +58,7 @@ func (w *Workers) AddJobs() error {
return nil
}

// Start begins the execution of scheduled jobs.
func (w *Workers) Start(ctx context.Context) {
w.ctx = ctx

Expand All @@ -62,6 +67,7 @@ func (w *Workers) Start(ctx context.Context) {
w.cron.Start()
}

// Stop halts the execution of scheduled jobs and shuts down the scheduler.
func (w *Workers) Stop() {
w.logger.Debug("Stopping workers")

Expand Down