diff --git a/config/config.go b/config/config.go index 59a15a1..9c065e6 100644 --- a/config/config.go +++ b/config/config.go @@ -110,6 +110,7 @@ type AmConfig struct { WorkerTasks int `yaml:"workerTasks"` } `yaml:"queues"` Caches struct { + Ads int `yaml:"ads"` Communities int `yaml:"communities"` CommunityProps int `yaml:"communityProps"` Conferences int `yaml:"conferences"` @@ -254,6 +255,7 @@ func overlayConfig(dest *AmConfig, loaded *AmConfig, defaults *AmConfig) { dest.Tuning.Queues.EmailSend = overlayInt(loaded.Tuning.Queues.EmailSend, defaults.Tuning.Queues.EmailSend) dest.Tuning.Queues.IPBans = overlayInt(loaded.Tuning.Queues.IPBans, defaults.Tuning.Queues.IPBans) dest.Tuning.Queues.WorkerTasks = overlayInt(loaded.Tuning.Queues.WorkerTasks, defaults.Tuning.Queues.WorkerTasks) + dest.Tuning.Caches.Ads = overlayInt(loaded.Tuning.Caches.Ads, defaults.Tuning.Caches.Ads) dest.Tuning.Caches.Communities = overlayInt(loaded.Tuning.Caches.Communities, defaults.Tuning.Caches.Communities) dest.Tuning.Caches.CommunityProps = overlayInt(loaded.Tuning.Caches.CommunityProps, defaults.Tuning.Caches.CommunityProps) dest.Tuning.Caches.Conferences = overlayInt(loaded.Tuning.Caches.Conferences, defaults.Tuning.Caches.Conferences) diff --git a/config/default.yaml b/config/default.yaml index 9a07869..45dd3ee 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -64,6 +64,7 @@ tuning: ipBans: 32 workerTasks: 128 caches: + ads: 30 communities: 50 communityProps: 100 conferences: 100 diff --git a/database/adverts.go b/database/adverts.go new file mode 100644 index 0000000..6796832 --- /dev/null +++ b/database/adverts.go @@ -0,0 +1,88 @@ +/* + * Amsterdam Web Communities System + * Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ +// The database package contains database management and storage logic. +package database + +import ( + "context" + crand "crypto/rand" + "math/big" + "sync" + + "git.erbosoft.com/amy/amsterdam/config" + lru "github.com/hashicorp/golang-lru" +) + +// Advert represents an advertising banner. +type Advert struct { + AdId int32 `db:"adid"` // ID of the ad + ImagePath string `db:"imagepath"` // path to the ad image + PathStyle int16 `db:"pathstyle"` // path style + Caption *string `db:"caption"` // caption + LinkURL *string `db:"linkurl"` // link URL +} + +// Values for PathStyle. +const ( + AdPathStyleContextRelative int16 = 0 // indicates context-relative image path +) + +// adCache is the cache for advertisements. +var adCache *lru.Cache = nil + +// adCacheMutex synchronizes access to adCache. +var adCacheMutex sync.Mutex + +// setupAdCache sets up the ad cache. +func setupAdCache() { + var err error + adCache, err = lru.New(config.GlobalConfig.Tuning.Caches.Ads) + if err != nil { + panic(err) + } +} + +// ResolvePath creates an absolute image path based on the stored path data. +func (ad *Advert) ResolvePath() string { + if ad.PathStyle == AdPathStyleContextRelative { + return "/" + ad.ImagePath + } + return "" +} + +// AmGetAd gets an ad by ID. +func AmGetAd(ctx context.Context, adid int32) (*Advert, error) { + adCacheMutex.Lock() + defer adCacheMutex.Unlock() + rc, ok := adCache.Get(adid) + if ok { + return rc.(*Advert), nil + } + var theAd Advert + err := amdb.GetContext(ctx, &theAd, "SELECT * FROM adverts WHERE adid = ?", adid) + if err != nil { + return nil, err + } + adCache.Add(adid, &theAd) + return &theAd, nil +} + +// AmGetRandomAd gets a random ad from the +func AmGetRandomAd(ctx context.Context) (*Advert, error) { + var num int + err := amdb.GetContext(ctx, &num, "SELECT COUNT(*) FROM adverts") + if err != nil { + return nil, err + } + v1, err := crand.Int(crand.Reader, big.NewInt(int64(num))) + if err != nil { + return nil, err + } + return AmGetAd(ctx, int32(v1.Int64())+1) +} diff --git a/database/base.go b/database/base.go index f9162cd..170b438 100644 --- a/database/base.go +++ b/database/base.go @@ -28,6 +28,7 @@ func SetupDb() (func(), error) { db, err := sqlx.Open(config.GlobalConfig.Database.Driver, config.GlobalConfig.Database.Dsn) if err == nil { amdb = db + setupAdCache() setupUserCache() setupContactsCache() setupCommunityCache() diff --git a/docs/MISSINGFUNCS.md b/docs/MISSINGFUNCS.md index b2cb793..fe1634a 100644 --- a/docs/MISSINGFUNCS.md +++ b/docs/MISSINGFUNCS.md @@ -11,7 +11,7 @@ _(italicized items can be deferred)_ - _Calendar (top menu link)_ - _Chat (top menu link)_ - _Documentation (top menu link)_ -- Quote banner rotation +- ~~Quote banner rotation~~ - ~~Sysadmin Menu:~~ - ~~Edit Global Properties~~ - ~~View/Edit IP Address Bans~~ diff --git a/main.go b/main.go index 93e61af..81e9d63 100644 --- a/main.go +++ b/main.go @@ -64,6 +64,7 @@ func setupEcho() *echo.Echo { } e.Match(GetAndPost, "/TODO/*", ui.AmWrap(NotImplPage), uiset...) e.GET("/img/*", ui.AmServeImage) + e.GET("/images/*", ui.AmServeImage) if config.GlobalConfig.Rendering.VeniceCompatibleImageURLs { e.GET("/venice/imagedata/:id", ui.AmServeVeniceCompatibleImage) } diff --git a/ui/adbanners/Brown.gif b/ui/adbanners/Brown.gif new file mode 100644 index 0000000..68f0996 Binary files /dev/null and b/ui/adbanners/Brown.gif differ diff --git a/ui/adbanners/Caine.gif b/ui/adbanners/Caine.gif new file mode 100644 index 0000000..112832d Binary files /dev/null and b/ui/adbanners/Caine.gif differ diff --git a/ui/adbanners/Frost.gif b/ui/adbanners/Frost.gif new file mode 100644 index 0000000..13fe7f6 Binary files /dev/null and b/ui/adbanners/Frost.gif differ diff --git a/ui/adbanners/Keller.gif b/ui/adbanners/Keller.gif new file mode 100644 index 0000000..99ee82d Binary files /dev/null and b/ui/adbanners/Keller.gif differ diff --git a/ui/adbanners/Letterman.gif b/ui/adbanners/Letterman.gif new file mode 100644 index 0000000..11af0dc Binary files /dev/null and b/ui/adbanners/Letterman.gif differ diff --git a/ui/adbanners/Pooh.gif b/ui/adbanners/Pooh.gif new file mode 100644 index 0000000..3b433fa Binary files /dev/null and b/ui/adbanners/Pooh.gif differ diff --git a/ui/adbanners/Shakespeare.gif b/ui/adbanners/Shakespeare.gif new file mode 100644 index 0000000..a03139e Binary files /dev/null and b/ui/adbanners/Shakespeare.gif differ diff --git a/ui/adbanners/Thomas.gif b/ui/adbanners/Thomas.gif new file mode 100644 index 0000000..496be40 Binary files /dev/null and b/ui/adbanners/Thomas.gif differ diff --git a/ui/adbanners/WolinskiTeamwork.gif b/ui/adbanners/WolinskiTeamwork.gif new file mode 100644 index 0000000..9cddd4f Binary files /dev/null and b/ui/adbanners/WolinskiTeamwork.gif differ diff --git a/ui/adbanners/Wonder.gif b/ui/adbanners/Wonder.gif new file mode 100644 index 0000000..7669e49 Binary files /dev/null and b/ui/adbanners/Wonder.gif differ diff --git a/ui/adbanners/bonaparte.gif b/ui/adbanners/bonaparte.gif new file mode 100644 index 0000000..bdd5cd0 Binary files /dev/null and b/ui/adbanners/bonaparte.gif differ diff --git a/ui/adbanners/buscaglia.gif b/ui/adbanners/buscaglia.gif new file mode 100644 index 0000000..8cf616c Binary files /dev/null and b/ui/adbanners/buscaglia.gif differ diff --git a/ui/adbanners/dana.gif b/ui/adbanners/dana.gif new file mode 100644 index 0000000..dccb6d8 Binary files /dev/null and b/ui/adbanners/dana.gif differ diff --git a/ui/adbanners/deadpoets.gif b/ui/adbanners/deadpoets.gif new file mode 100644 index 0000000..1840274 Binary files /dev/null and b/ui/adbanners/deadpoets.gif differ diff --git a/ui/adbanners/ford.gif b/ui/adbanners/ford.gif new file mode 100644 index 0000000..4f3fd33 Binary files /dev/null and b/ui/adbanners/ford.gif differ diff --git a/ui/adbanners/karen.gif b/ui/adbanners/karen.gif new file mode 100644 index 0000000..f7d8482 Binary files /dev/null and b/ui/adbanners/karen.gif differ diff --git a/ui/adbanners/lynett.gif b/ui/adbanners/lynett.gif new file mode 100644 index 0000000..cd57a4c Binary files /dev/null and b/ui/adbanners/lynett.gif differ diff --git a/ui/adbanners/mcauliffe.gif b/ui/adbanners/mcauliffe.gif new file mode 100644 index 0000000..69a256a Binary files /dev/null and b/ui/adbanners/mcauliffe.gif differ diff --git a/ui/adbanners/midler.gif b/ui/adbanners/midler.gif new file mode 100644 index 0000000..f21824a Binary files /dev/null and b/ui/adbanners/midler.gif differ diff --git a/ui/adbanners/sophocles.gif b/ui/adbanners/sophocles.gif new file mode 100644 index 0000000..8b0ff4a Binary files /dev/null and b/ui/adbanners/sophocles.gif differ diff --git a/ui/adbanners/talbert.gif b/ui/adbanners/talbert.gif new file mode 100644 index 0000000..7927862 Binary files /dev/null and b/ui/adbanners/talbert.gif differ diff --git a/ui/adbanners/torvalds.gif b/ui/adbanners/torvalds.gif new file mode 100644 index 0000000..d63a5c9 Binary files /dev/null and b/ui/adbanners/torvalds.gif differ diff --git a/ui/adbanners/wonka.gif b/ui/adbanners/wonka.gif new file mode 100644 index 0000000..823bc04 Binary files /dev/null and b/ui/adbanners/wonka.gif differ diff --git a/ui/adbanners/worf.gif b/ui/adbanners/worf.gif new file mode 100644 index 0000000..e7e7cb3 Binary files /dev/null and b/ui/adbanners/worf.gif differ diff --git a/ui/images.go b/ui/images.go index 8223202..3491517 100644 --- a/ui/images.go +++ b/ui/images.go @@ -34,6 +34,9 @@ import ( //go:embed static_images/* var static_images embed.FS +//go:embed adbanners/* +var ad_banners embed.FS + // Constants for default photo sizes. const ( UserPhotoWidth = 100 @@ -71,6 +74,12 @@ func AmServeImage(c echo.Context) error { if err == nil { return c.Blob(http.StatusOK, mimeTypeFromFilename(components[3]), b) } + case "ads/": + var b []byte + b, err = ad_banners.ReadFile(filepath.Join("adbanners", components[3])) + if err == nil { + return c.Blob(http.StatusOK, mimeTypeFromFilename(components[3]), b) + } case "store/": var id int id, err = strconv.Atoi(components[3]) diff --git a/ui/render_wrap.go b/ui/render_wrap.go index ada7b24..ebaba3b 100644 --- a/ui/render_wrap.go +++ b/ui/render_wrap.go @@ -16,6 +16,7 @@ import ( "net/http" "time" + "git.erbosoft.com/amy/amsterdam/database" "github.com/klauspost/lctime" "github.com/labstack/echo/v4" log "github.com/sirupsen/logrus" @@ -129,6 +130,17 @@ func AmSendPageData(ctxt echo.Context, amctxt AmContext, command string, data an } menus[1] = AmMenu("fixed") amctxt.VarMap().Set("__leftMenus", menus) + ad, err := database.AmGetRandomAd(ctxt.Request().Context()) + if err != nil { + ad = &database.Advert{ + AdId: -1, + ImagePath: "", + PathStyle: -1, + Caption: nil, + LinkURL: nil, + } + } + amctxt.VarMap().Set("__bannerad", ad) if tmp := amctxt.GetScratch("frame_suppressLogin"); tmp != nil { amctxt.VarMap().Set("__suppressLogin", true) } diff --git a/ui/views/frame.jet b/ui/views/frame.jet index 3cd5299..f1ca745 100644 --- a/ui/views/frame.jet +++ b/ui/views/frame.jet @@ -49,7 +49,16 @@
- + {{ p := __bannerad.ResolvePath() }} + {{ if p != "" }} + {{ if isset(__bannerad.LinkURL) }} + + {{ else }} + + {{ end }} + {{ else }} +   + {{ end }}