landed all of confererence activity reports functionality
This commit is contained in:
+52
-2
@@ -446,13 +446,63 @@ func ConfCustom(ctxt ui.AmContext) (string, any) {
|
|||||||
func ConfReports(ctxt ui.AmContext) (string, any) {
|
func ConfReports(ctxt ui.AmContext) (string, any) {
|
||||||
comm := ctxt.CurrentCommunity()
|
comm := ctxt.CurrentCommunity()
|
||||||
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
conf := ctxt.GetScratch("currentConference").(*database.Conference)
|
||||||
|
myLevel := ctxt.GetScratch("levelInConference").(uint16)
|
||||||
|
if !conf.TestPermission("Conference.Read", myLevel) {
|
||||||
|
return "error", ENOPERM
|
||||||
|
}
|
||||||
|
|
||||||
ctxt.VarMap().Set("confName", conf.Name)
|
ctxt.VarMap().Set("confName", conf.Name)
|
||||||
ctxt.VarMap().Set("selfLink", fmt.Sprintf("/comm/%s/conf/%s/activity", comm.Alias, ctxt.GetScratch("currentAlias")))
|
ctxt.VarMap().Set("selfLink", fmt.Sprintf("/comm/%s/conf/%s/activity", comm.Alias, ctxt.GetScratch("currentAlias")))
|
||||||
|
|
||||||
if ctxt.HasParameter("r") {
|
if ctxt.HasParameter("r") {
|
||||||
// TODO: generate report here
|
// generate a report
|
||||||
return "error", nil
|
reportMode := ctxt.Parameter("r")
|
||||||
|
var reportTypeSel int
|
||||||
|
switch reportMode {
|
||||||
|
case "post":
|
||||||
|
reportTypeSel = database.ActivityReportPosters
|
||||||
|
case "read":
|
||||||
|
reportTypeSel = database.ActivityReportReaders
|
||||||
|
default:
|
||||||
|
return "error", EINVAL
|
||||||
|
}
|
||||||
|
ctxt.VarMap().Set("reportMode", reportMode)
|
||||||
|
if ctxt.HasParameter("t") {
|
||||||
|
topicId := ctxt.QueryParamInt("t", -1)
|
||||||
|
if topicId > 0 {
|
||||||
|
topic, err := database.AmGetTopic(ctxt.Ctx(), int32(topicId))
|
||||||
|
if err != nil {
|
||||||
|
return "error", err
|
||||||
|
}
|
||||||
|
ctxt.VarMap().Set("topic", topic)
|
||||||
|
report, err := topic.GetActivity(ctxt.Ctx(), reportTypeSel)
|
||||||
|
if err != nil {
|
||||||
|
return "error", err
|
||||||
|
}
|
||||||
|
ctxt.VarMap().Set("report", report)
|
||||||
|
if reportTypeSel == database.ActivityReportPosters {
|
||||||
|
ctxt.SetFrameTitle("Users Posting in Topic " + topic.Name)
|
||||||
|
} else {
|
||||||
|
ctxt.SetFrameTitle("Users Reading Topic " + topic.Name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "error", "Invalid topic ID specified"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
report, err := conf.GetActivity(ctxt.Ctx(), reportTypeSel)
|
||||||
|
if err != nil {
|
||||||
|
return "error", err
|
||||||
|
}
|
||||||
|
ctxt.VarMap().Set("report", report)
|
||||||
|
if reportTypeSel == database.ActivityReportPosters {
|
||||||
|
ctxt.SetFrameTitle("Users Posting in Conference " + conf.Name)
|
||||||
|
} else {
|
||||||
|
ctxt.SetFrameTitle("Users Reading Conference " + conf.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "framed", "conf_reportout.jet"
|
||||||
} else {
|
} else {
|
||||||
|
// generate the listing
|
||||||
topicList, err := database.AmListTopics(ctxt.Ctx(), conf.ConfId, ctxt.CurrentUserId(), database.TopicViewAll, database.TopicSortNumber, true)
|
topicList, err := database.AmListTopics(ctxt.Ctx(), conf.ConfId, ctxt.CurrentUserId(), database.TopicViewAll, database.TopicSortNumber, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "error", err
|
return "error", err
|
||||||
|
|||||||
@@ -674,6 +674,58 @@ func (c *Conference) RemoveCustomBlocks(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ActivityReport is used to get activity reports from the conference or topic.
|
||||||
|
type ActivityReport struct {
|
||||||
|
Uid int32
|
||||||
|
Username string
|
||||||
|
LastRead *time.Time
|
||||||
|
LastPost *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activity report types.
|
||||||
|
const (
|
||||||
|
ActivityReportPosters = 0 // report on all posters
|
||||||
|
ActivityReportReaders = 1 // report on all readers
|
||||||
|
)
|
||||||
|
|
||||||
|
/* GetActivity returns a list of ActivityReport objects detailing the conference activity.
|
||||||
|
* Parameters:
|
||||||
|
* ctx - Standard Go context value.
|
||||||
|
* reportType - Determines which report to generate:
|
||||||
|
* ActivityReportPosters - Report on all posters in the conference.
|
||||||
|
* ActivityReportReaders - Report on all readers in the conference.
|
||||||
|
* Returns:
|
||||||
|
* List of ActivityReport objects detailing the conference activity.
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func (c *Conference) GetActivity(ctx context.Context, reportType int) ([]ActivityReport, error) {
|
||||||
|
var myfield string
|
||||||
|
switch reportType {
|
||||||
|
case ActivityReportPosters:
|
||||||
|
myfield = "s.last_post"
|
||||||
|
case ActivityReportReaders:
|
||||||
|
myfield = "s.last_read"
|
||||||
|
default:
|
||||||
|
return nil, errors.New("invalid report type parameter")
|
||||||
|
}
|
||||||
|
sql := fmt.Sprintf(`SELECT s.uid, u.username, s.last_read, s.last_post FROM confsettings s, users u WHERE u.uid = s.uid
|
||||||
|
AND s.confid = ? AND u.is_anon = 0 AND ISNULL(%s) = 0 ORDER BY %s DESC`, myfield, myfield)
|
||||||
|
rs, err := amdb.QueryContext(ctx, sql, c.ConfId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rc := make([]ActivityReport, 0)
|
||||||
|
for rs.Next() {
|
||||||
|
var cur ActivityReport
|
||||||
|
err = rs.Scan(&(cur.Uid), &(cur.Username), &(cur.LastRead), &(cur.LastPost))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rc = append(rc, cur)
|
||||||
|
}
|
||||||
|
return rc, nil
|
||||||
|
}
|
||||||
|
|
||||||
/* AmGetConference returns a conference given its ID.
|
/* AmGetConference returns a conference given its ID.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* ctx - Standard Go context value.
|
* ctx - Standard Go context value.
|
||||||
|
|||||||
@@ -290,6 +290,44 @@ func (t *Topic) GetSubscribers(ctx context.Context) ([]int32, error) {
|
|||||||
return rc, err
|
return rc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* GetActivity returns a list of ActivityReport objects detailing the topic activity.
|
||||||
|
* Parameters:
|
||||||
|
* ctx - Standard Go context value.
|
||||||
|
* reportType - Determines which report to generate:
|
||||||
|
* ActivityReportPosters - Report on all posters in the topic.
|
||||||
|
* ActivityReportReaders - Report on all readers in the topic.
|
||||||
|
* Returns:
|
||||||
|
* List of ActivityReport objects detailing the topic activity.
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func (t *Topic) GetActivity(ctx context.Context, reportType int) ([]ActivityReport, error) {
|
||||||
|
var myfield string
|
||||||
|
switch reportType {
|
||||||
|
case ActivityReportPosters:
|
||||||
|
myfield = "s.last_post"
|
||||||
|
case ActivityReportReaders:
|
||||||
|
myfield = "s.last_read"
|
||||||
|
default:
|
||||||
|
return nil, errors.New("invalid report type parameter")
|
||||||
|
}
|
||||||
|
sql := fmt.Sprintf(`SELECT s.uid, u.username, s.last_read, s.last_post FROM topicsettings s, users u WHERE u.uid = s.uid
|
||||||
|
AND s.topicid = ? AND u.is_anon = 0 AND ISNULL(%s) = 0 ORDER BY %s DESC`, myfield, myfield)
|
||||||
|
rs, err := amdb.QueryContext(ctx, sql, t.TopicId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rc := make([]ActivityReport, 0)
|
||||||
|
for rs.Next() {
|
||||||
|
var cur ActivityReport
|
||||||
|
err = rs.Scan(&(cur.Uid), &(cur.Username), &(cur.LastRead), &(cur.LastPost))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rc = append(rc, cur)
|
||||||
|
}
|
||||||
|
return rc, nil
|
||||||
|
}
|
||||||
|
|
||||||
// backgroundPurgeTopic removes all posts from a topic that's been deleted.
|
// backgroundPurgeTopic removes all posts from a topic that's been deleted.
|
||||||
func backgroundPurgeTopic(ctx context.Context, topicid int32) error {
|
func backgroundPurgeTopic(ctx context.Context, topicid int32) error {
|
||||||
success := false
|
success := false
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ _(italicized items can be deferred)_
|
|||||||
- ~~Add alias~~
|
- ~~Add alias~~
|
||||||
- ~~Manage members~~
|
- ~~Manage members~~
|
||||||
- ~~Custom appearance~~
|
- ~~Custom appearance~~
|
||||||
- Activity reports
|
- ~~Activity reports~~
|
||||||
- E-mail
|
- E-mail
|
||||||
- Export Messages
|
- Export Messages
|
||||||
- Import Messages
|
- Import Messages
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ import (
|
|||||||
// EBUTTON is the standard error for an unknown button.
|
// EBUTTON is the standard error for an unknown button.
|
||||||
var EBUTTON error = errors.New("invalid or unknown button pressed")
|
var EBUTTON error = errors.New("invalid or unknown button pressed")
|
||||||
|
|
||||||
|
// EINVAL is the standard error for an invalid parameter.
|
||||||
|
var EINVAL error = errors.New("invalid parameter to operation")
|
||||||
|
|
||||||
// ELOGIN is the standard error for not being logged in
|
// ELOGIN is the standard error for not being logged in
|
||||||
var ELOGIN error = errors.New("you are not logged in")
|
var ELOGIN error = errors.New("you are not logged in")
|
||||||
|
|
||||||
|
|||||||
+13
-1
@@ -146,7 +146,19 @@ func userContactInfo(a jet.Arguments) reflect.Value {
|
|||||||
|
|
||||||
// displayDateTime formats a date and time value.
|
// displayDateTime formats a date and time value.
|
||||||
func displayDateTime(a jet.Arguments) reflect.Value {
|
func displayDateTime(a jet.Arguments) reflect.Value {
|
||||||
timeval := a.Get(0).Convert(reflect.TypeFor[time.Time]()).Interface().(time.Time)
|
var timeval time.Time
|
||||||
|
p0 := a.Get(0)
|
||||||
|
if p0.CanConvert(reflect.TypeFor[time.Time]()) {
|
||||||
|
timeval = p0.Convert(reflect.TypeFor[time.Time]()).Interface().(time.Time)
|
||||||
|
} else if p0.CanConvert(reflect.TypeFor[*time.Time]()) {
|
||||||
|
ptr := p0.Convert(reflect.TypeFor[*time.Time]()).Interface().(*time.Time)
|
||||||
|
if ptr == nil {
|
||||||
|
return reflect.ValueOf("<<NIL>>")
|
||||||
|
}
|
||||||
|
timeval = *ptr
|
||||||
|
} else {
|
||||||
|
return reflect.ValueOf("<<BOGUS>>")
|
||||||
|
}
|
||||||
ctxt := a.Get(1).Convert(reflect.TypeFor[AmContext]()).Interface().(AmContext)
|
ctxt := a.Get(1).Convert(reflect.TypeFor[AmContext]()).Interface().(AmContext)
|
||||||
prefs, err := ctxt.CurrentUser().Prefs(ctxt.Ctx())
|
prefs, err := ctxt.CurrentUser().Prefs(ctxt.Ctx())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
{*
|
||||||
|
* 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/.
|
||||||
|
*}
|
||||||
|
<div class="p-4">
|
||||||
|
<!-- Page Title -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<div class="flex items-baseline gap-3 mb-2">
|
||||||
|
{{ if isset(topic) }}
|
||||||
|
{{ if reportMode == "post" }}
|
||||||
|
<h1 class="text-blue-800 text-4xl font-bold">Posters in Topic:</h1>
|
||||||
|
{{ else }}
|
||||||
|
<h1 class="text-blue-800 text-4xl font-bold">Readers in Topic:</h1>
|
||||||
|
{{ end }}
|
||||||
|
<h2 class="text-blue-800 text-2xl font-bold">{{ topic.Name | raw }}</h2>
|
||||||
|
{{ else }}
|
||||||
|
{{ if reportMode == "post" }}
|
||||||
|
<h1 class="text-blue-800 text-4xl font-bold">Posters in Conference:</h1>
|
||||||
|
{{ else }}
|
||||||
|
<h1 class="text-blue-800 text-4xl font-bold">Readers in Conference:</h1>
|
||||||
|
{{ end }}
|
||||||
|
<h2 class="text-blue-800 text-2xl font-bold">{{ confName }}</h2>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
<hr class="border-2 border-gray-400 w-4/5 mb-6">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Return Link -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<a href="{{ selfLink }}" class="text-blue-700 hover:text-blue-900 text-sm flex items-center gap-2 w-fit">
|
||||||
|
<span>←</span>
|
||||||
|
Return to Conference Reports Menu
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Reports Table -->
|
||||||
|
<div class="max-w-4xl">
|
||||||
|
<div class="bg-white border border-gray-300 rounded-lg overflow-hidden">
|
||||||
|
<table class="w-full">
|
||||||
|
<thead class="bg-gray-100 border-b-2 border-gray-300">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">User Name</th>
|
||||||
|
{{ if reportMode == "post" }}
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">Last Posted</th>
|
||||||
|
<th class="px-4 py-3 text-center text-xs font-bold text-gray-700 uppercase tracking-wider">Last Read</th>
|
||||||
|
{{ else }}
|
||||||
|
<th class="px-4 py-3 text-center text-xs font-bold text-gray-700 uppercase tracking-wider">Last Read</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">Last Posted</th>
|
||||||
|
{{ end }}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-gray-200">
|
||||||
|
{{ if len(report) == 0 }}
|
||||||
|
<tr class="hover:bg-blue-50 bg-blue-100">
|
||||||
|
<td class="px-4 py-3 text-gray-700 text-center text-sm" colspan="3">
|
||||||
|
{{ if isset(topic) }}
|
||||||
|
{{ if reportMode == "post" }}
|
||||||
|
<i>No posters found in topic "{{ topic.Name | raw }}."</i>
|
||||||
|
{{ else }}
|
||||||
|
<i>No readers found in topic "{{ topic.Name | raw }}."</i>
|
||||||
|
{{ end }}
|
||||||
|
{{ else }}
|
||||||
|
{{ if reportMode == "post" }}
|
||||||
|
<i>No posters found in conference "{{ confName }}."</i>
|
||||||
|
{{ else }}
|
||||||
|
<i>No readers found in conference "{{ confName }}."</i>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{ else }}
|
||||||
|
{{ range _, r := report }}
|
||||||
|
<tr class="hover:bg-blue-50 bg-blue-100">
|
||||||
|
<td class="px-4 py-3 text-sm">
|
||||||
|
<a href="/user/{{ r.Username }}" class="text-blue-700 hover:text-blue-900 font-medium" target="_blank">{{ r.Username }}</a>
|
||||||
|
</td>
|
||||||
|
{{ if reportMode == "post" }}
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-800">{{ DisplayDateTime(r.LastPost, .) }}</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-800">{{ DisplayDateTime(r.LastRead, .) }}</td>
|
||||||
|
{{ else }}
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-800">{{ DisplayDateTime(r.LastRead, .) }}</td>
|
||||||
|
{{ if isset(r.LastPost) }}
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-800">{{ DisplayDateTime(r.LastPost, .) }}</td>
|
||||||
|
{{ else }}
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-800">Never</td>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user