put the ad (quote) banners in - last major feature in MISSINGFUNCS
@@ -110,6 +110,7 @@ type AmConfig struct {
|
|||||||
WorkerTasks int `yaml:"workerTasks"`
|
WorkerTasks int `yaml:"workerTasks"`
|
||||||
} `yaml:"queues"`
|
} `yaml:"queues"`
|
||||||
Caches struct {
|
Caches struct {
|
||||||
|
Ads int `yaml:"ads"`
|
||||||
Communities int `yaml:"communities"`
|
Communities int `yaml:"communities"`
|
||||||
CommunityProps int `yaml:"communityProps"`
|
CommunityProps int `yaml:"communityProps"`
|
||||||
Conferences int `yaml:"conferences"`
|
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.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.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.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.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.CommunityProps = overlayInt(loaded.Tuning.Caches.CommunityProps, defaults.Tuning.Caches.CommunityProps)
|
||||||
dest.Tuning.Caches.Conferences = overlayInt(loaded.Tuning.Caches.Conferences, defaults.Tuning.Caches.Conferences)
|
dest.Tuning.Caches.Conferences = overlayInt(loaded.Tuning.Caches.Conferences, defaults.Tuning.Caches.Conferences)
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ tuning:
|
|||||||
ipBans: 32
|
ipBans: 32
|
||||||
workerTasks: 128
|
workerTasks: 128
|
||||||
caches:
|
caches:
|
||||||
|
ads: 30
|
||||||
communities: 50
|
communities: 50
|
||||||
communityProps: 100
|
communityProps: 100
|
||||||
conferences: 100
|
conferences: 100
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ func SetupDb() (func(), error) {
|
|||||||
db, err := sqlx.Open(config.GlobalConfig.Database.Driver, config.GlobalConfig.Database.Dsn)
|
db, err := sqlx.Open(config.GlobalConfig.Database.Driver, config.GlobalConfig.Database.Dsn)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
amdb = db
|
amdb = db
|
||||||
|
setupAdCache()
|
||||||
setupUserCache()
|
setupUserCache()
|
||||||
setupContactsCache()
|
setupContactsCache()
|
||||||
setupCommunityCache()
|
setupCommunityCache()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ _(italicized items can be deferred)_
|
|||||||
- _Calendar (top menu link)_
|
- _Calendar (top menu link)_
|
||||||
- _Chat (top menu link)_
|
- _Chat (top menu link)_
|
||||||
- _Documentation (top menu link)_
|
- _Documentation (top menu link)_
|
||||||
- Quote banner rotation
|
- ~~Quote banner rotation~~
|
||||||
- ~~Sysadmin Menu:~~
|
- ~~Sysadmin Menu:~~
|
||||||
- ~~Edit Global Properties~~
|
- ~~Edit Global Properties~~
|
||||||
- ~~View/Edit IP Address Bans~~
|
- ~~View/Edit IP Address Bans~~
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ func setupEcho() *echo.Echo {
|
|||||||
}
|
}
|
||||||
e.Match(GetAndPost, "/TODO/*", ui.AmWrap(NotImplPage), uiset...)
|
e.Match(GetAndPost, "/TODO/*", ui.AmWrap(NotImplPage), uiset...)
|
||||||
e.GET("/img/*", ui.AmServeImage)
|
e.GET("/img/*", ui.AmServeImage)
|
||||||
|
e.GET("/images/*", ui.AmServeImage)
|
||||||
if config.GlobalConfig.Rendering.VeniceCompatibleImageURLs {
|
if config.GlobalConfig.Rendering.VeniceCompatibleImageURLs {
|
||||||
e.GET("/venice/imagedata/:id", ui.AmServeVeniceCompatibleImage)
|
e.GET("/venice/imagedata/:id", ui.AmServeVeniceCompatibleImage)
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
@@ -34,6 +34,9 @@ import (
|
|||||||
//go:embed static_images/*
|
//go:embed static_images/*
|
||||||
var static_images embed.FS
|
var static_images embed.FS
|
||||||
|
|
||||||
|
//go:embed adbanners/*
|
||||||
|
var ad_banners embed.FS
|
||||||
|
|
||||||
// Constants for default photo sizes.
|
// Constants for default photo sizes.
|
||||||
const (
|
const (
|
||||||
UserPhotoWidth = 100
|
UserPhotoWidth = 100
|
||||||
@@ -71,6 +74,12 @@ func AmServeImage(c echo.Context) error {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
return c.Blob(http.StatusOK, mimeTypeFromFilename(components[3]), b)
|
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/":
|
case "store/":
|
||||||
var id int
|
var id int
|
||||||
id, err = strconv.Atoi(components[3])
|
id, err = strconv.Atoi(components[3])
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
"github.com/klauspost/lctime"
|
"github.com/klauspost/lctime"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -129,6 +130,17 @@ func AmSendPageData(ctxt echo.Context, amctxt AmContext, command string, data an
|
|||||||
}
|
}
|
||||||
menus[1] = AmMenu("fixed")
|
menus[1] = AmMenu("fixed")
|
||||||
amctxt.VarMap().Set("__leftMenus", menus)
|
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 {
|
if tmp := amctxt.GetScratch("frame_suppressLogin"); tmp != nil {
|
||||||
amctxt.VarMap().Set("__suppressLogin", true)
|
amctxt.VarMap().Set("__suppressLogin", true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,16 @@
|
|||||||
|
|
||||||
<!-- Banner Ad -->
|
<!-- Banner Ad -->
|
||||||
<div class="w-2/5 flex justify-end">
|
<div class="w-2/5 flex justify-end">
|
||||||
<img src="/img/builtin/placeholder_ad.gif" alt="" class="w-96 h-15 mx-2 my-2">
|
{{ p := __bannerad.ResolvePath() }}
|
||||||
|
{{ if p != "" }}
|
||||||
|
{{ if isset(__bannerad.LinkURL) }}
|
||||||
|
<a href="{{ __bannerad.LinkURL }}"><img src="{{ p }}" {{ if isset(__bannerad.Caption) }}alt="{{ __bannerad.Caption }}"{{ end }} class="w-96 h-15 mx-2 my-2"></a>
|
||||||
|
{{ else }}
|
||||||
|
<img src="{{ p }}" {{ if isset(__bannerad.Caption) }}alt="{{ __bannerad.Caption }}"{{ end }} class="w-96 h-15 mx-2 my-2">
|
||||||
|
{{ end }}
|
||||||
|
{{ else }}
|
||||||
|
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||