001/*
002 *  Copyright 2025 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.workspaces.activities.activitystream;
017
018import java.time.ZonedDateTime;
019import java.util.Arrays;
020import java.util.Collections;
021import java.util.Date;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.stream.Collectors;
027
028import org.apache.avalon.framework.component.Component;
029import org.apache.avalon.framework.context.Context;
030import org.apache.avalon.framework.context.ContextException;
031import org.apache.avalon.framework.context.Contextualizable;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.avalon.framework.service.Serviceable;
035import org.apache.cocoon.components.ContextHelper;
036import org.apache.cocoon.environment.Request;
037import org.apache.commons.lang3.StringUtils;
038
039import org.ametys.core.user.CurrentUserProvider;
040import org.ametys.core.user.UserIdentity;
041import org.ametys.core.userpref.UserPreferencesException;
042import org.ametys.core.userpref.UserPreferencesManager;
043import org.ametys.core.util.DateUtils;
044import org.ametys.plugins.workspaces.project.ProjectManager;
045import org.ametys.plugins.workspaces.project.modules.WorkspaceModule;
046import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint;
047import org.ametys.plugins.workspaces.project.objects.Project;
048import org.ametys.web.WebHelper;
049
050/**
051 * Component to return a map for action to get the activities for activity stream
052 */
053public class ActivityStreamServiceComponent implements Serviceable, Component, Contextualizable
054{
055    /** The avalon role */
056    public static final String ROLE = ActivityStreamServiceComponent.class.getName();
057    
058    private ActivityStreamClientInteraction _activityStream;
059    private ProjectManager _projectManager;
060    private UserPreferencesManager _userPrefManager;
061    private CurrentUserProvider _currentUserProvider;
062    private WorkspaceModuleExtensionPoint _moduleEP;
063    private Context _context;
064
065    
066    public void service(ServiceManager manager) throws ServiceException
067    {
068        _activityStream = (ActivityStreamClientInteraction) manager.lookup(ActivityStreamClientInteraction.ROLE);
069        _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE);
070        _userPrefManager = (UserPreferencesManager) manager.lookup(UserPreferencesManager.ROLE);
071        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
072        _moduleEP = (WorkspaceModuleExtensionPoint) manager.lookup(WorkspaceModuleExtensionPoint.ROLE);
073    }
074    
075    public void contextualize(Context context) throws ContextException
076    {
077        _context = context;
078    }
079    
080    /**
081     * Get the activity stream of the current user. In project context, only the activities of the current project are returned, unless allActivities is true.
082     * @param limit The max number of results
083     * @param allActivities Set to true to force to get activities from all projects of the current user, even if we are in a project context
084     * @param markAsRead If true mark the activities as read
085     * @return The activity stream
086     * @throws UserPreferencesException If an error occurs while marking activities as read
087     */
088    public Map<String, Object> getActivityStream(int limit, boolean allActivities, boolean markAsRead) throws UserPreferencesException
089    {
090        Request request = ContextHelper.getRequest(_context);
091        
092        // The filtered modules
093        Set<String> activityTypes = _getFilteredActivityTypes(request);
094        
095        // The filtered projects' categories
096        Set<String> categories = _getFilteredCategories(request);
097
098        // The filtered projects
099        Set<String> projects = _getFilteredProjects(request);
100
101        // Fultext search
102        String pattern = request.getParameter("pattern");
103        
104        // Dante range
105        ZonedDateTime fromDate = null;
106        String fromDateAsStr = request.getParameter("fromDate");
107        if (StringUtils.isNotEmpty(fromDateAsStr))
108        {
109            fromDate = DateUtils.parseZonedDateTime(fromDateAsStr);
110        }
111        ZonedDateTime untilDate = null;
112        String untilDateAsStr = request.getParameter("untilDate");
113        if (StringUtils.isNotEmpty(untilDateAsStr))
114        {
115            untilDate = DateUtils.parseZonedDateTime(untilDateAsStr);
116        }
117        
118        String siteName = WebHelper.getSiteName(request);
119        
120        List<String> currentProjectNames = _projectManager.getProjectsForSite(siteName);
121        Project currentProject = !currentProjectNames.isEmpty() ? _projectManager.getProject(currentProjectNames.get(0)) : null;
122
123        List<Map<String, Object>> activities;
124        if (currentProject == null || allActivities)
125        {
126            // Get activity stream of all current user's projects
127            activities = _activityStream.getActivitiesForCurrentUser(pattern, projects, categories, activityTypes, fromDate, untilDate, limit);
128        }
129        else
130        {
131            // Get activity stream  of current project only
132            activities = _activityStream.getActivitiesForCurrentUser(Collections.singleton(currentProject), activityTypes, fromDate, untilDate, pattern, limit);
133        }
134        
135        UserIdentity user = _currentUserProvider.getUser();
136        if (markAsRead && user != null)
137        {
138            // Mark as read in user prefs
139            String userPrefContext = ActivityStreamClientInteraction.ACTIVITY_STREAM_USER_PREF_CONTEXT;
140            if (currentProject != null && !allActivities)
141            {
142                // if we are in a project, use its name as context
143                userPrefContext += "/" + currentProject.getName();
144            }
145            _userPrefManager.setUserPreferences(user, userPrefContext, Map.of(), Map.of(ActivityStreamClientInteraction.ACTIVITY_STREAM_USER_PREF_LAST_UPDATE, DateUtils.dateToString(new Date())));
146        }
147        
148        Map<String, Object> result = new HashMap<>();
149        // FIXME this should be rename activities
150        result.put("events", activities);
151        
152        return result;
153    }
154    
155    private Set<String> _getFilteredActivityTypes(Request request)
156    {
157        String[] moduleIds = request.getParameterValues("modules");
158        if (moduleIds != null)
159        {
160            return Arrays.stream(moduleIds)
161                    .filter(StringUtils::isNotEmpty)
162                    .map(id -> _moduleEP.<WorkspaceModule>getModule(id))
163                    .map(WorkspaceModule::getAllowedEventTypes)
164                    .flatMap(Set::stream)
165                    .collect(Collectors.toSet());
166        }
167        else
168        {
169            return Set.of();
170        }
171    }
172    
173    private Set<String> _getFilteredCategories(Request request)
174    {
175        String[] categoryIds = request.getParameterValues("categories");
176        if (categoryIds != null)
177        {
178            return Arrays.stream(categoryIds)
179                    .filter(StringUtils::isNotEmpty)
180                    .collect(Collectors.toSet());
181        }
182        else
183        {
184            return Set.of();
185        }
186    }
187    
188    private Set<String> _getFilteredProjects(Request request)
189    {
190        String[] projectIds = request.getParameterValues("projects");
191        if (projectIds != null)
192        {
193            return Arrays.stream(projectIds)
194                    .filter(StringUtils::isNotEmpty)
195                    .collect(Collectors.toSet());
196        }
197        else
198        {
199            return Set.of();
200        }
201    }
202}