mirror of
https://github.com/kevin-DL/services.git
synced 2026-01-11 19:04:35 +00:00
Quran API (#205)
* Add basic version of a Quran API * remove examples json swp * fix search translation * add examples json
This commit is contained in:
2
quran/.gitignore
vendored
Normal file
2
quran/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
quran
|
||||
3
quran/Dockerfile
Normal file
3
quran/Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
||||
FROM alpine
|
||||
ADD quran /quran
|
||||
ENTRYPOINT [ "/quran" ]
|
||||
28
quran/Makefile
Normal file
28
quran/Makefile
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
GOPATH:=$(shell go env GOPATH)
|
||||
.PHONY: init
|
||||
init:
|
||||
go get -u github.com/golang/protobuf/proto
|
||||
go get -u github.com/golang/protobuf/protoc-gen-go
|
||||
go get github.com/micro/micro/v3/cmd/protoc-gen-micro
|
||||
go get github.com/micro/micro/v3/cmd/protoc-gen-openapi
|
||||
|
||||
.PHONY: api
|
||||
api:
|
||||
protoc --openapi_out=. --proto_path=. proto/quran.proto
|
||||
|
||||
.PHONY: proto
|
||||
proto:
|
||||
protoc --proto_path=. --micro_out=. --go_out=:. proto/quran.proto
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go build -o quran *.go
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -v ./... -cover
|
||||
|
||||
.PHONY: docker
|
||||
docker:
|
||||
docker build . -t quran:latest
|
||||
6
quran/README.md
Normal file
6
quran/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
The Holy Quran
|
||||
|
||||
# Quran Service
|
||||
|
||||
The Holy Quran powered by [Quran.com](https://quran.api-docs.io)
|
||||
|
||||
206
quran/domain/domain.go
Normal file
206
quran/domain/domain.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
pb "github.com/micro/services/quran/proto"
|
||||
)
|
||||
|
||||
var (
|
||||
// the default tafsir author
|
||||
tafsir = map[int32]string{
|
||||
169: "Tafsir Ibn Kathir",
|
||||
}
|
||||
)
|
||||
|
||||
type Chapter struct {
|
||||
Id int32 `json:"id"`
|
||||
RevelationPlace string `json:"revelation_place"`
|
||||
RevelationOrder int32 `json:"revelation_order"`
|
||||
BismillahPrefix bool `json:"bismillah_pre"`
|
||||
NameSimple string `json:"name_simple"`
|
||||
NameComplex string `json:"name_complex"`
|
||||
NameArabic string `json:"name_arabic"`
|
||||
VersesCount int32 `json:"verses_count"`
|
||||
Pages []int32 `json:"pages"`
|
||||
TranslatedName *TranslatedName `json:"translated_name"`
|
||||
}
|
||||
|
||||
type TranslatedName struct {
|
||||
LanguageName string `json:"language_name"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type TranslationText struct {
|
||||
Id int32 `json:"id"`
|
||||
ResourceId int32 `json:"resource_id"`
|
||||
Text string `json:"text"`
|
||||
ResourceName string `json:"resource_name"`
|
||||
}
|
||||
|
||||
type Translation struct {
|
||||
Text string `json:"text"`
|
||||
LanguageName string `json:"language_name"`
|
||||
}
|
||||
|
||||
type Transliteration struct {
|
||||
Text string `json:"text"`
|
||||
LanguageName string `json:"language_name"`
|
||||
}
|
||||
|
||||
type Tafsir struct {
|
||||
Id int32 `json:"id"`
|
||||
ResourceId int32 `json:"resource_id"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type ChapterInfo struct {
|
||||
Id int32 `json:"id"`
|
||||
ChapterId int32 `json:"chapter_id"`
|
||||
LanguageName string `json:"language_name"`
|
||||
ShortText string `json:"short_text"`
|
||||
Source string `json:"source"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type Pagination struct {
|
||||
PerPage int32 `json:"per_page"`
|
||||
CurrentPage int32 `json:"current_page"`
|
||||
NextPage int32 `json:"next_page"`
|
||||
TotalPages int32 `json:"total_pages"`
|
||||
TotalRecords int32 `json:"total_records"`
|
||||
}
|
||||
|
||||
type Verse struct {
|
||||
Id int32 `json:"id"`
|
||||
VerseNumber int32 `json:"verse_number"`
|
||||
VerseKey string `json:"verse_key"`
|
||||
JuzNumber int32 `json:"juz_number"`
|
||||
HizbNumber int32 `json:"hizb_number"`
|
||||
RubNumber int32 `json:"rub_number"`
|
||||
PageNumber int32 `json:"page_number"`
|
||||
Translations []*TranslationText `json:"translations"`
|
||||
Tafsirs []*Tafsir `json:"tafsirs"`
|
||||
Words []*Word `json:"words"`
|
||||
TextImlaei string `json:"text_imlaei"`
|
||||
}
|
||||
|
||||
type Word struct {
|
||||
Id int32 `json:"id"`
|
||||
Position int32 `json:"position"`
|
||||
AudioUrl string `json:"audio_url"`
|
||||
CharTypeName string `json:"char_type_name"`
|
||||
CodeV1 string `json:"code_v1"`
|
||||
PageNumber int32 `json:"page_number"`
|
||||
LineNumber int32 `json:"line_number"`
|
||||
Text string `json:"text_imlaei"`
|
||||
Code string `json:"code_v2"`
|
||||
Translation *Translation `json:"translation"`
|
||||
Transliteration *Translation `json:"transliteration"`
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
VerseId int32 `json:"verse_id"`
|
||||
VerseKey string `json:"verse_key"`
|
||||
Text string `json:"text"`
|
||||
Translations []*SearchTranslation `json:"translations"`
|
||||
}
|
||||
|
||||
type SearchResults struct {
|
||||
Query string `json:"query"`
|
||||
TotalResults int32 `json:"total_results"`
|
||||
CurrentPage int32 `json:"current_page"`
|
||||
TotalPages int32 `json:"total_pages"`
|
||||
Results []*Result `json:"results"`
|
||||
}
|
||||
|
||||
type SearchTranslation struct {
|
||||
ResourceId int32 `json:"resource_id"`
|
||||
Text string `json:"text"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type VersesByChapter struct {
|
||||
Pagination *Pagination `json:"pagination"`
|
||||
Verses []*Verse `json:"verses"`
|
||||
}
|
||||
|
||||
func VerseToProto(verse *Verse) *pb.Verse {
|
||||
var transliteration []string
|
||||
var translation []string
|
||||
var words []*pb.Word
|
||||
|
||||
for _, word := range verse.Words {
|
||||
words = append(words, &pb.Word{
|
||||
Id: word.Id,
|
||||
Position: word.Position,
|
||||
CharType: word.CharTypeName,
|
||||
Page: word.PageNumber,
|
||||
Line: word.LineNumber,
|
||||
Text: word.Text,
|
||||
Code: word.Code,
|
||||
Translation: word.Translation.Text,
|
||||
Transliteration: word.Transliteration.Text,
|
||||
})
|
||||
|
||||
// skip the end
|
||||
if word.CharTypeName == "end" {
|
||||
continue
|
||||
}
|
||||
|
||||
translation = append(translation, word.Translation.Text)
|
||||
transliteration = append(transliteration, word.Transliteration.Text)
|
||||
}
|
||||
|
||||
var translations []*pb.Translation
|
||||
|
||||
for _, tr := range verse.Translations {
|
||||
translations = append(translations, &pb.Translation{
|
||||
Id: tr.Id,
|
||||
Source: tr.ResourceName,
|
||||
Text: tr.Text,
|
||||
})
|
||||
}
|
||||
|
||||
var interpretations []*pb.Interpretation
|
||||
|
||||
for _, tf := range verse.Tafsirs {
|
||||
interpretations = append(interpretations, &pb.Interpretation{
|
||||
Id: tf.Id,
|
||||
Source: tafsir[tf.ResourceId],
|
||||
Text: tf.Text,
|
||||
})
|
||||
}
|
||||
|
||||
return &pb.Verse{
|
||||
Id: verse.Id,
|
||||
Key: verse.VerseKey,
|
||||
Number: verse.VerseNumber,
|
||||
Page: verse.PageNumber,
|
||||
Text: verse.TextImlaei,
|
||||
TranslatedText: strings.Join(translation, " "),
|
||||
Transliteration: strings.Join(transliteration, " "),
|
||||
Words: words,
|
||||
Translations: translations,
|
||||
Interpretations: interpretations,
|
||||
}
|
||||
}
|
||||
|
||||
func ResultToProto(r *Result) *pb.Result {
|
||||
var translations []*pb.Translation
|
||||
|
||||
for _, tr := range r.Translations {
|
||||
translations = append(translations, &pb.Translation{
|
||||
Id: tr.ResourceId,
|
||||
Source: tr.Name,
|
||||
Text: tr.Text,
|
||||
})
|
||||
}
|
||||
|
||||
return &pb.Result{
|
||||
VerseKey: r.VerseKey,
|
||||
VerseId: r.VerseId,
|
||||
Text: r.Text,
|
||||
Translations: translations,
|
||||
}
|
||||
}
|
||||
142
quran/examples.json
Normal file
142
quran/examples.json
Normal file
@@ -0,0 +1,142 @@
|
||||
{
|
||||
"chapters": [{
|
||||
"title": "List Chapters",
|
||||
"run_check": false,
|
||||
"request": {
|
||||
"language": "en"
|
||||
},
|
||||
"response": {
|
||||
"chapters": [{
|
||||
"id": 1,
|
||||
"verses": 7,
|
||||
"name": "Al-Fatihah",
|
||||
"arabic_name": "الفاتحة",
|
||||
"complex_name": "Al-Fātiĥah",
|
||||
"translated_name": "The Opener",
|
||||
"prefix_bismillah": false,
|
||||
"revelation_place": "makkah",
|
||||
"revelation_order": 5,
|
||||
"pages": [
|
||||
1,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"verses": 286,
|
||||
"name": "Al-Baqarah",
|
||||
"arabic_name": "البقرة",
|
||||
"complex_name": "Al-Baqarah",
|
||||
"translated_name": "The Cow",
|
||||
"prefix_bismillah": true,
|
||||
"revelation_place": "madinah",
|
||||
"revelation_order": 87,
|
||||
"pages": [
|
||||
2,
|
||||
49
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"verses": 200,
|
||||
"name": "Ali 'Imran",
|
||||
"arabic_name": "آل عمران",
|
||||
"complex_name": "Āli `Imrān",
|
||||
"translated_name": "Family of Imran",
|
||||
"prefix_bismillah": true,
|
||||
"revelation_place": "madinah",
|
||||
"revelation_order": 89,
|
||||
"pages": [
|
||||
50,
|
||||
76
|
||||
]
|
||||
}]
|
||||
}
|
||||
}],
|
||||
"summary": [{
|
||||
"title": "Get chapter summary",
|
||||
"run_check": false,
|
||||
"request": {
|
||||
"chapter": 1
|
||||
},
|
||||
"response": {
|
||||
"chapter": 1,
|
||||
"summary": "This Surah is named Al-Fatihah because of its subject matter. Fatihah is that which opens a subject or a book or any other thing. In other words, Al-Fatihah is a sort of preface.",
|
||||
"source": "Sayyid Abul Ala Maududi - Tafhim al-Qur'an - The Meaning of the Quran",
|
||||
"text": "\u003ch2\u003eName\u003c/h2\u003e\u003cp\u003eThis Surah is named Al-Fatihah because of its subject matter. Fatihah is that which opens a subject or a book or any other thing. In other words, Al-Fatihah is a sort of preface.\u003c/p\u003e\u003ch2\u003ePeriod of Revelation\u003c/h2\u003e\u003cp\u003eSurah Al-Fatihah is one of the very earliest Revelations to the Holy Prophet. As a matter of fact, we learn from authentic traditions that it was the first complete Surah that was revealed to Muhammad (Allah's peace be upon him). Before this, only a few miscellaneous verses were revealed which form parts of Alaq, Muzzammil, Muddaththir, etc.\u003c/p\u003e\u003ch2\u003eTheme\u003c/h2\u003e\u003cp\u003eThis Surah is in fact a prayer that Allah has taught to all those who want to make a study of His book. It has been placed at the very beginning of the Quran to teach this lesson to the reader: if you sincerely want to benefit from the Quran, you should offer this prayer to the Lord of the Universe.\u003c/p\u003e\u003cp\u003eThis preface is meant to create a strong desire in the heart of the reader to seek guidance from the Lord of the Universe Who alone can grant it. Thus Al-Fatihah indirectly teaches that the best thing for a man is to pray for guidance to the straight path, to study the Quran with the mental attitude of a seeker searching for the truth, and to recognize the fact that the Lord of the Universe is the source of all knowledge. He should, therefore, begin the study of the Quran with a prayer to Him for guidance.\u003c/p\u003e\u003cp\u003eFrom this theme, it becomes clear that the real relation between Al-Fatihah and the Quran is not that of an introduction to a book but that of a prayer and its answer. Al-Fatihah is the prayer from the servant and the Quran is the answer from the Master to the servant's prayer. The servant prays to Allah to show him guidance and the Master places the whole of the Quran before him in answer to his prayer, as if to say, \"This is the Guidance you begged from Me.\"\u003c/p\u003e"
|
||||
}
|
||||
}],
|
||||
"verses": [{
|
||||
"title": "Get verses of a chapter",
|
||||
"run_check": false,
|
||||
"request": {
|
||||
"chapter": 1
|
||||
},
|
||||
"response": {
|
||||
"chapter": 1,
|
||||
"page": 1,
|
||||
"total_pages": 1,
|
||||
"verses": [{
|
||||
"id": 1,
|
||||
"number": 1,
|
||||
"key": "1:1",
|
||||
"page": 1,
|
||||
"text": "بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ",
|
||||
"transliteration": "bis'mi l-lahi l-raḥmāni l-raḥīmi",
|
||||
"translated_text": "In (the) name (of) Allah the Most Gracious the Most Merciful",
|
||||
"translations": [],
|
||||
"interpretations": [],
|
||||
"words": []
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"number": 2,
|
||||
"key": "1:2",
|
||||
"page": 1,
|
||||
"text": "الْحَمْدُ لِلَّهِ رَبِّ الْعَالَمِينَ",
|
||||
"transliteration": "al-ḥamdu lillahi rabbi l-ʿālamīna",
|
||||
"translated_text": "All praises and thanks (be) to Allah the Lord of the universe",
|
||||
"translations": [],
|
||||
"interpretations": [],
|
||||
"words": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"number": 3,
|
||||
"key": "1:3",
|
||||
"page": 1,
|
||||
"text": "الرَّحْمَٰنِ الرَّحِيمِ",
|
||||
"transliteration": "al-raḥmāni l-raḥīmi",
|
||||
"translated_text": "The Most Gracious the Most Merciful",
|
||||
"translations": [],
|
||||
"interpretations": [],
|
||||
"words": []
|
||||
}]
|
||||
}
|
||||
}],
|
||||
"search": [{
|
||||
"title": "Search the Quran",
|
||||
"run_check": false,
|
||||
"request": {
|
||||
"query": "messenger"
|
||||
},
|
||||
"response": {
|
||||
"query": "messenger",
|
||||
"total_results": 5756,
|
||||
"page": 1,
|
||||
"total_pages": 288,
|
||||
"results": [{
|
||||
"verse_id": 4211,
|
||||
"verse_key": "40:78",
|
||||
"text": "وَلَقَدْ أَرْسَلْنَا رُسُلًا مِّن قَبْلِكَ مِنْهُم مَّن قَصَصْنَا عَلَيْكَ وَمِنْهُم مَّن لَّمْ نَقْصُصْ عَلَيْكَ ۗ وَمَا كَانَ لِرَسُولٍ أَن يَأْتِىَ بِـَٔايَةٍ إِلَّا بِإِذْنِ ٱللَّهِ ۚ فَإِذَا جَآءَ أَمْرُ ٱللَّهِ قُضِىَ بِٱلْحَقِّ وَخَسِرَ هُنَالِكَ ٱلْمُبْطِلُونَ",
|
||||
"translations": [
|
||||
{
|
||||
"id": 171,
|
||||
"source": "Abridged Explanation of the Quran",
|
||||
"text": "O \u003cem\u003eMessenger\u003c/em\u003e! I have sent many \u003cem\u003emessengers\u003c/em\u003e before you to their people, but they rejected them and caused them harm. The \u003cem\u003emessengers\u003c/em\u003e were patient upon the rejection and harm they faced. From among these \u003cem\u003emessengers\u003c/em\u003e are those whom I have related their stories to you, and those whose stories I have not related. It is not correct for a \u003cem\u003emessenger\u003c/em\u003e to bring a sign to his people from his Lord, except if He (may He be glorified) wishes for him to bring it. So, for the disbelievers to demand signs is wrong. When the decree of Allah for victory or judgement in favour of the \u003cem\u003emessengers\u003c/em\u003e against their nations happens, the judgement will be made fairly. The disbelievers will then be destroyed and the \u003cem\u003emessengers\u003c/em\u003e will be saved. At that point when the judgement is made between the servants, the people of falsehood will lose out by having put themselves in a position of doom by committing disbelief."
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
3
quran/generate.go
Normal file
3
quran/generate.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package main
|
||||
|
||||
//go:generate make proto
|
||||
197
quran/handler/quran.go
Normal file
197
quran/handler/quran.go
Normal file
@@ -0,0 +1,197 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/micro/micro/v3/service/errors"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
"github.com/micro/services/pkg/api"
|
||||
"github.com/micro/services/quran/domain"
|
||||
pb "github.com/micro/services/quran/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
// the default api url
|
||||
apiUrl = "https://api.quran.com/api/v4/"
|
||||
// TODO: allow multiple translations
|
||||
// the default translation id
|
||||
translationId = "131,20"
|
||||
// TODO: allow multiple interpretations
|
||||
// the default tafsir id
|
||||
tafsirId = "169"
|
||||
// TODO: make configurable
|
||||
arabicText = "text_imlaei"
|
||||
)
|
||||
|
||||
type Quran struct{}
|
||||
|
||||
// Chapters returns a list of the chapters of the Quran
|
||||
func (q *Quran) Chapters(ctx context.Context, req *pb.ChaptersRequest, rsp *pb.ChaptersResponse) error {
|
||||
lang := "en"
|
||||
if len(req.Language) > 0 {
|
||||
lang = req.Language
|
||||
}
|
||||
|
||||
var resp map[string][]*domain.Chapter
|
||||
if err := api.Get(apiUrl+"chapters?language="+lang, &resp); err != nil {
|
||||
logger.Errorf("Failed to retrieve chapters: %v", err)
|
||||
return errors.InternalServerError("quran.chapters", "Failed to retrieve chapters")
|
||||
}
|
||||
|
||||
for _, c := range resp["chapters"] {
|
||||
rsp.Chapters = append(rsp.Chapters, &pb.Chapter{
|
||||
Id: c.Id,
|
||||
RevelationPlace: c.RevelationPlace,
|
||||
RevelationOrder: c.RevelationOrder,
|
||||
PrefixBismillah: c.BismillahPrefix,
|
||||
Name: c.NameSimple,
|
||||
ComplexName: c.NameComplex,
|
||||
ArabicName: c.NameArabic,
|
||||
TranslatedName: c.TranslatedName.Name,
|
||||
Verses: c.VersesCount,
|
||||
Pages: c.Pages,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Retrieve the summary for a given chapter
|
||||
func (q *Quran) Summary(ctx context.Context, req *pb.SummaryRequest, rsp *pb.SummaryResponse) error {
|
||||
lang := "en"
|
||||
if len(req.Language) > 0 {
|
||||
lang = req.Language
|
||||
}
|
||||
if req.Chapter <= 0 {
|
||||
return errors.BadRequest("quran.chapter-summary", "require chapter id")
|
||||
}
|
||||
|
||||
var resp map[string]*domain.ChapterInfo
|
||||
uri := fmt.Sprintf(apiUrl+"chapters/%d/info?language=%s", req.Chapter, lang)
|
||||
|
||||
if err := api.Get(uri, &resp); err != nil {
|
||||
logger.Errorf("Failed to retrieve chapter info: %v", err)
|
||||
return errors.InternalServerError("quran.chapter-summary", "Failed to retrieve chapter summary")
|
||||
}
|
||||
|
||||
info := resp["chapter_info"]
|
||||
rsp.Chapter = info.ChapterId
|
||||
rsp.Summary = info.ShortText
|
||||
rsp.Source = info.Source
|
||||
rsp.Text = info.Text
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the verses by chapter
|
||||
func (q *Quran) Verses(ctx context.Context, req *pb.VersesRequest, rsp *pb.VersesResponse) error {
|
||||
lang := "en"
|
||||
if len(req.Language) > 0 {
|
||||
lang = req.Language
|
||||
}
|
||||
|
||||
if req.Chapter <= 0 {
|
||||
return errors.BadRequest("quran.verses", "require chapter id")
|
||||
}
|
||||
|
||||
// TODO: enable configuring translations
|
||||
// comma separated list of resource ids
|
||||
// https://quran.api-docs.io/v4/resources/translations
|
||||
translations := translationId
|
||||
// TODO: enable configuring tafirs
|
||||
// https://api.quran.com/api/v4/resources/tafsirs
|
||||
tafsirs := tafsirId
|
||||
|
||||
uri := fmt.Sprintf(apiUrl+"verses/by_chapter/%d?language=%s", req.Chapter, lang)
|
||||
|
||||
// additional fields we require
|
||||
// arabic text in imlaei script
|
||||
uri += "&fields=" + arabicText
|
||||
|
||||
uri += "&words=true"
|
||||
uri += "&word_fields=code_v2,text_imlaei"
|
||||
|
||||
if len(translations) > 0 && req.Translate {
|
||||
uri += "&translations=" + translations
|
||||
uri += "&translation_fields=resource_name"
|
||||
}
|
||||
|
||||
if len(tafsirs) > 0 && req.Interpret {
|
||||
uri += "&tafsirs=" + tafsirs
|
||||
}
|
||||
|
||||
if req.Page > 0 {
|
||||
uri += fmt.Sprintf("&page=%d", req.Page)
|
||||
}
|
||||
|
||||
if req.Limit > 0 {
|
||||
uri += fmt.Sprintf("&per_page=%d", req.Limit)
|
||||
}
|
||||
|
||||
var resp *domain.VersesByChapter
|
||||
|
||||
if err := api.Get(uri, &resp); err != nil {
|
||||
logger.Errorf("Failed to retrieve verses: %v", err)
|
||||
return errors.InternalServerError("quran.verses", "Failed to retrieve verses")
|
||||
}
|
||||
|
||||
rsp.Chapter = req.Chapter
|
||||
rsp.Page = resp.Pagination.CurrentPage
|
||||
rsp.TotalPages = resp.Pagination.TotalPages
|
||||
|
||||
for _, verse := range resp.Verses {
|
||||
v := domain.VerseToProto(verse)
|
||||
// strip words if not asked for
|
||||
if req.Words != true {
|
||||
v.Words = nil
|
||||
}
|
||||
rsp.Verses = append(rsp.Verses, v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the search results for a given query
|
||||
func (q *Quran) Search(ctx context.Context, req *pb.SearchRequest, rsp *pb.SearchResponse) error {
|
||||
if len(req.Query) == 0 {
|
||||
return errors.BadRequest("quran.search", "missing search query")
|
||||
}
|
||||
|
||||
lang := "en"
|
||||
if len(req.Language) > 0 {
|
||||
lang = req.Language
|
||||
}
|
||||
|
||||
if req.Limit <= 0 {
|
||||
req.Limit = 20
|
||||
}
|
||||
|
||||
qq := url.Values{}
|
||||
qq.Set("q", req.Query)
|
||||
qq.Set("size", fmt.Sprintf("%d", req.Limit))
|
||||
qq.Set("page", fmt.Sprintf("%d", req.Page))
|
||||
qq.Set("language", lang)
|
||||
|
||||
uri := fmt.Sprintf(apiUrl+"search?%s", qq.Encode())
|
||||
|
||||
var resp map[string]*domain.SearchResults
|
||||
|
||||
if err := api.Get(uri, &resp); err != nil {
|
||||
logger.Errorf("Failed to retrieve search results: %v", err)
|
||||
return errors.InternalServerError("quran.search", "Failed to retrieve search results")
|
||||
}
|
||||
|
||||
rsp.Query = req.Query
|
||||
rsp.Page = resp["search"].CurrentPage
|
||||
rsp.TotalPages = resp["search"].TotalPages
|
||||
rsp.TotalResults = resp["search"].TotalResults
|
||||
|
||||
for _, result := range resp["search"].Results {
|
||||
r := domain.ResultToProto(result)
|
||||
rsp.Results = append(rsp.Results, r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
25
quran/main.go
Normal file
25
quran/main.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/micro/services/quran/handler"
|
||||
pb "github.com/micro/services/quran/proto"
|
||||
|
||||
"github.com/micro/micro/v3/service"
|
||||
"github.com/micro/micro/v3/service/logger"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create service
|
||||
srv := service.New(
|
||||
service.Name("quran"),
|
||||
service.Version("latest"),
|
||||
)
|
||||
|
||||
// Register handler
|
||||
pb.RegisterQuranHandler(srv.Server(), new(handler.Quran))
|
||||
|
||||
// Run service
|
||||
if err := srv.Run(); err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
}
|
||||
1
quran/micro.mu
Normal file
1
quran/micro.mu
Normal file
@@ -0,0 +1 @@
|
||||
service quran
|
||||
1577
quran/proto/quran.pb.go
Normal file
1577
quran/proto/quran.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
144
quran/proto/quran.pb.micro.go
Normal file
144
quran/proto/quran.pb.micro.go
Normal file
@@ -0,0 +1,144 @@
|
||||
// Code generated by protoc-gen-micro. DO NOT EDIT.
|
||||
// source: proto/quran.proto
|
||||
|
||||
package quran
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
math "math"
|
||||
)
|
||||
|
||||
import (
|
||||
context "context"
|
||||
api "github.com/micro/micro/v3/service/api"
|
||||
client "github.com/micro/micro/v3/service/client"
|
||||
server "github.com/micro/micro/v3/service/server"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ api.Endpoint
|
||||
var _ context.Context
|
||||
var _ client.Option
|
||||
var _ server.Option
|
||||
|
||||
// Api Endpoints for Quran service
|
||||
|
||||
func NewQuranEndpoints() []*api.Endpoint {
|
||||
return []*api.Endpoint{}
|
||||
}
|
||||
|
||||
// Client API for Quran service
|
||||
|
||||
type QuranService interface {
|
||||
Chapters(ctx context.Context, in *ChaptersRequest, opts ...client.CallOption) (*ChaptersResponse, error)
|
||||
Summary(ctx context.Context, in *SummaryRequest, opts ...client.CallOption) (*SummaryResponse, error)
|
||||
Verses(ctx context.Context, in *VersesRequest, opts ...client.CallOption) (*VersesResponse, error)
|
||||
Search(ctx context.Context, in *SearchRequest, opts ...client.CallOption) (*SearchResponse, error)
|
||||
}
|
||||
|
||||
type quranService struct {
|
||||
c client.Client
|
||||
name string
|
||||
}
|
||||
|
||||
func NewQuranService(name string, c client.Client) QuranService {
|
||||
return &quranService{
|
||||
c: c,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *quranService) Chapters(ctx context.Context, in *ChaptersRequest, opts ...client.CallOption) (*ChaptersResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Quran.Chapters", in)
|
||||
out := new(ChaptersResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *quranService) Summary(ctx context.Context, in *SummaryRequest, opts ...client.CallOption) (*SummaryResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Quran.Summary", in)
|
||||
out := new(SummaryResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *quranService) Verses(ctx context.Context, in *VersesRequest, opts ...client.CallOption) (*VersesResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Quran.Verses", in)
|
||||
out := new(VersesResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *quranService) Search(ctx context.Context, in *SearchRequest, opts ...client.CallOption) (*SearchResponse, error) {
|
||||
req := c.c.NewRequest(c.name, "Quran.Search", in)
|
||||
out := new(SearchResponse)
|
||||
err := c.c.Call(ctx, req, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Quran service
|
||||
|
||||
type QuranHandler interface {
|
||||
Chapters(context.Context, *ChaptersRequest, *ChaptersResponse) error
|
||||
Summary(context.Context, *SummaryRequest, *SummaryResponse) error
|
||||
Verses(context.Context, *VersesRequest, *VersesResponse) error
|
||||
Search(context.Context, *SearchRequest, *SearchResponse) error
|
||||
}
|
||||
|
||||
func RegisterQuranHandler(s server.Server, hdlr QuranHandler, opts ...server.HandlerOption) error {
|
||||
type quran interface {
|
||||
Chapters(ctx context.Context, in *ChaptersRequest, out *ChaptersResponse) error
|
||||
Summary(ctx context.Context, in *SummaryRequest, out *SummaryResponse) error
|
||||
Verses(ctx context.Context, in *VersesRequest, out *VersesResponse) error
|
||||
Search(ctx context.Context, in *SearchRequest, out *SearchResponse) error
|
||||
}
|
||||
type Quran struct {
|
||||
quran
|
||||
}
|
||||
h := &quranHandler{hdlr}
|
||||
return s.Handle(s.NewHandler(&Quran{h}, opts...))
|
||||
}
|
||||
|
||||
type quranHandler struct {
|
||||
QuranHandler
|
||||
}
|
||||
|
||||
func (h *quranHandler) Chapters(ctx context.Context, in *ChaptersRequest, out *ChaptersResponse) error {
|
||||
return h.QuranHandler.Chapters(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *quranHandler) Summary(ctx context.Context, in *SummaryRequest, out *SummaryResponse) error {
|
||||
return h.QuranHandler.Summary(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *quranHandler) Verses(ctx context.Context, in *VersesRequest, out *VersesResponse) error {
|
||||
return h.QuranHandler.Verses(ctx, in, out)
|
||||
}
|
||||
|
||||
func (h *quranHandler) Search(ctx context.Context, in *SearchRequest, out *SearchResponse) error {
|
||||
return h.QuranHandler.Search(ctx, in, out)
|
||||
}
|
||||
191
quran/proto/quran.proto
Normal file
191
quran/proto/quran.proto
Normal file
@@ -0,0 +1,191 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package quran;
|
||||
|
||||
option go_package = "./proto;quran";
|
||||
|
||||
service Quran {
|
||||
rpc Chapters(ChaptersRequest) returns (ChaptersResponse) {}
|
||||
rpc Summary(SummaryRequest) returns (SummaryResponse) {}
|
||||
rpc Verses(VersesRequest) returns (VersesResponse) {}
|
||||
rpc Search(SearchRequest) returns (SearchResponse) {}
|
||||
}
|
||||
|
||||
message Chapter {
|
||||
// The id of the chapter as a number e.g 1
|
||||
int32 id = 1;
|
||||
// The number of verses in the chapter
|
||||
int32 verses = 2;
|
||||
// The simple name of the chapter
|
||||
string name = 3;
|
||||
// The arabic name of the chapter
|
||||
string arabic_name = 4;
|
||||
// The complex name of the chapter
|
||||
string complex_name = 5;
|
||||
// The translated name
|
||||
string translated_name = 6;
|
||||
// Should the chapter start with bismillah
|
||||
bool prefix_bismillah = 7;
|
||||
// The place of revelation
|
||||
string revelation_place = 8;
|
||||
// The order in which it was revealed
|
||||
int32 revelation_order = 9;
|
||||
// The pages from and to e.g 1, 1
|
||||
repeated int32 pages = 10;
|
||||
}
|
||||
|
||||
message Verse {
|
||||
// The unique id of the verse in the whole book
|
||||
int32 id = 1;
|
||||
// The verse number in this chapter
|
||||
int32 number = 2;
|
||||
// The key of this verse (chapter:verse) e.g 1:1
|
||||
string key = 3;
|
||||
// The page of the Quran this verse is on
|
||||
int32 page = 4;
|
||||
// The arabic text for this verse
|
||||
string text = 5;
|
||||
// The phonetic transliteration from arabic
|
||||
string transliteration = 6;
|
||||
// The basic translation of the verse
|
||||
string translated_text = 7;
|
||||
// The alternative translations for the verse
|
||||
repeated Translation translations = 8;
|
||||
// The interpretations of the verse
|
||||
repeated Interpretation interpretations = 9;
|
||||
// The individual words within the verse (Ayah)
|
||||
repeated Word words = 10;
|
||||
}
|
||||
|
||||
message Interpretation {
|
||||
// The unique id of the interpretation
|
||||
int32 id = 1;
|
||||
// The source of the interpretation
|
||||
string source = 2;
|
||||
// The translated text
|
||||
string text = 3;
|
||||
}
|
||||
|
||||
message Translation {
|
||||
// The unique id of the translation
|
||||
int32 id = 1;
|
||||
// The source of the translation
|
||||
string source = 2;
|
||||
// The translated text
|
||||
string text = 3;
|
||||
}
|
||||
|
||||
message Word {
|
||||
// The id of the word within the verse
|
||||
int32 id = 1;
|
||||
// The position of the word
|
||||
int32 position = 2;
|
||||
// The character type e.g word, end
|
||||
string char_type = 3;
|
||||
// The page number
|
||||
int32 page = 4;
|
||||
// The line number
|
||||
int32 line = 5;
|
||||
// The arabic text for this word
|
||||
string text = 6;
|
||||
// The QCF v2 font code
|
||||
string code = 7;
|
||||
// The translated text
|
||||
string translation = 8;
|
||||
// The transliteration text
|
||||
string transliteration = 9;
|
||||
}
|
||||
|
||||
message Result {
|
||||
// The unique verse id across the Quran
|
||||
int32 verse_id = 1;
|
||||
// The verse key e.g 1:1
|
||||
string verse_key = 2;
|
||||
// The associated arabic text
|
||||
string text = 3;
|
||||
// The related translations to the text
|
||||
repeated Translation translations = 4;
|
||||
}
|
||||
|
||||
// List the Chapters (surahs) of the Quran
|
||||
message ChaptersRequest {
|
||||
// Specify the language e.g en
|
||||
string language = 1;
|
||||
}
|
||||
|
||||
message ChaptersResponse {
|
||||
repeated Chapter chapters = 1;
|
||||
}
|
||||
|
||||
// Get a summary for a given chapter (surah)
|
||||
message SummaryRequest {
|
||||
// The chapter id e.g 1
|
||||
int32 chapter = 1;
|
||||
// Specify the language e.g en
|
||||
string language = 2;
|
||||
}
|
||||
|
||||
message SummaryResponse {
|
||||
// The chapter id
|
||||
int32 chapter = 1;
|
||||
// The short summary for the chapter
|
||||
string summary = 2;
|
||||
// The source of the summary
|
||||
string source = 3;
|
||||
// The full description for the chapter
|
||||
string text = 4;
|
||||
}
|
||||
|
||||
// Lookup the verses (ayahs) for a chapter
|
||||
message VersesRequest {
|
||||
// The chapter id to retrieve
|
||||
int32 chapter = 1;
|
||||
// The language of translation
|
||||
string language = 2;
|
||||
// Return the individual words with the verses
|
||||
bool words = 3;
|
||||
// Return the interpretation (tafsir)
|
||||
bool interpret = 4;
|
||||
// Return alternate translations
|
||||
bool translate = 5;
|
||||
// The page number to request
|
||||
int32 page = 6;
|
||||
// The verses per page
|
||||
int32 limit = 7;
|
||||
}
|
||||
|
||||
message VersesResponse {
|
||||
// The chapter requested
|
||||
int32 chapter = 1;
|
||||
// The page requested
|
||||
int32 page = 2;
|
||||
// The total pages
|
||||
int32 total_pages = 3;
|
||||
// The verses on the page
|
||||
repeated Verse verses = 4;
|
||||
}
|
||||
|
||||
// Search the Quran for any form of query or questions
|
||||
message SearchRequest {
|
||||
// The query to ask
|
||||
string query = 1;
|
||||
// The language for translation
|
||||
string language = 2;
|
||||
// The number of results to return
|
||||
int32 limit = 3;
|
||||
// The pagination number
|
||||
int32 page = 4;
|
||||
}
|
||||
|
||||
message SearchResponse {
|
||||
// The question asked
|
||||
string query = 1;
|
||||
// The total results returned
|
||||
int32 total_results = 2;
|
||||
// The current page
|
||||
int32 page = 3;
|
||||
// The total pages
|
||||
int32 total_pages = 4;
|
||||
// The results for the query
|
||||
repeated Result results = 5;
|
||||
}
|
||||
5
quran/publicapi.json
Normal file
5
quran/publicapi.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "quran",
|
||||
"icon": "📖",
|
||||
"category": "religion"
|
||||
}
|
||||
Reference in New Issue
Block a user