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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 3 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ linters:
- staticcheck
- unused
settings:
staticcheck:
checks: ["-ST1017"]
errcheck:
# Deferred Close errors are rarely actionable; keep errcheck for real ignored returns.
exclude-functions:
Expand All @@ -27,4 +29,4 @@ run:

issues:
max-issues-per-linter: 0
max-same-issues: 0
max-same-issues: 0
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ standards: check-sql check-logs check-test-layout

check-sql:
@bash scripts/check_sql_safety.sh
@bash scripts/check_sql_allowlist.sh

check-logs:
@bash scripts/check_no_stdlog.sh
Expand Down
4 changes: 2 additions & 2 deletions addons/audit/audit_cleanup.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func init() {
}

func purgeAuditLog(ctx context.Context, model string, id int, _ map[string]string) (string, error) {
if model != auditCleanupModel || id <= 0 {
if model != auditCleanupModel || 0 >= id {
return "", fmt.Errorf("invalid cleanup record")
}
if !orm.UserHasGroupXML(ctx, orm.SecurityUID(ctx), "base.group_system") {
Expand Down Expand Up @@ -85,7 +85,7 @@ func retentionDaysFromRecord(rec map[string]interface{}) (int, error) {
return 30, nil
case "custom":
d, ok := orm.CoerceInt64(rec["retention_days"])
if !ok || d <= 0 {
if !ok || 0 >= d {
return 0, fmt.Errorf("custom retention requires days greater than zero")
}
if d > 3650 {
Expand Down
4 changes: 2 additions & 2 deletions addons/audit/audit_export.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func sha256Hex(data []byte) string {
}

func auditPeriodBounds(rows []auditExportRow) (from, to string) {
if len(rows) == 0 {
if 0 == len(rows) {
return "", ""
}
dates := make([]string, len(rows))
Expand All @@ -102,7 +102,7 @@ func auditPeriodBounds(rows []auditExportRow) (from, to string) {
}

func auditMinMaxID(rows []auditExportRow) (minID, maxID int64) {
if len(rows) == 0 {
if 0 == len(rows) {
return 0, 0
}
minID = rows[0].ID
Expand Down
30 changes: 15 additions & 15 deletions addons/audit/audit_retention.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ const (

// RetentionOptions configures one retention pass.
type RetentionOptions struct {
RetentionDays int
FilterModel string
DryRun bool
LegalHold bool
DeleteAfterExport bool
Compress bool
BatchSize int
MaxRowsPerRun int
RetentionDays int
FilterModel string
DryRun bool
LegalHold bool
DeleteAfterExport bool
Compress bool
BatchSize int
MaxRowsPerRun int
}

// RetentionResult aggregates one run.
Expand Down Expand Up @@ -56,7 +56,7 @@ func runAuditRetentionCron(ctx context.Context, _ map[string]interface{}) error
}

func runRetentionFromPolicy(ctx context.Context, model string, id int, _ map[string]string) (string, error) {
if model != auditRetentionPolicyModel || id <= 0 {
if model != auditRetentionPolicyModel || 0 >= id {
return "", fmt.Errorf("invalid policy record")
}
if !orm.UserHasGroupXML(ctx, orm.SecurityUID(ctx), "base.group_system") {
Expand Down Expand Up @@ -107,22 +107,22 @@ func loadRetentionPolicy(ctx context.Context) (map[string]interface{}, error) {
if err != nil {
return nil, err
}
if len(rows) == 0 {
if 0 == len(rows) {
return nil, fmt.Errorf("audit retention policy not configured")
}
return rows[0], nil
}

func retentionOptionsFromPolicy(policy map[string]interface{}, forceDryRun bool) RetentionOptions {
days, _ := orm.CoerceInt64(policy["hot_retention_days"])
if days <= 0 {
if 0 >= days {
if raw := strings.TrimSpace(orm.GetConfigParam(context.Background(), "audit.default_retention_days", "")); raw != "" {
if d, ok := orm.CoerceInt64(raw); ok && d > 0 {
days = d
}
}
}
if days <= 0 {
if 0 >= days {
days = 90
}
batch, _ := orm.CoerceInt64(policy["batch_size"])
Expand Down Expand Up @@ -175,7 +175,7 @@ func runAuditRetention(ctx context.Context, opts RetentionOptions) (RetentionRes
if err != nil {
return out, err
}
if len(rows) == 0 {
if 0 == len(rows) {
break
}
if err := processAuditBatch(ctx, opts, rows, &out); err != nil {
Expand Down Expand Up @@ -215,7 +215,7 @@ func processAuditBatch(ctx context.Context, opts RetentionOptions, rows []map[st
ids = append(ids, id)
}
}
if len(exportRows) == 0 {
if 0 == len(exportRows) {
return nil
}

Expand Down Expand Up @@ -287,7 +287,7 @@ func processAuditBatch(ctx context.Context, opts RetentionOptions, rows []map[st

func unlinkAuditIDs(ctx context.Context, ids []int64) error {
for _, id := range ids {
if id <= 0 {
if 0 >= id {
continue
}
if err := orm.Unlink(ctx, auditModel, int(id)); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion addons/audit/models/sys_audit_cleanup.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
type SysAuditCleanup struct {
sdk.Model `sumeru:"model=sys.audit.cleanup"`

RetentionPreset sdk.String `sumeru:"required,string=Retention,default=30_days,selection=7_days:Older than 7 days,30_days:Older than 30 days,custom:Custom days"`
RetentionPreset sdk.String `sumeru:"required,string=Retention,default=30_days,selection=7_days:Older than 7 days,30_days:Older than 30 days,custom:Custom days"`
RetentionDays sdk.Integer `sumeru:"string=Custom days"`
FilterModel sdk.String `sumeru:"string=Limit to model"`
DryRun sdk.Boolean `sumeru:"string=Dry run only"`
Expand Down
24 changes: 12 additions & 12 deletions addons/audit/models/sys_audit_retention_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ import (
type SysAuditRetentionPolicy struct {
sdk.Model `sumeru:"model=sys.audit.retention.policy"`

Name sdk.String `sumeru:"required,string=Name,default=Audit retention"`
HotRetentionDays sdk.Integer `sumeru:"required,string=Hot retention days,default=90"`
FilterModel sdk.String `sumeru:"string=Limit to model"`
AutoEnabled sdk.Boolean `sumeru:"string=Automatic retention,default=false"`
BatchSize sdk.Integer `sumeru:"string=Batch size,default=500"`
MaxRowsPerRun sdk.Integer `sumeru:"string=Max rows per run,default=5000"`
Compress sdk.Boolean `sumeru:"string=Gzip exports,default=true"`
DeleteAfterExport sdk.Boolean `sumeru:"string=Delete from database after export,default=false"`
LegalHold sdk.Boolean `sumeru:"string=Legal hold no deletes,default=false"`
DryRunNext sdk.Boolean `sumeru:"string=Dry run on next manual run,default=false"`
LastRunAt sdk.DateTime `sumeru:"string=Last run"`
LastRunSummary sdk.Text `sumeru:"string=Last run summary"`
Name sdk.String `sumeru:"required,string=Name,default=Audit retention"`
HotRetentionDays sdk.Integer `sumeru:"required,string=Hot retention days,default=90"`
FilterModel sdk.String `sumeru:"string=Limit to model"`
AutoEnabled sdk.Boolean `sumeru:"string=Automatic retention,default=false"`
BatchSize sdk.Integer `sumeru:"string=Batch size,default=500"`
MaxRowsPerRun sdk.Integer `sumeru:"string=Max rows per run,default=5000"`
Compress sdk.Boolean `sumeru:"string=Gzip exports,default=true"`
DeleteAfterExport sdk.Boolean `sumeru:"string=Delete from database after export,default=false"`
LegalHold sdk.Boolean `sumeru:"string=Legal hold no deletes,default=false"`
DryRunNext sdk.Boolean `sumeru:"string=Dry run on next manual run,default=false"`
LastRunAt sdk.DateTime `sumeru:"string=Last run"`
LastRunSummary sdk.Text `sumeru:"string=Last run summary"`
}
8 changes: 4 additions & 4 deletions addons/automation/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func actionApplies(ctx context.Context, row map[string]interface{}, ev event.Eve
return true
}
resID, ok := orm.CoerceInt64(ev.Payload["id"])
if evModel == "" || !ok || resID <= 0 {
if evModel == "" || !ok || 0 >= resID {
return false
}
var domain [][]interface{}
Expand Down Expand Up @@ -107,7 +107,7 @@ func executeServerAction(ctx context.Context, row map[string]interface{}, ev eve
}
modelName := strings.TrimSpace(orm.AsString(ev.Payload["model"]))
resID, ok := orm.CoerceInt64(ev.Payload["id"])
if modelName == "" || !ok || resID <= 0 {
if modelName == "" || !ok || 0 >= resID {
return nil
}
if err := rejectUnsafeWrite(modelName, vals); err != nil {
Expand Down Expand Up @@ -140,14 +140,14 @@ func createActivity(ctx context.Context, spec string, ev event.Event) error {
summary = strings.TrimSpace(summary)
modelName := strings.TrimSpace(orm.AsString(ev.Payload["model"]))
resID, ok := orm.CoerceInt64(ev.Payload["id"])
if xmlid == "" || modelName == "" || !ok || resID <= 0 {
if xmlid == "" || modelName == "" || !ok || 0 >= resID {
return fmt.Errorf("activity action requires type, model, and id")
}
if _, registered := orm.Registry["mail.activity"]; !registered {
return fmt.Errorf("mail.activity is not installed")
}
typeID, typeModel, err := orm.ResolveXmlId(ctx, xmlid)
if err != nil || typeModel != "mail.activity.type" || typeID <= 0 {
if err != nil || typeModel != "mail.activity.type" || 0 >= typeID {
return fmt.Errorf("unknown activity type %q", xmlid)
}
if summary == "" {
Expand Down
4 changes: 2 additions & 2 deletions addons/automation/safety.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func deleteActionMatched(model string, id int, actionID int64) bool {
}

func matchBeforeDelete(ctx context.Context, model string, id int, before map[string]interface{}) {
if orm.DB == nil || id <= 0 || automationSkippedModel(model) {
if orm.DB == nil || 0 >= id || automationSkippedModel(model) {
return
}
if _, ok := orm.Registry["sys.server.action"]; !ok {
Expand Down Expand Up @@ -121,7 +121,7 @@ func matchBeforeDelete(ctx context.Context, model string, id int, before map[str
}

func claimRun(ctx context.Context, actionID int64, ev event.Event, model string, resID int64) (bool, error) {
if orm.DB == nil || actionID <= 0 {
if orm.DB == nil || 0 >= actionID {
return true, nil
}
if _, ok := orm.Registry["sys.server.action.log"]; !ok {
Expand Down
2 changes: 1 addition & 1 deletion addons/automation/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func resolveWebhookDialIPs(host string) ([]net.IP, error) {
}
out = append(out, ip)
}
if len(out) == 0 {
if 0 == len(out) {
return nil, fmt.Errorf("webhook host resolved to no addresses")
}
return out, nil
Expand Down
16 changes: 0 additions & 16 deletions addons/base/data/core_company_data.xml

This file was deleted.

1 change: 0 additions & 1 deletion addons/base/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"description": "The kernel of Sumeru, providing core identities (Users, Companies, Partners) and system metadata (Views, Rules, Models).",
"application": true,
"data": [
"data/core_company_data.xml",
"data/partner_property_definition_data.xml",
"views/core_company_actions.xml",
"views/core_users_actions.xml",
Expand Down
6 changes: 6 additions & 0 deletions addons/base/views/menus.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
<!-- Security -->
<menuitem id="menu_security_section" name="Security" parent="menu_settings_root"
sequence="15" web_icon="settings" groups="base.group_system" />
<menuitem id="menu_security_auth_settings" name="Authentication" parent="menu_security_section"
sequence="5" action="base.action_settings.auth" groups="base.group_system"
web_icon="settings" />
<menuitem id="menu_security_mail_settings" name="Mail delivery" parent="menu_security_section"
sequence="8" action="base.action_settings.mail" groups="base.group_system"
web_icon="settings" />
<menuitem id="menu_security_access" name="Access Rights" parent="menu_security_section"
sequence="10" action="base.access_action_sys.access" groups="base.group_system"
web_icon="settings" />
Expand Down
2 changes: 2 additions & 0 deletions addons/base/views/platform_actions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
<action id="action_app_logs" type="url" name="Event Log" url="/web/settings/app-logs"/>
<action id="action_settings.hub" type="url" name="General Settings" url="/web/settings"/>
<action id="action_settings.account" type="url" name="Account security" url="/web/settings/account"/>
<action id="action_settings.auth" type="url" name="Authentication" url="/web/settings/auth"/>
<action id="action_settings.mail" type="url" name="Mail delivery" url="/web/settings/mail"/>
</data>
</sumeru>
20 changes: 10 additions & 10 deletions addons/calendar/models/calendar_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import (
type CalendarEvent struct {
sdk.Model `sumeru:"model=calendar.event"`

Name sdk.String `sumeru:"required,string=Meeting Subject"`
Start sdk.DateTime `sumeru:"required,string=Start"`
Stop sdk.DateTime `sumeru:"required,string=Stop"`
Allday sdk.Boolean `sumeru:"string=All Day,default=false"`
PartnerIDs sdk.Many2Many[CorePartner] `sumeru:"string=Attendees,table=calendar_event_partner_rel,left=event_id,right=partner_id"`
UserID sdk.Many2One[CoreUser] `sumeru:"string=Organizer"`
ResModel sdk.String `sumeru:"index,column=model,string=Linked Document Model"`
ResID sdk.Integer `sumeru:"index,string=Linked Document"`
Description sdk.Text `sumeru:"string=Description"`
Active sdk.Boolean `sumeru:"string=Active,default=true"`
Name sdk.String `sumeru:"required,string=Meeting Subject"`
Start sdk.DateTime `sumeru:"required,string=Start"`
Stop sdk.DateTime `sumeru:"required,string=Stop"`
Allday sdk.Boolean `sumeru:"string=All Day,default=false"`
PartnerIDs sdk.Many2Many[CorePartner] `sumeru:"string=Attendees,table=calendar_event_partner_rel,left=event_id,right=partner_id"`
UserID sdk.Many2One[CoreUser] `sumeru:"string=Organizer"`
ResModel sdk.String `sumeru:"index,column=model,string=Linked Document Model"`
ResID sdk.Integer `sumeru:"index,string=Linked Document"`
Description sdk.Text `sumeru:"string=Description"`
Active sdk.Boolean `sumeru:"string=Active,default=true"`
}
12 changes: 8 additions & 4 deletions addons/digest/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ func runDueDigests(ctx context.Context, _ event.Event) error {
}
return orm.WithElevated(ctx, "digest.hook", func(bypass context.Context) error {
rows, err := orm.Search(bypass, "digest.digest", [][]interface{}{{"active", "=", true}})
if err != nil || len(rows) == 0 {
if err != nil {
return err
}
if 0 == len(rows) {
return err
}

for _, digestRow := range rows {
if err := runDigest(bypass, digestRow); err != nil {
applog.Warn(ctx, applog.Event{
Expand All @@ -40,7 +44,7 @@ func runDueDigests(ctx context.Context, _ event.Event) error {

func runDigest(ctx context.Context, digestRow map[string]interface{}) error {
digestID, ok := orm.CoerceInt64(digestRow["id"])
if !ok || digestID <= 0 {
if !ok || 0 >= digestID {
return nil
}
name := strings.TrimSpace(orm.AsString(digestRow["name"]))
Expand All @@ -51,7 +55,7 @@ func runDigest(ctx context.Context, digestRow map[string]interface{}) error {
if err != nil {
return err
}
if len(kpiRows) == 0 {
if 0 == len(kpiRows) {
return nil
}
var lines []string
Expand All @@ -71,7 +75,7 @@ func runDigest(ctx context.Context, digestRow map[string]interface{}) error {
}
lines = append(lines, fmt.Sprintf("- %s: %.0f", label, value))
}
if len(lines) == 0 {
if 0 == len(lines) {
return nil
}
body := name + "\n" + strings.Join(lines, "\n")
Expand Down
2 changes: 1 addition & 1 deletion addons/i18n/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ addons/<module>/i18n/<lang>.po

Examples: `i18n/fr_FR.po`, `i18n/es.po`. Language comes from the PO header `Language:` line or from the filename (normalized to Sumeru codes like `fr_FR`).

On **install** or **`-u <module>`**, the server imports all `i18n/*.po` files into `sys.translation` with `module` set to that addon’s technical name.
On **server start**, **install**, or **`-u <module>`**, the server imports `i18n/*.po` from every installed module on `addons_path` (kernel, standard, and custom) into `sys.translation`. `module` is that addon’s technical name. `msgid` is the English source (menu name, field label, button label). `en_US` stays the source language; set `core.user.lang` to the catalog language to see translations.

## CLI

Expand Down
4 changes: 2 additions & 2 deletions addons/im/company_href.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import (

// CompanySettingsFormHref returns a workspace URL to edit company IM settings when the user may read core.company.
func CompanySettingsFormHref(ctx context.Context, companyID int) string {
if companyID <= 0 {
if 0 >= companyID {
return ""
}
uid := orm.SecurityUID(ctx)
if uid <= 0 {
if 0 >= uid {
return ""
}
if err := orm.CheckModelAccess(ctx, uid, "core.company", "read"); err != nil {
Expand Down
Loading
Loading