You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
189 lines
6.8 KiB
189 lines
6.8 KiB
/* © SRSoftware 2025 */ |
|
package de.srsoftware.umbrella.notes; |
|
|
|
import static de.srsoftware.umbrella.core.ConnectionProvider.connect; |
|
import static de.srsoftware.umbrella.core.Constants.*; |
|
import static de.srsoftware.umbrella.core.Constants.FULLTEXT; |
|
import static de.srsoftware.umbrella.core.ModuleRegistry.userService; |
|
import static de.srsoftware.umbrella.core.Paths.SEARCH; |
|
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_UNPROCESSABLE; |
|
import static de.srsoftware.umbrella.core.Util.mapValues; |
|
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; |
|
import static de.srsoftware.umbrella.notes.Constants.CONFIG_DATABASE; |
|
|
|
import com.sun.net.httpserver.HttpExchange; |
|
import de.srsoftware.configuration.Configuration; |
|
import de.srsoftware.tools.Path; |
|
import de.srsoftware.tools.SessionToken; |
|
import de.srsoftware.umbrella.core.BaseHandler; |
|
import de.srsoftware.umbrella.core.ModuleRegistry; |
|
import de.srsoftware.umbrella.core.api.NoteService; |
|
import de.srsoftware.umbrella.core.exceptions.UmbrellaException; |
|
import de.srsoftware.umbrella.core.model.Note; |
|
import de.srsoftware.umbrella.core.model.Token; |
|
import de.srsoftware.umbrella.core.model.UmbrellaUser; |
|
import java.io.IOException; |
|
import java.time.LocalDateTime; |
|
import java.util.Arrays; |
|
import java.util.Map; |
|
import java.util.Optional; |
|
import java.util.stream.Collectors; |
|
|
|
public class NoteModule extends BaseHandler implements NoteService { |
|
private final NotesDb notesDb; |
|
|
|
public NoteModule(Configuration config) { |
|
super(); |
|
var dbFile = config.get(CONFIG_DATABASE).orElseThrow(() -> missingFieldException(CONFIG_DATABASE)); |
|
notesDb = new SqliteDb(connect(dbFile)); |
|
ModuleRegistry.add(this); |
|
} |
|
|
|
@Override |
|
public void deleteEntity(String module, String entityId) { |
|
notesDb.deleteEntity(module,entityId); |
|
} |
|
|
|
@Override |
|
public boolean doDelete(Path path, HttpExchange ex) throws IOException { |
|
addCors(ex); |
|
try { |
|
Optional<Token> token = SessionToken.from(ex).map(Token::of); |
|
var user = userService().loadUser(token); |
|
if (user.isEmpty()) return unauthorized(ex); |
|
var head = path.pop(); |
|
if (head == null) throw unprocessable("Module missing in path."); |
|
// try { |
|
var noteId = Long.parseLong(head); |
|
var note = notesDb.load(noteId); |
|
if (note.authorId() != user.get().id()) throw forbidden("You are not allowed to delete notes of another user"); |
|
return sendContent(ex, notesDb.delete(noteId)); |
|
/* } catch (NumberFormatException ignored) { |
|
var module = head; |
|
head = path.pop(); |
|
long entityId = Long.parseLong(head); |
|
notesDb.deleteEntity(module,entityId); |
|
return sendContent(ex, entityId); |
|
}*/ |
|
} catch (NumberFormatException e){ |
|
return sendContent(ex,HTTP_UNPROCESSABLE,"Entity id missing in path."); |
|
} catch (UmbrellaException e){ |
|
return send(ex,e); |
|
} |
|
} |
|
|
|
@Override |
|
public boolean doGet(Path path, HttpExchange ex) throws IOException { |
|
addCors(ex); |
|
try { |
|
var user = userService().refreshSession(ex); |
|
if (user.isEmpty()) return unauthorized(ex); |
|
var module = path.pop(); |
|
return switch (module){ |
|
case null -> sendContent(ex,getUserNotes(ex,user.get())); |
|
default -> sendContent(ex,getEntityNotes(module,path.pop())); |
|
}; |
|
} catch (NumberFormatException e){ |
|
return sendContent(ex,HTTP_UNPROCESSABLE,"Entity id missing in path."); |
|
} catch (UmbrellaException e){ |
|
return send(ex,e); |
|
} |
|
} |
|
|
|
private boolean getUserNotes(HttpExchange ex, UmbrellaUser user) throws IOException { |
|
var param = queryParam(ex); |
|
long offset = switch (param.get(OFFSET)){ |
|
case String s -> Long.parseLong(s); |
|
case Number n -> n.longValue(); |
|
case null, default -> 0; |
|
}; |
|
long limit = switch (param.get(LIMIT)){ |
|
case String s -> Long.parseLong(s); |
|
case Number n -> n.longValue(); |
|
case null, default -> 100; |
|
}; |
|
|
|
var notes = notesDb.list(user.id(),offset,limit); |
|
return sendContent(ex,addUsers(notes)); |
|
} |
|
|
|
private Map<String, Object> addUsers(Map<Long, Note> notes) { |
|
var authors = notes.values().stream().map(Note::authorId).distinct().map(userService()::loadUser).collect(Collectors.toMap(UmbrellaUser::id,UmbrellaUser::toMap)); |
|
return Map.of("notes",mapValues(notes),"authors",authors); |
|
} |
|
|
|
@Override |
|
public boolean doPatch(Path path, HttpExchange ex) throws IOException { |
|
addCors(ex); |
|
try { |
|
Optional<Token> token = SessionToken.from(ex).map(Token::of); |
|
var user = userService().refreshSession(ex); |
|
if (user.isEmpty()) return unauthorized(ex); |
|
var head = path.pop(); |
|
long noteId = Long.parseLong(head); |
|
String text = body(ex); |
|
if (text.isBlank()) throw missingFieldException("Note text"); |
|
var note = notesDb.load(noteId); |
|
if (note.authorId() != user.get().id()) throw forbidden("You are not allowed to edit notes of another user!"); |
|
note = new Note(note.id(),note.module(),note.entityId(),note.authorId(),text,LocalDateTime.now()); |
|
note = save(note); |
|
return sendContent(ex,note); |
|
} catch (NumberFormatException e) { |
|
return sendContent(ex, HTTP_UNPROCESSABLE, "Entity id missing in path."); |
|
} catch (UmbrellaException e){ |
|
return send(ex,e); |
|
} |
|
} |
|
|
|
@Override |
|
public boolean doPost(Path path, HttpExchange ex) throws IOException { |
|
addCors(ex); |
|
try { |
|
Optional<Token> token = SessionToken.from(ex).map(Token::of); |
|
var user = userService().loadUser(token); |
|
if (user.isEmpty()) return unauthorized(ex); |
|
var module = path.pop(); |
|
if (SEARCH.equals(module)) return postSearch(ex,user.get()); |
|
if (module == null) throw unprocessable("Module missing in path."); |
|
var entityId = path.pop(); |
|
if (entityId == null || entityId.isBlank()) throw missingFieldException(ENTITY_ID); |
|
String text = body(ex); |
|
if (text.isBlank()) throw missingFieldException("Note text"); |
|
var note = new Note(0,module,entityId,user.get().id(),text, LocalDateTime.now()); |
|
note = save(note); |
|
return sendContent(ex, note); |
|
} catch (NumberFormatException e) { |
|
return sendContent(ex, HTTP_UNPROCESSABLE, "Entity id missing in path."); |
|
} catch (UmbrellaException e) { |
|
return send(ex, e); |
|
} |
|
} |
|
|
|
public Map<String,Object> getEntityNotes(String module, String entityId) throws UmbrellaException{ |
|
return addUsers(getNotes(module, entityId)); |
|
} |
|
|
|
@Override |
|
public Map<Long, Note> getNotes(String module, String entityId) throws UmbrellaException { |
|
return notesDb.list(module, entityId); |
|
} |
|
|
|
private boolean postSearch(HttpExchange ex, UmbrellaUser user) throws IOException { |
|
var json = json(ex); |
|
if (!(json.has(KEY) && json.get(KEY) instanceof String key)) throw missingFieldException(KEY); |
|
var keys = Arrays.asList(key.split(" ")); |
|
var fulltext = json.has(FULLTEXT) && json.get(FULLTEXT) instanceof Boolean val && val; |
|
var notes = notesDb.find(user.id(),keys,fulltext); |
|
return sendContent(ex,mapValues(notes)); |
|
} |
|
|
|
@Override |
|
public Note save(Note note) { |
|
return notesDb.save(note); |
|
} |
|
|
|
@Override |
|
public void updateId(String module, Object oldId, Object newId) { |
|
notesDb.updateId(module,oldId,newId); |
|
} |
|
}
|
|
|