parent
4bb495d8f9
commit
dd4fd1f080
@ -1,69 +1,166 @@ |
|||||||
package com.bupt.note.Controller; |
package com.bupt.note.Controller; |
||||||
|
|
||||||
import com.bupt.note.Model.Note; |
import com.bupt.note.Model.Note; |
||||||
|
import com.bupt.note.Model.PaperNote; |
||||||
import com.bupt.note.Repository.NoteRepository; |
import com.bupt.note.Repository.NoteRepository; |
||||||
|
import com.bupt.note.Repository.PaperNoteRepository; |
||||||
import com.bupt.note.ResponseData.ResponseData; |
import com.bupt.note.ResponseData.ResponseData; |
||||||
import com.bupt.note.ResponseData.ResponseDataUtil; |
import com.bupt.note.ResponseData.ResponseDataUtil; |
||||||
import com.bupt.note.ResponseData.ResultEnums; |
import com.bupt.note.dto.DeleteNote; |
||||||
|
import com.bupt.note.dto.NoteForm; |
||||||
|
import com.bupt.note.dto.Page; |
||||||
|
import com.bupt.note.service.FileService; |
||||||
|
import org.apache.commons.lang3.StringUtils; |
||||||
|
import org.slf4j.Logger; |
||||||
|
import org.slf4j.LoggerFactory; |
||||||
import org.springframework.beans.factory.annotation.Autowired; |
import org.springframework.beans.factory.annotation.Autowired; |
||||||
import org.springframework.web.bind.annotation.*; |
import org.springframework.web.bind.annotation.*; |
||||||
|
|
||||||
import java.util.*; |
import javax.persistence.criteria.Predicate; |
||||||
|
import java.io.File; |
||||||
|
import java.io.FileWriter; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.Writer; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
@RestController |
@RestController |
||||||
@RequestMapping(value = "/v1/api/notes") |
@RequestMapping(value = "/v1/api/notes") |
||||||
public class NoteController { |
public class NoteController { |
||||||
|
|
||||||
@Autowired |
@Autowired |
||||||
NoteRepository noteRepository; |
private NoteRepository noteRepository; |
||||||
|
|
||||||
@RequestMapping(value="/{docId}", method=RequestMethod.POST, produces = "application/json") |
@Autowired |
||||||
public ResponseData postNote(@PathVariable long docId, @RequestBody Note note) { |
private PaperNoteRepository paperNoteRepository; |
||||||
// 处理"/notes/[doc_id]"的POST请求,用来创建Note
|
|
||||||
Note newNote; |
|
||||||
note.setDocId(docId); |
|
||||||
try { |
|
||||||
newNote = noteRepository.save(note); |
|
||||||
} catch (Exception e) { |
|
||||||
return ResponseDataUtil.buildError(ResultEnums.ErrNoteSaveFailed); |
|
||||||
} |
|
||||||
return ResponseDataUtil.buildSuccess(newNote); |
|
||||||
} |
|
||||||
|
|
||||||
@RequestMapping(value="/{docId}/{noteId}", method=RequestMethod.DELETE, produces = "application/json") |
@Autowired |
||||||
public ResponseData deleteNote(@PathVariable long docId, @PathVariable long noteId) { |
private FileService fileService; |
||||||
// 处理"/notes/[doc_id]/[note_id]"的DELETE请求,用来删除Note
|
|
||||||
Optional<Note> optionalNote = noteRepository.findById(noteId); |
private Logger logger = LoggerFactory.getLogger(NoteController.class); |
||||||
if (!optionalNote.isPresent()) { |
|
||||||
return ResponseDataUtil.buildError(ResultEnums.ErrNoteDeleteFailed); |
/** |
||||||
|
* 添加笔记 |
||||||
|
* |
||||||
|
* @param noteForm |
||||||
|
* @param username |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
@PostMapping("add") |
||||||
|
public ResponseData addNote(@RequestBody NoteForm noteForm, @CookieValue("user") String username) { |
||||||
|
if (noteForm.getPaperId() != null && StringUtils.isNoneBlank(noteForm.getNoteId(), |
||||||
|
noteForm.getNoteTitle(), noteForm.getNoteContent(), noteForm.getContent())) { |
||||||
|
Note note = noteForm.toNote(); |
||||||
|
note.setUserName(username); |
||||||
|
noteRepository.save(note); |
||||||
|
if (noteRepository.existsById(note.getNoteId())) { |
||||||
|
Writer wr = null; |
||||||
|
try { |
||||||
|
PaperNote paperNote; |
||||||
|
File f; |
||||||
|
if (paperNoteRepository.existsByPaperIdAndUserName(noteForm.getPaperId(), username)) { |
||||||
|
paperNote = paperNoteRepository.findByPaperIdAndUserName(note.getPaperId(), username); |
||||||
|
f = new File(paperNote.getFilePath()); |
||||||
|
} else { |
||||||
|
f = fileService.newFile(); |
||||||
|
paperNote = new PaperNote(); |
||||||
|
paperNote.setFileId(noteForm.getFileId()); |
||||||
|
paperNote.setPaperId(noteForm.getPaperId()); |
||||||
|
paperNote.setFilePath(f.getAbsolutePath()); |
||||||
|
paperNote.setUrlPath("/txt/" + f.getName()); |
||||||
|
paperNote.setUserName(username); |
||||||
|
paperNoteRepository.save(paperNote); |
||||||
|
} |
||||||
|
wr = new FileWriter(f); |
||||||
|
wr.write(noteForm.getContent()); |
||||||
|
return ResponseDataUtil.buildSuccess(note); |
||||||
|
} catch (IOException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
logger.error(String.valueOf(e)); |
||||||
|
} finally { |
||||||
|
if (wr != null) { |
||||||
|
try { |
||||||
|
wr.close(); |
||||||
|
} catch (IOException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
logger.error(String.valueOf(e)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
logger.error(String.format("更新论文id=%d失败", noteForm.getPaperId())); |
||||||
|
return ResponseDataUtil.buildError(); |
||||||
|
|
||||||
|
} else { |
||||||
|
logger.error("保存笔记失败"); |
||||||
|
return ResponseDataUtil.buildError(); |
||||||
|
} |
||||||
|
} else { |
||||||
|
logger.error("表单校验失败"); |
||||||
|
return ResponseDataUtil.buildError(); |
||||||
} |
} |
||||||
Note note = optionalNote.get(); |
|
||||||
noteRepository.delete(note); |
|
||||||
return ResponseDataUtil.buildSuccess("success"); |
|
||||||
} |
} |
||||||
|
|
||||||
@RequestMapping(value="/{docId}/{noteId}", method=RequestMethod.PATCH, produces = "application/json") |
/** |
||||||
public ResponseData updateNote(@PathVariable long docId, @PathVariable long noteId, |
* 查找笔记 |
||||||
@RequestParam String type, @RequestParam String text) { |
* |
||||||
// 处理"/notes/[doc_id]/[note_id]"的PATCH请求,用来更新Note
|
* @param username |
||||||
Optional<Note> optionalNote = noteRepository.findById(noteId); |
* @return |
||||||
if (!optionalNote.isPresent()) { |
*/ |
||||||
return ResponseDataUtil.buildError(ResultEnums.ErrNoteIdNotExist); |
@GetMapping("list/{id}") |
||||||
|
public ResponseData list(@PathVariable("id") Long paperId,@CookieValue("user") String username) { |
||||||
|
try { |
||||||
|
Page<Note> list = new Page<>(); |
||||||
|
list.setTotal(noteRepository.count()); |
||||||
|
List<Note> noteList = noteRepository.findAll((root, criteriaQuery, criteriaBuilder) -> { |
||||||
|
List<Predicate> predicates = new ArrayList<>(); |
||||||
|
if (StringUtils.isNotEmpty(username)) { |
||||||
|
predicates.add(criteriaBuilder.equal(root.get("userName"), username)); |
||||||
|
} |
||||||
|
predicates.add(criteriaBuilder.equal(root.get("paperId"),paperId)); |
||||||
|
return criteriaQuery.where(predicates.toArray(new Predicate[0])).getRestriction(); |
||||||
|
}); |
||||||
|
list.setData(noteList); |
||||||
|
return ResponseDataUtil.buildSuccess(list); |
||||||
|
} catch (Exception e) { |
||||||
|
e.printStackTrace(); |
||||||
|
logger.error(String.valueOf(e)); |
||||||
|
return ResponseDataUtil.buildError(); |
||||||
} |
} |
||||||
Note note = optionalNote.get(); |
|
||||||
note.setText(text); |
|
||||||
note.setType(type); |
|
||||||
noteRepository.save(note); |
|
||||||
return ResponseDataUtil.buildSuccess("success"); |
|
||||||
} |
} |
||||||
|
|
||||||
@RequestMapping(value="/{docId}", method=RequestMethod.GET, produces = "application/json") |
/** |
||||||
public ResponseData getNote(@PathVariable long docId) { |
* 删除笔记 |
||||||
// 处理"/[doc_id]"的GET请求,用来获取某个doc的笔记列表
|
* @return |
||||||
List<Note> noteList = noteRepository.findByDocId(docId); |
*/ |
||||||
if (noteList.isEmpty()) { |
@DeleteMapping("remove") |
||||||
return ResponseDataUtil.buildError(ResultEnums.ErrDocIdNotExist); |
public ResponseData remove(@RequestBody DeleteNote deleteNote,@CookieValue("user") String username){ |
||||||
|
if(deleteNote.getNoteId()!=null&&deleteNote.getPaperId()!=null&&StringUtils.isNotEmpty(deleteNote.getContent())&& |
||||||
|
paperNoteRepository.existsByPaperIdAndUserName(deleteNote.getPaperId(),username)){ |
||||||
|
noteRepository.deleteById(deleteNote.getNoteId()); |
||||||
|
PaperNote paperNote = paperNoteRepository.findByPaperIdAndUserName(deleteNote.getPaperId(), username); |
||||||
|
File f = new File(paperNote.getFilePath()); |
||||||
|
Writer wr = null; |
||||||
|
try { |
||||||
|
wr = new FileWriter(f); |
||||||
|
wr.write(deleteNote.getContent()); |
||||||
|
return ResponseDataUtil.buildSuccess(); |
||||||
|
} catch (IOException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
logger.error(String.valueOf(e)); |
||||||
|
} finally { |
||||||
|
if (wr != null) { |
||||||
|
try { |
||||||
|
wr.close(); |
||||||
|
} catch (IOException e) { |
||||||
|
e.printStackTrace(); |
||||||
|
logger.error(String.valueOf(e)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
logger.error("删除笔记失败"); |
||||||
|
}else{ |
||||||
|
logger.error("删除笔记表单校验失败"); |
||||||
} |
} |
||||||
return ResponseDataUtil.buildSuccess(noteList); |
return ResponseDataUtil.buildError(); |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -1,78 +1,63 @@ |
|||||||
package com.bupt.note.Model; |
package com.bupt.note.Model; |
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty; |
import javax.persistence.Column; |
||||||
|
import javax.persistence.Entity; |
||||||
import javax.persistence.*; |
import javax.persistence.Id; |
||||||
|
import javax.persistence.Table; |
||||||
import java.io.Serializable; |
import java.io.Serializable; |
||||||
|
|
||||||
@Entity |
@Entity |
||||||
@Table(name = "sys_note") |
@Table(name = "sys_note") |
||||||
public class Note implements Serializable { |
public class Note implements Serializable { |
||||||
@Id |
private String noteId; |
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY) |
private String noteTitle; |
||||||
private Long Id; |
private String noteContent; |
||||||
@Column(nullable = false) |
private Long paperId; |
||||||
@JsonProperty(value = "docId") |
private String userName; |
||||||
private Long docId; |
|
||||||
@Column(nullable = false) |
|
||||||
@JsonProperty(value = "userId") |
|
||||||
private Long userId; |
|
||||||
@Column(nullable = false) |
|
||||||
private String type; |
|
||||||
@Column(nullable = false) |
|
||||||
private String text; |
|
||||||
|
|
||||||
public Note() { |
|
||||||
super(); |
|
||||||
} |
|
||||||
|
|
||||||
public Note(long docId, long userId, String type, String text) { |
@Id |
||||||
super(); |
public String getNoteId() { |
||||||
this.docId = docId; |
return noteId; |
||||||
this.userId = userId; |
|
||||||
this.type = type; |
|
||||||
this.text = text; |
|
||||||
} |
|
||||||
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY) |
|
||||||
public Long getId() { |
|
||||||
return Id; |
|
||||||
} |
} |
||||||
|
|
||||||
public void setId(Long id) { |
public void setNoteId(String noteId) { |
||||||
Id = id; |
this.noteId = noteId; |
||||||
} |
} |
||||||
|
|
||||||
public Long getDocId() { |
@Column(nullable = false) |
||||||
return docId; |
public String getNoteTitle() { |
||||||
|
return noteTitle; |
||||||
} |
} |
||||||
|
|
||||||
public void setDocId(Long docId) { |
public void setNoteTitle(String noteTitle) { |
||||||
this.docId = docId; |
this.noteTitle = noteTitle; |
||||||
} |
} |
||||||
|
|
||||||
public Long getUserId() { |
@Column(nullable = false) |
||||||
return userId; |
public String getNoteContent() { |
||||||
|
return noteContent; |
||||||
} |
} |
||||||
|
|
||||||
public void setUserId(Long userId) { |
public void setNoteContent(String noteContent) { |
||||||
this.userId = userId; |
this.noteContent = noteContent; |
||||||
} |
} |
||||||
|
|
||||||
public String getType() { |
@Column(nullable = false) |
||||||
return type; |
public Long getPaperId() { |
||||||
|
return paperId; |
||||||
} |
} |
||||||
|
|
||||||
public void setType(String type) { |
public void setPaperId(Long paperId) { |
||||||
this.type = type; |
this.paperId = paperId; |
||||||
} |
} |
||||||
|
|
||||||
public String getText() { |
@Column(nullable = false) |
||||||
return text; |
public String getUserName() { |
||||||
|
return userName; |
||||||
} |
} |
||||||
|
|
||||||
public void setText(String text) { |
public void setUserName(String userName) { |
||||||
this.text = text; |
this.userName = userName; |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
|
@ -0,0 +1,69 @@ |
|||||||
|
package com.bupt.note.Model; |
||||||
|
|
||||||
|
import javax.persistence.*; |
||||||
|
|
||||||
|
@Entity |
||||||
|
@Table(name = "sys_paper_note") |
||||||
|
public class PaperNote { |
||||||
|
private Long id; |
||||||
|
private Long fileId; |
||||||
|
private Long paperId; |
||||||
|
private String filePath; |
||||||
|
private String urlPath; |
||||||
|
private String userName; |
||||||
|
|
||||||
|
@Id |
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY) |
||||||
|
public Long getId() { |
||||||
|
return id; |
||||||
|
} |
||||||
|
|
||||||
|
public void setId(Long id) { |
||||||
|
this.id = id; |
||||||
|
} |
||||||
|
|
||||||
|
@Column(nullable = false) |
||||||
|
public Long getFileId() { |
||||||
|
return fileId; |
||||||
|
} |
||||||
|
|
||||||
|
public void setFileId(Long fileId) { |
||||||
|
this.fileId = fileId; |
||||||
|
} |
||||||
|
|
||||||
|
@Column(nullable = false) |
||||||
|
public String getFilePath() { |
||||||
|
return filePath; |
||||||
|
} |
||||||
|
|
||||||
|
public void setFilePath(String filePath) { |
||||||
|
this.filePath = filePath; |
||||||
|
} |
||||||
|
|
||||||
|
@Column(nullable = false) |
||||||
|
public String getUrlPath() { |
||||||
|
return urlPath; |
||||||
|
} |
||||||
|
|
||||||
|
public void setUrlPath(String urlPath) { |
||||||
|
this.urlPath = urlPath; |
||||||
|
} |
||||||
|
|
||||||
|
@Column(nullable = false) |
||||||
|
public String getUserName() { |
||||||
|
return userName; |
||||||
|
} |
||||||
|
|
||||||
|
public void setUserName(String userName) { |
||||||
|
this.userName = userName; |
||||||
|
} |
||||||
|
|
||||||
|
@Column(nullable = false) |
||||||
|
public Long getPaperId() { |
||||||
|
return paperId; |
||||||
|
} |
||||||
|
|
||||||
|
public void setPaperId(Long paperId) { |
||||||
|
this.paperId = paperId; |
||||||
|
} |
||||||
|
} |
@ -1,15 +1,9 @@ |
|||||||
package com.bupt.note.Repository; |
package com.bupt.note.Repository; |
||||||
|
|
||||||
import com.bupt.note.Model.Note; |
import com.bupt.note.Model.Note; |
||||||
import org.springframework.data.repository.CrudRepository; |
import org.springframework.data.jpa.repository.JpaRepository; |
||||||
import java.util.List; |
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; |
||||||
import java.util.Optional; |
|
||||||
|
|
||||||
public interface NoteRepository extends CrudRepository<Note, Long> { |
public interface NoteRepository extends JpaRepository<Note, String>, JpaSpecificationExecutor<Note> { |
||||||
@Override |
|
||||||
Optional<Note> findById(Long id); |
|
||||||
|
|
||||||
List<Note> findByDocId(Long id); |
|
||||||
int countByDocId(Long id); |
|
||||||
void deleteById(Long id); |
|
||||||
} |
} |
||||||
|
@ -0,0 +1,9 @@ |
|||||||
|
package com.bupt.note.Repository; |
||||||
|
|
||||||
|
import com.bupt.note.Model.PaperNote; |
||||||
|
import org.springframework.data.jpa.repository.JpaRepository; |
||||||
|
|
||||||
|
public interface PaperNoteRepository extends JpaRepository<PaperNote, Long> { |
||||||
|
boolean existsByPaperIdAndUserName(Long paperId, String userName); |
||||||
|
PaperNote findByPaperIdAndUserName(Long paperId, String userName); |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
package com.bupt.note.dto; |
||||||
|
|
||||||
|
public class DeleteNote { |
||||||
|
private String noteId; |
||||||
|
private Long paperId; |
||||||
|
private String content; |
||||||
|
|
||||||
|
public String getNoteId() { |
||||||
|
return noteId; |
||||||
|
} |
||||||
|
|
||||||
|
public void setNoteId(String noteId) { |
||||||
|
this.noteId = noteId; |
||||||
|
} |
||||||
|
|
||||||
|
public Long getPaperId() { |
||||||
|
return paperId; |
||||||
|
} |
||||||
|
|
||||||
|
public void setPaperId(Long paperId) { |
||||||
|
this.paperId = paperId; |
||||||
|
} |
||||||
|
|
||||||
|
public String getContent() { |
||||||
|
return content; |
||||||
|
} |
||||||
|
|
||||||
|
public void setContent(String content) { |
||||||
|
this.content = content; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,72 @@ |
|||||||
|
package com.bupt.note.dto; |
||||||
|
|
||||||
|
import com.bupt.note.Model.Note; |
||||||
|
|
||||||
|
/** |
||||||
|
* 笔记表单 |
||||||
|
*/ |
||||||
|
public class NoteForm { |
||||||
|
private String noteId; |
||||||
|
private String noteTitle; |
||||||
|
private String noteContent; |
||||||
|
private Long paperId; |
||||||
|
private Long fileId; |
||||||
|
private String content; |
||||||
|
|
||||||
|
public String getNoteId() { |
||||||
|
return noteId; |
||||||
|
} |
||||||
|
|
||||||
|
public void setNoteId(String noteId) { |
||||||
|
this.noteId = noteId; |
||||||
|
} |
||||||
|
|
||||||
|
public String getNoteTitle() { |
||||||
|
return noteTitle; |
||||||
|
} |
||||||
|
|
||||||
|
public void setNoteTitle(String noteTitle) { |
||||||
|
this.noteTitle = noteTitle; |
||||||
|
} |
||||||
|
|
||||||
|
public String getNoteContent() { |
||||||
|
return noteContent; |
||||||
|
} |
||||||
|
|
||||||
|
public void setNoteContent(String noteContent) { |
||||||
|
this.noteContent = noteContent; |
||||||
|
} |
||||||
|
|
||||||
|
public Long getPaperId() { |
||||||
|
return paperId; |
||||||
|
} |
||||||
|
|
||||||
|
public void setPaperId(Long paperId) { |
||||||
|
this.paperId = paperId; |
||||||
|
} |
||||||
|
|
||||||
|
public String getContent() { |
||||||
|
return content; |
||||||
|
} |
||||||
|
|
||||||
|
public void setContent(String content) { |
||||||
|
this.content = content; |
||||||
|
} |
||||||
|
|
||||||
|
public Long getFileId() { |
||||||
|
return fileId; |
||||||
|
} |
||||||
|
|
||||||
|
public void setFileId(Long fileId) { |
||||||
|
this.fileId = fileId; |
||||||
|
} |
||||||
|
|
||||||
|
public Note toNote(){ |
||||||
|
Note note=new Note(); |
||||||
|
note.setPaperId(this.paperId); |
||||||
|
note.setNoteContent(this.noteContent); |
||||||
|
note.setNoteTitle(this.noteTitle); |
||||||
|
note.setNoteId(this.noteId); |
||||||
|
return note; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
package com.bupt.note.dto; |
||||||
|
|
||||||
|
public class QueryNote { |
||||||
|
private Long currentPage; |
||||||
|
|
||||||
|
public Long getCurrentPage() { |
||||||
|
return currentPage; |
||||||
|
} |
||||||
|
|
||||||
|
public void setCurrentPage(Long currentPage) { |
||||||
|
this.currentPage = currentPage; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
package com.bupt.note.service; |
||||||
|
|
||||||
|
import org.slf4j.Logger; |
||||||
|
import org.slf4j.LoggerFactory; |
||||||
|
import org.springframework.beans.factory.annotation.Value; |
||||||
|
import org.springframework.stereotype.Service; |
||||||
|
import org.springframework.util.ResourceUtils; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.FileNotFoundException; |
||||||
|
import java.net.URLDecoder; |
||||||
|
import java.nio.charset.StandardCharsets; |
||||||
|
import java.util.UUID; |
||||||
|
|
||||||
|
@Service |
||||||
|
public class FileService { |
||||||
|
|
||||||
|
@Value("${spring.resources.static-locations}") |
||||||
|
private String txtPath; |
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(FileService.class); |
||||||
|
|
||||||
|
public File newFile() throws FileNotFoundException { |
||||||
|
File txtDir = new File(URLDecoder.decode(ResourceUtils.getURL("classpath:").getPath(), StandardCharsets.UTF_8) + txtPath.replace("classpath:/", "")); |
||||||
|
if (!txtDir.exists() && txtDir.mkdirs()) { |
||||||
|
logger.info("成功初始化上传论文目录:" + txtDir.getAbsolutePath()); |
||||||
|
} |
||||||
|
return new File(txtDir, UUID.randomUUID() + ".txt"); |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
package com.bupt.note; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
|
||||||
|
public class MyTest { |
||||||
|
@Test |
||||||
|
public void test1(){ |
||||||
|
System.out.println(String.format("%d=%d",1,1L)); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue