ServiceNews.java

/*
 * Copyright (C) 2000 - 2025 Silverpeas
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * As a special exception to the terms and conditions of version 3.0 of
 * the GPL, you may redistribute this Program in connection with Free/Libre
 * Open Source Software ("FLOSS") applications as described in Silverpeas's
 * FLOSS exception.  You should have received a copy of the text describing
 * the FLOSS exception, and it is also available here:
 * "https://www.silverpeas.org/legal/floss_exception.html"
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.silverpeas.mobile.server.services;

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.silverpeas.components.quickinfo.model.News;
import org.silverpeas.components.quickinfo.model.QuickInfoService;
import org.silverpeas.components.quickinfo.model.QuickInfoServiceProvider;
import org.silverpeas.core.ResourceReference;
import org.silverpeas.core.admin.service.OrganizationController;
import org.silverpeas.core.annotation.WebService;
import org.silverpeas.core.date.Period;
import org.silverpeas.core.io.media.image.thumbnail.control.ThumbnailController;
import org.silverpeas.core.util.file.FileItem;
import org.silverpeas.core.util.file.FileUtil;
import org.silverpeas.core.web.rs.annotation.Authorized;
import org.silverpeas.mobile.server.common.LocalDiskFileItem;
import org.silverpeas.mobile.server.helpers.DataURLHelper;
import org.silverpeas.mobile.server.services.helpers.NewsHelper;
import org.silverpeas.mobile.shared.dto.news.NewsDTO;

import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import static java.time.OffsetDateTime.ofInstant;
import static org.silverpeas.core.cache.service.VolatileIdentifierProvider.newVolatileIntegerIdentifierOn;

@WebService
@Authorized
@Path(ServiceNews.PATH + "/{appId}")
public class ServiceNews extends AbstractRestWebService {

  @PathParam("appId")
  private String componentId;

  static final String PATH = "mobile/news";

  private SimpleDateFormat sdf = new SimpleDateFormat("dd MMMM yyyy");

  @PUT
  @Consumes(MediaType.APPLICATION_JSON)
  @Path("update")
  public void updateNews(NewsDTO news) {

    try {
      News n = QuickInfoService.get().getNews(news.getIdNews());
      n.setTitle(news.getTitle());
      n.setImportant(news.getImportant());
      n.setDescription(news.getDescription());
      n.setContentToStore(news.getContent());
      n.setUpdaterId(getUser().getId());
      setVignette(news, n);
      setPeriod(news, n);
      QuickInfoService.get().update(n, null, null, false);
    } catch (Exception e) {
      throw new WebApplicationException(e);
    }
  }

  @POST
  @Consumes(MediaType.APPLICATION_JSON)
  @Produces(MediaType.APPLICATION_JSON)
  @Path("create")
  public NewsDTO createNews(NewsDTO news) {

    try {
      QuickInfoService service = QuickInfoServiceProvider.getQuickInfoService();

      News n = News.builder()
              .setTitleAndDescription(news.getTitle(), news.getDescription())
              .build();
      n.setDraft();
      n.setComponentInstanceId(componentId);
      // Dummy identifiers
      n.setId("volatileId@" + getComponentId() + "@" + System.nanoTime());
      n.setPublicationId(newVolatileIntegerIdentifierOn(getComponentId()));
      n.getPublication().getPK().setId(n.getPublicationId());

      n.setImportant(news.getImportant());
      n.setDescription(news.getDescription());
      n.setCreatorId(getUser().getId());
      n.setUpdaterId(getUser().getId());
      News n2 = service.create(n);
      n2.setContentToStore(news.getContent());

      setPeriod(news, n2);

      // vignette
      setVignette(news, n2);
      n2.markAsModified();
      service.update(n2, null, null, false);
      return NewsHelper.getInstance().populate(n2, true);
    } catch (Exception e) {
      throw new WebApplicationException(e);
    }
  }

  private void setPeriod(NewsDTO news, News n2) throws ParseException {
    Date start = new Date();
    Date end = null;
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
    if (!news.getStartDate().isEmpty()) start = format.parse(news.getStartDate());
    if (!news.getEndDate().isEmpty()) end = format.parse(news.getEndDate());
    n2.setVisibilityPeriod(getPeriod(start, end));
  }

  private void setVignette(NewsDTO news, News n2) throws Exception {
    // An existing thumbnail URL is not a new upload; only decode image data URLs.
    if (news.getVignette() != null && news.getVignette().startsWith("data:image/")) {
      File f = DataURLHelper.createPictureFromUrlData(news.getVignette(), n2.getComponentInstanceId() + "-" + n2.getId());
      LocalDiskFileItem item = new LocalDiskFileItem(f, FileUtil.getMimeType(f.getAbsolutePath()));
      List<FileItem> items = new ArrayList<>();
      items.add(item);
      ThumbnailController.processThumbnail(new ResourceReference(n2.getPublicationId(), componentId), items);
    }
  }

  private Period getPeriod(Date begin, Date end) {
    return Period.betweenNullable(
            begin != null ? ofInstant(begin.toInstant(), ZoneId.systemDefault()) : null,
            end != null ? ofInstant(end.toInstant(), ZoneId.systemDefault()) : null
    );
  }

  @GET
  @Produces(MediaType.APPLICATION_JSON)
  @Path("all")
  public List<NewsDTO> getNews() {

    String[] profiles =
        OrganizationController.get().getUserProfiles(getUser().getId(), componentId);
    boolean managerAccess = isManagerOrPublisher(profiles);
    List<News> news = NewsHelper.getInstance().getNewsByAppId(componentId, managerAccess, getUser().getId());
    return NewsHelper.getInstance().populate(news, managerAccess);
  }

  @GET
  @Produces(MediaType.APPLICATION_JSON)
  @Path("byPubId/{pubId}")
  public NewsDTO getNewsByPubId(@PathParam("pubId") String pubId) {
    News n = QuickInfoService.get().getNewsByForeignId(pubId);
    return NewsHelper.getInstance().populate(n, false);
  }


  private boolean isManagerOrPublisher(final String[] profiles) {
    for (String profile : profiles) {
      if (profile.equals("admin")) {
        return true;
      }
      if (profile.equals("publisher")) {
        return true;
      }
    }
    return false;
  }

  @Override
  protected String getResourceBasePath() {
    return PATH;
  }

  @Override
  public String getComponentId() {
    return this.componentId;
  }

  public SimpleDateFormat getSdf() {
    return sdf;
  }

  public void setSdf(SimpleDateFormat sdf) {
    this.sdf = sdf;
  }
}