diff --git a/.mega-linter.yml b/.mega-linter.yml index db81778f..1cbcd11a 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -8,5 +8,7 @@ DISABLE_LINTERS: - REPOSITORY_DEVSKIM - REPOSITORY_TRIVY - COPYPASTE_JSCPD + - PYTHON_PYRIGHT + - PYTHON_PYLINT FILTER_REGEX_EXCLUDE: "(src/main/resources/static|script)" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..f884024a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,34 @@ +version: "3.8" + +services: + mock-suggestion: + build: + context: ./mock/suggestion + dockerfile: Dockerfile + container_name: mock-suggestion + ports: + - "5001:5000" + networks: + - cg-network + + postgres: + image: postgres:latest + container_name: postgres + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_DB: commonground + ports: + - "5432:5432" + networks: + - cg-network + volumes: + - postgres_data:/var/lib/postgresql/data + +networks: + cg-network: + driver: bridge + +volumes: + postgres_data: + driver: local diff --git a/mock/suggestion/Dockerfile b/mock/suggestion/Dockerfile new file mode 100644 index 00000000..623bbde2 --- /dev/null +++ b/mock/suggestion/Dockerfile @@ -0,0 +1,14 @@ +FROM python:3.9-slim +LABEL authors="yichen" + +WORKDIR /app + +COPY app.py . +COPY config.py . +COPY requirements.txt . + +RUN pip install --no-cache-dir -r requirements.txt + +EXPOSE 5000 + +CMD ["python", "app.py"] diff --git a/mock/suggestion/app.py b/mock/suggestion/app.py new file mode 100644 index 00000000..9302d3e3 --- /dev/null +++ b/mock/suggestion/app.py @@ -0,0 +1,93 @@ +from flask import Flask, request, jsonify +from flask_cors import CORS +import json +from config import KEYWORDS, REPLACEMENTS + +app = Flask(__name__) +CORS(app) + + +def highlight_text(text): + """Scan text for predefined keywords and highlight them with tags.""" + highlighted_text = text + suggestions = [] + counter = 1 + + for category, words in KEYWORDS.items(): + for word in words: + if word in highlighted_text: + tag = f"{word}" + highlighted_text = highlighted_text.replace(word, tag, 1) + suggestions.append({ + "message": f"{highlighted_text}", + "feedback": + f"This phrase falls under {category}. Consider rewording.", + "replacement": + REPLACEMENTS.get(word, f"Consider rewording '{word}'") + }) + counter += 1 + + return highlighted_text, suggestions + + +@app.route('/api/mock-text-suggestion', methods=['POST']) +def mock_text_suggestion(): + """Mock API to analyze the original text input.""" + data = request.json + text = data.get("text", "") + + if not text: + return jsonify({"error": "No text provided"}), 400 + + highlighted_text, suggestions = highlight_text(text) + + response = { + "text": text, + "suggestions": suggestions + } + + return app.response_class( + response=json.dumps(response, ensure_ascii=False), + status=200, + mimetype="application/json" + ) + + +@app.route('/api/mock-edited-text-suggestion', methods=['POST']) +def mock_edited_text_suggestion(): + """Mock API to analyze user-edited text.""" + data = request.json + text = data.get("text", "") + edited_text = data.get("edited_text", "") + suggestions = data.get("suggestions", []) + + if not text or not edited_text: + return jsonify({"error": "No text or edited_text provided"}), 400 + + # Mock the text-suggestion for edited highlight text + new_suggestions = [] + for suggestion in suggestions: + if "" in suggestion["message"]: # Only provide suggestion for `` + new_suggestions.append({ + "edited_message": suggestion["edited_message"].replace( + "來佔便宜", + "可能尋求更好的經濟機會" + ), + "feedback": "‘佔便宜’ 可能帶有偏見性表述,建議以更客觀的方式描述難民的處境。", + "replacement": "部分難民可能尋求更好的經濟機會與生活條件" + }) + + response = { + "edited_text": edited_text, + "suggestions": new_suggestions + } + + return app.response_class( + response=json.dumps(response, ensure_ascii=False), + status=200, + mimetype="application/json" + ) + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000, debug=False) # nosec B104 diff --git a/mock/suggestion/config.py b/mock/suggestion/config.py new file mode 100644 index 00000000..8f4330b9 --- /dev/null +++ b/mock/suggestion/config.py @@ -0,0 +1,19 @@ +KEYWORDS = { + "煽動性詞語": ["邪惡", "愚蠢", "可恥", "荒謬", "噁心", "醜陋"], + "極端化詞彙": ["所有", "沒有例外", "絕對", "從來沒有", "毫無疑問"], + "缺乏事實支持": ["明顯", "大家都知道", "鐵一般的事實"], + "人身攻擊": ["蠢蛋", "白痴", "低能", "垃圾", "腦殘", "智障"], + "偏見 & 刻板印象": ["某某派", "某某黨", "年輕人都", "女人就是", "男人就是"], + "引戰語言": ["你不懂啦", "這麼簡單的道理", "睜眼說瞎話", "你果然是"], + "缺乏邏輯推理": ["我猜測", "應該是這樣", "想都不用想", "這一定是"] +} + +REPLACEMENTS = { + "愚蠢": "可能存在問題", + "所有": "許多", + "明顯": "根據數據顯示", + "垃圾": "缺乏邏輯", + "年輕人都": "某些年輕人可能傾向於", + "你不懂啦": "這點可能需要進一步討論", + "我猜測": "根據目前資訊,我推測" +} diff --git a/mock/suggestion/requirements.txt b/mock/suggestion/requirements.txt new file mode 100644 index 00000000..70114e3f --- /dev/null +++ b/mock/suggestion/requirements.txt @@ -0,0 +1,2 @@ +flask +flask-cors \ No newline at end of file diff --git a/src/main/java/tw/commonground/backend/service/suggestion/SuggestionController.java b/src/main/java/tw/commonground/backend/service/suggestion/SuggestionController.java new file mode 100644 index 00000000..7425b77d --- /dev/null +++ b/src/main/java/tw/commonground/backend/service/suggestion/SuggestionController.java @@ -0,0 +1,27 @@ +package tw.commonground.backend.service.suggestion; + +import tw.commonground.backend.service.suggestion.dto.*; +import org.springframework.web.bind.annotation.*; +import tw.commonground.backend.shared.tracing.Traced; + +@Traced +@RestController +@RequestMapping("/api") +public class SuggestionController { + + private final SuggestionService suggestionService; + + public SuggestionController(SuggestionService suggestionService) { + this.suggestionService = suggestionService; + } + + @PostMapping("/text-suggestion") + public TextSuggestionResponse getTextSuggestions(@RequestBody TextSuggestionRequest request) { + return suggestionService.getTextSuggestions(request); + } + + @PostMapping("/edited-text-suggestion") + public EditedTextSuggestionResponse getEditedTextSuggestions(@RequestBody EditedTextSuggestionRequest request) { + return suggestionService.getEditedTextSuggestions(request); + } +} diff --git a/src/main/java/tw/commonground/backend/service/suggestion/SuggestionService.java b/src/main/java/tw/commonground/backend/service/suggestion/SuggestionService.java new file mode 100644 index 00000000..7f221195 --- /dev/null +++ b/src/main/java/tw/commonground/backend/service/suggestion/SuggestionService.java @@ -0,0 +1,43 @@ +package tw.commonground.backend.service.suggestion; + +import org.springframework.beans.factory.annotation.Value; +import tw.commonground.backend.service.suggestion.dto.*; +import org.springframework.http.*; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +@Service +public class SuggestionService { + + private final RestTemplate restTemplate = new RestTemplate(); + + @Value("${mockApiUrl}") + private String mockApiUrl; + + public SuggestionService() { } + + + public TextSuggestionResponse getTextSuggestions(TextSuggestionRequest request) { + String url = mockApiUrl + "/api/mock-text-suggestion"; + + return makePostRequest(url, request, TextSuggestionResponse.class); + } + + + public EditedTextSuggestionResponse getEditedTextSuggestions(EditedTextSuggestionRequest request) { + String url = mockApiUrl + "/api/mock-edited-text-suggestion"; + + return makePostRequest(url, request, EditedTextSuggestionResponse.class); + } + + + private T makePostRequest(String url, Object request, Class responseType) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity requestEntity = new HttpEntity<>(request, headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, responseType); + + return response.getBody(); + } +} diff --git a/src/main/java/tw/commonground/backend/service/suggestion/dto/EditedTextRequestSuggestionItem.java b/src/main/java/tw/commonground/backend/service/suggestion/dto/EditedTextRequestSuggestionItem.java new file mode 100644 index 00000000..58d942d2 --- /dev/null +++ b/src/main/java/tw/commonground/backend/service/suggestion/dto/EditedTextRequestSuggestionItem.java @@ -0,0 +1,21 @@ +package tw.commonground.backend.service.suggestion.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Getter +@Setter +@Builder +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class EditedTextRequestSuggestionItem { + private String message; + + private String feedback; + + private String replacement; + + @JsonProperty("edited_message") + private String editedMessage; +} diff --git a/src/main/java/tw/commonground/backend/service/suggestion/dto/EditedTextResponseSuggestionItem.java b/src/main/java/tw/commonground/backend/service/suggestion/dto/EditedTextResponseSuggestionItem.java new file mode 100644 index 00000000..5041088f --- /dev/null +++ b/src/main/java/tw/commonground/backend/service/suggestion/dto/EditedTextResponseSuggestionItem.java @@ -0,0 +1,19 @@ +package tw.commonground.backend.service.suggestion.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +@Getter +@Setter +@Builder +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class EditedTextResponseSuggestionItem { + private String feedback; + + private String replacement; + + @JsonProperty("edited_message") + private String editedMessage; +} diff --git a/src/main/java/tw/commonground/backend/service/suggestion/dto/EditedTextSuggestionRequest.java b/src/main/java/tw/commonground/backend/service/suggestion/dto/EditedTextSuggestionRequest.java new file mode 100644 index 00000000..eec8c7d9 --- /dev/null +++ b/src/main/java/tw/commonground/backend/service/suggestion/dto/EditedTextSuggestionRequest.java @@ -0,0 +1,21 @@ +package tw.commonground.backend.service.suggestion.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +import java.util.List; + +@Getter +@Setter +@Builder +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class EditedTextSuggestionRequest { + private String text; + + @JsonProperty("edited_text") + private String editedText; + + private List suggestions; +} diff --git a/src/main/java/tw/commonground/backend/service/suggestion/dto/EditedTextSuggestionResponse.java b/src/main/java/tw/commonground/backend/service/suggestion/dto/EditedTextSuggestionResponse.java new file mode 100644 index 00000000..e4343ff9 --- /dev/null +++ b/src/main/java/tw/commonground/backend/service/suggestion/dto/EditedTextSuggestionResponse.java @@ -0,0 +1,19 @@ +package tw.commonground.backend.service.suggestion.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; + +import java.util.List; + +@Getter +@Setter +@Builder +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class EditedTextSuggestionResponse { + @JsonProperty("edited_text") + private String editedText; + + private List suggestions; +} diff --git a/src/main/java/tw/commonground/backend/service/suggestion/dto/TextResponseSuggestionItem.java b/src/main/java/tw/commonground/backend/service/suggestion/dto/TextResponseSuggestionItem.java new file mode 100644 index 00000000..01fad687 --- /dev/null +++ b/src/main/java/tw/commonground/backend/service/suggestion/dto/TextResponseSuggestionItem.java @@ -0,0 +1,17 @@ +package tw.commonground.backend.service.suggestion.dto; + +import lombok.*; + +@Getter +@Setter +@Builder +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class TextResponseSuggestionItem { + private String message; + + private String feedback; + + private String replacement; +} diff --git a/src/main/java/tw/commonground/backend/service/suggestion/dto/TextSuggestionRequest.java b/src/main/java/tw/commonground/backend/service/suggestion/dto/TextSuggestionRequest.java new file mode 100644 index 00000000..32443666 --- /dev/null +++ b/src/main/java/tw/commonground/backend/service/suggestion/dto/TextSuggestionRequest.java @@ -0,0 +1,13 @@ +package tw.commonground.backend.service.suggestion.dto; + +import lombok.*; + +@Getter +@Setter +@Builder +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class TextSuggestionRequest { + private String text; +} diff --git a/src/main/java/tw/commonground/backend/service/suggestion/dto/TextSuggestionResponse.java b/src/main/java/tw/commonground/backend/service/suggestion/dto/TextSuggestionResponse.java new file mode 100644 index 00000000..0f93b7b7 --- /dev/null +++ b/src/main/java/tw/commonground/backend/service/suggestion/dto/TextSuggestionResponse.java @@ -0,0 +1,17 @@ +package tw.commonground.backend.service.suggestion.dto; + +import lombok.*; + +import java.util.List; + +@Getter +@Setter +@Builder +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class TextSuggestionResponse { + private String text; + + private List suggestions; +} diff --git a/src/main/java/tw/commonground/backend/service/suggestion/dto/package-info.java b/src/main/java/tw/commonground/backend/service/suggestion/dto/package-info.java new file mode 100644 index 00000000..98679e7b --- /dev/null +++ b/src/main/java/tw/commonground/backend/service/suggestion/dto/package-info.java @@ -0,0 +1 @@ +package tw.commonground.backend.service.suggestion.dto; diff --git a/src/main/java/tw/commonground/backend/service/suggestion/package-info.java b/src/main/java/tw/commonground/backend/service/suggestion/package-info.java new file mode 100644 index 00000000..e7b40a5c --- /dev/null +++ b/src/main/java/tw/commonground/backend/service/suggestion/package-info.java @@ -0,0 +1 @@ +package tw.commonground.backend.service.suggestion;