Migrate to Blazor WASM, MSSQL platform DB, and durable audio PVC.
build-and-deploy / deploy (push) Failing after 15m11s
build-and-deploy / deploy (push) Failing after 15m11s
Remove in-cluster Postgres; serve WASM via nginx; add audio import job for Gooya archives.
This commit is contained in:
+7
-7
@@ -5,6 +5,7 @@ ARG http_proxy
|
|||||||
ARG https_proxy
|
ARG https_proxy
|
||||||
ARG NO_PROXY
|
ARG NO_PROXY
|
||||||
ARG no_proxy
|
ARG no_proxy
|
||||||
|
ARG API_BASE=http://185.252.31.44:30821/
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY NuGet.Config ./
|
COPY NuGet.Config ./
|
||||||
COPY src/TafsirQuran.Shared/TafsirQuran.Shared.csproj src/TafsirQuran.Shared/
|
COPY src/TafsirQuran.Shared/TafsirQuran.Shared.csproj src/TafsirQuran.Shared/
|
||||||
@@ -14,11 +15,10 @@ RUN dotnet restore src/TafsirQuran.Web/TafsirQuran.Web.csproj
|
|||||||
COPY src/TafsirQuran.Shared/ src/TafsirQuran.Shared/
|
COPY src/TafsirQuran.Shared/ src/TafsirQuran.Shared/
|
||||||
COPY src/TafsirQuran.UI/ src/TafsirQuran.UI/
|
COPY src/TafsirQuran.UI/ src/TafsirQuran.UI/
|
||||||
COPY src/TafsirQuran.Web/ src/TafsirQuran.Web/
|
COPY src/TafsirQuran.Web/ src/TafsirQuran.Web/
|
||||||
RUN dotnet publish src/TafsirQuran.Web/TafsirQuran.Web.csproj -c Release -o /app/publish --no-restore
|
RUN printf '{\n "ApiBaseAddress": "%s"\n}\n' "$API_BASE" > src/TafsirQuran.Web/wwwroot/appsettings.json \
|
||||||
|
&& dotnet publish src/TafsirQuran.Web/TafsirQuran.Web.csproj -c Release -o /app/publish
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS final
|
FROM nginx:1.27-alpine AS final
|
||||||
WORKDIR /app
|
COPY docker/nginx-web.conf /etc/nginx/conf.d/default.conf
|
||||||
ENV ASPNETCORE_URLS=http://+:8080
|
COPY --from=build /app/publish/wwwroot /usr/share/nginx/html
|
||||||
EXPOSE 8080
|
EXPOSE 80
|
||||||
COPY --from=build /app/publish .
|
|
||||||
ENTRYPOINT ["dotnet", "TafsirQuran.Web.dll"]
|
|
||||||
|
|||||||
@@ -7,10 +7,18 @@
|
|||||||
- ریپو: https://git.se.kbs1.ir/admin/TafsirQuran
|
- ریپو: https://git.se.kbs1.ir/admin/TafsirQuran
|
||||||
- CI: `.gitea/workflows/deploy.yaml` روی runner label `deploy`
|
- CI: `.gitea/workflows/deploy.yaml` روی runner label `deploy`
|
||||||
- Namespace: `tafsirquran`
|
- Namespace: `tafsirquran`
|
||||||
- وب: http://185.252.31.44:30820/
|
- وب (Blazor WASM): http://185.252.31.44:30820/
|
||||||
- API: http://185.252.31.44:30821/
|
- API: http://185.252.31.44:30821/
|
||||||
|
- DB: MSSQL `mssql-svc.platform` / database `TafsirQuran`
|
||||||
|
- صوت: PVC `tafsirquran-audio` → mount `/data/audio`
|
||||||
|
|
||||||
پوش به `main` یا `workflow_dispatch` تصویرها را میسازد، به k3s import میکند و رولاوت میکند.
|
پوش به `main` دیپلوی میکند. ایمپورت صوت (یکبار، طولانی):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl -n tafsirquran apply -f k8s/audio-import-job.yaml
|
||||||
|
kubectl -n tafsirquran logs -f job/tafsirquran-audio-import
|
||||||
|
kubectl -n tafsirquran rollout restart deploy/tafsirquran-api # resync episodes
|
||||||
|
```
|
||||||
|
|
||||||
## پیشنیاز توسعه محلی
|
## پیشنیاز توسعه محلی
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~* \.(dll|wasm|dat|blat|json|js|css|png|svg|woff2?)$ {
|
||||||
|
expires 7d;
|
||||||
|
add_header Cache-Control "public";
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
# One-shot import of Tafsir Gooya audio into tafsirquran-audio PVC.
|
||||||
|
# Run: kubectl -n tafsirquran apply -f k8s/audio-import-job.yaml
|
||||||
|
# Watch: kubectl -n tafsirquran logs -f job/tafsirquran-audio-import
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: tafsirquran-audio-import
|
||||||
|
namespace: tafsirquran
|
||||||
|
spec:
|
||||||
|
backoffLimit: 1
|
||||||
|
ttlSecondsAfterFinished: 86400
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
restartPolicy: Never
|
||||||
|
containers:
|
||||||
|
- name: import
|
||||||
|
image: debian:bookworm-slim
|
||||||
|
command: ["/bin/bash", "-lc"]
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
set -euo pipefail
|
||||||
|
apt-get update -qq
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq curl unrar ca-certificates >/dev/null
|
||||||
|
chmod +x /scripts/fetch-audio.sh
|
||||||
|
/scripts/fetch-audio.sh /data/audio
|
||||||
|
volumeMounts:
|
||||||
|
- name: audio
|
||||||
|
mountPath: /data/audio
|
||||||
|
- name: scripts
|
||||||
|
mountPath: /scripts
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: 512Mi
|
||||||
|
cpu: 250m
|
||||||
|
volumes:
|
||||||
|
- name: audio
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: tafsirquran-audio
|
||||||
|
- name: scripts
|
||||||
|
configMap:
|
||||||
|
name: tafsirquran-audio-scripts
|
||||||
|
defaultMode: 0755
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: tafsirquran-audio-scripts
|
||||||
|
namespace: tafsirquran
|
||||||
|
data:
|
||||||
|
fetch-audio.sh: |
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
AUDIO_DIR="${1:-/data/audio}"
|
||||||
|
PASSWORD="www.p30download.com"
|
||||||
|
BASE_URL="https://pdn.sharezilla.ir/d/audio"
|
||||||
|
WORKDIR="${AUDIO_DIR}/.download"
|
||||||
|
mkdir -p "$AUDIO_DIR" "$WORKDIR"
|
||||||
|
SLUGS=(
|
||||||
|
001.Tafsir.Gooya.Hamd 002.Tafsir.Gooya.Baqara 003.Tafsir.Gooya.Ale.imran 004.Tafsir.Gooya.Nisa
|
||||||
|
005.Tafsir.Gooya.Maeda 006.Tafsir.Gooya.Anaam 007.Tafsir.Gooya.Araf 008.Tafsir.Gooya.Anfal
|
||||||
|
009.Tafsir.Gooya.Tawba 010.Tafsir.Gooya.Yunus 011.Tafsir.Gooya.Hud 012.Tafsir.Gooya.Yusuf
|
||||||
|
013.Tafsir.Gooya.Rad 014.Tafsir.Gooya.Ibrahim 015.Tafsir.Gooya.Hijr 016.Tafsir.Gooya.Nahl
|
||||||
|
017.Tafsir.Gooya.Isra 018.Tafsir.Gooya.Kahf 019.Tafsir.Gooya.Maryam 020.Tafsir.Gooya.Taha
|
||||||
|
021.Tafsir.Gooya.Anbiya 022.Tafsir.Gooya.Hajj 023.Tafsir.Gooya.Mumenoon 024.Tafsir.Gooya.Noor
|
||||||
|
025.Tafsir.Gooya.Furqan 026.Tafsir.Gooya.Shuara 027.Tafsir.Gooya.Naml 028.Tafsir.Gooya.Qasas
|
||||||
|
029.Tafsir.Gooya.Ankaboot 030.Tafsir.Gooya.Room 031.Tafsir.Gooya.Luqman 032.Tafsir.Gooya.Sajda
|
||||||
|
033.Tafsir.Gooya.Ahzab 034.Tafsir.Gooya.Saba 035.Tafsir.Gooya.Fatir 036.Tafsir.Gooya.Yaseen
|
||||||
|
037.Tafsir.Gooya.Saaffat 038.Tafsir.Gooya.Sad 039.Tafsir.Gooya.Zumar 040.Tafsir.Gooya.Ghafir
|
||||||
|
041.Tafsir.Gooya.Fussilat 042.Tafsir.Gooya.Shura 043.Tafsir.Gooya.Zukhruf 044.Tafsir.Gooya.Dukhan
|
||||||
|
045.Tafsir.Gooya.Jathiya 046.Tafsir.Gooya.Ahqaf 047.Tafsir.Gooya.Muhammad 048.Tafsir.Gooya.Fath
|
||||||
|
049.Tafsir.Gooya.Hujaraat 050.Tafsir.Gooya.Qaf 051.Tafsir.Gooya.Dhariyat 052.Tafsir.Gooya.Tur
|
||||||
|
053.Tafsir.Gooya.Najm 054.Tafsir.Gooya.Qamar 055.Tafsir.Gooya.Ar-Rahman 056.Tafsir.Gooya.Waqia
|
||||||
|
057.Tafsir.Gooya.Hadid 058.Tafsir.Gooya.Mujadila 059.Tafsir.Gooya.Hashr 060.Tafsir.Gooya.Mumtahina
|
||||||
|
061.Tafsir.Gooya.Saff 062.Tafsir.Gooya.Jumua 063.Tafsir.Gooya.Munafiqoon 064.Tafsir.Gooya.Taghabun
|
||||||
|
065.Tafsir.Gooya.Talaq 066.Tafsir.Gooya.Tahrim 067.Tafsir.Gooya.Mulk 068.Tafsir.Gooya.Qalam
|
||||||
|
069.Tafsir.Gooya.Haaqqa 070.Tafsir.Gooya.Maarij 071.Tafsir.Gooya.Nooh 072.Tafsir.Gooya.Jinn
|
||||||
|
073.Tafsir.Gooya.Muzzammil 074.Tafsir.Gooya.muddaththir 075.Tafsir.Gooya.Qiyama 076.Tafsir.Gooya.Insan
|
||||||
|
077.Tafsir.Gooya.Mursalat 078.Tafsir.Gooya.Naba 079.Tafsir.Gooya.Naziat 080.Tafsir.Gooya.Abasa
|
||||||
|
081.Tafsir.Gooya.Takwir 082.Tafsir.Gooya.Infitar 083.Tafsir.Gooya.Mutaffifin 084.Tafsir.Gooya.Inshiqaq
|
||||||
|
085.Tafsir.Gooya.Brooj 086.Tafsir.Gooya.Tariq 087.Tafsir.Gooya.Ala 088.Tafsir.Gooya.Ghashiya
|
||||||
|
089.Tafsir.Gooya.Fajr 090.Tafsir.Gooya.Balad 091.Tafsir.Gooya.Shams 092.Tafsir.Gooya.Lail
|
||||||
|
093.Tafsir.Gooya.Dhuha 094.Tafsir.Gooya.Inshirah 095.Tafsir.Gooya.Tin 096.Tafsir.Gooya.Alaq
|
||||||
|
097.Tafsir.Gooya.Qadr 098.Tafsir.Gooya.Bayyina 099.Tafsir.Gooya.Zalzala 100.Tafsir.Gooya.Adiyat
|
||||||
|
101.Tafsir.Gooya.Qaria 102.Tafsir.Gooya.Takathur 103.Tafsir.Gooya.Asr 104.Tafsir.Gooya.Humaza
|
||||||
|
105.Tafsir.Gooya.Fil 106.Tafsir.Gooya.Quarish 107.Tafsir.Gooya.Maun 108.Tafsir.Gooya.kauther
|
||||||
|
109.Tafsir.Gooya.Kafiroon 110.Tafsir.Gooya.Nasr 111.Tafsir.Gooya.Masad 112.Tafsir.Gooya.Ikhlas
|
||||||
|
113.Tafsir.Gooya.Falaq 114.Tafsir.Gooya.Nas
|
||||||
|
)
|
||||||
|
for slug in "${SLUGS[@]}"; do
|
||||||
|
num="${slug%%.*}"
|
||||||
|
out_dir="${AUDIO_DIR}/${num}"
|
||||||
|
if find "$out_dir" -type f \( -iname '*.mp3' -o -iname '*.m4a' \) 2>/dev/null | grep -q .; then
|
||||||
|
echo "skip $num"; continue
|
||||||
|
fi
|
||||||
|
rar="${WORKDIR}/${slug}.rar"
|
||||||
|
echo "download $slug"
|
||||||
|
curl -fL --retry 5 --retry-delay 3 -o "$rar" "${BASE_URL}/${slug}.rar"
|
||||||
|
mkdir -p "$out_dir"
|
||||||
|
echo "extract $slug"
|
||||||
|
unrar x -o+ -p"${PASSWORD}" "$rar" "${out_dir}/"
|
||||||
|
rm -f "$rar"
|
||||||
|
echo "done $num"
|
||||||
|
done
|
||||||
|
echo "files:" $(find "$AUDIO_DIR" -type f \( -iname '*.mp3' -o -iname '*.m4a' \) | wc -l)
|
||||||
+26
-72
@@ -10,8 +10,9 @@ metadata:
|
|||||||
namespace: tafsirquran
|
namespace: tafsirquran
|
||||||
type: Opaque
|
type: Opaque
|
||||||
stringData:
|
stringData:
|
||||||
POSTGRES_PASSWORD: tafsir_pg_2026
|
MSSQL_SA_PASSWORD: "87zH26nbqT"
|
||||||
Jwt__Key: TafsirQuran-Prod-Secret-Key-Change-Me-32chars
|
Jwt__Key: TafsirQuran-Prod-Secret-Key-Change-Me-32chars
|
||||||
|
ConnectionStrings__Default: "Server=mssql-svc.platform.svc.cluster.local,1433;Database=TafsirQuran;User Id=sa;Password=87zH26nbqT;TrustServerCertificate=True;Encrypt=False"
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
@@ -20,7 +21,7 @@ metadata:
|
|||||||
namespace: tafsirquran
|
namespace: tafsirquran
|
||||||
data:
|
data:
|
||||||
ASPNETCORE_URLS: "http://+:5080"
|
ASPNETCORE_URLS: "http://+:5080"
|
||||||
ConnectionStrings__Default: "Host=tafsirquran-postgres;Port=5432;Database=tafsirquran;Username=tafsir;Password=tafsir_pg_2026"
|
AudioRoot: "/data/audio"
|
||||||
Jwt__Issuer: "TafsirQuran"
|
Jwt__Issuer: "TafsirQuran"
|
||||||
Jwt__Audience: "TafsirQuran"
|
Jwt__Audience: "TafsirQuran"
|
||||||
Jwt__ExpireMinutes: "43200"
|
Jwt__ExpireMinutes: "43200"
|
||||||
@@ -31,75 +32,19 @@ metadata:
|
|||||||
name: tafsirquran-web-env
|
name: tafsirquran-web-env
|
||||||
namespace: tafsirquran
|
namespace: tafsirquran
|
||||||
data:
|
data:
|
||||||
ASPNETCORE_URLS: "http://+:8080"
|
|
||||||
ApiBaseAddress: "http://185.252.31.44:30821/"
|
ApiBaseAddress: "http://185.252.31.44:30821/"
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: PersistentVolumeClaim
|
kind: PersistentVolumeClaim
|
||||||
metadata:
|
metadata:
|
||||||
name: tafsirquran-pg-data
|
name: tafsirquran-audio
|
||||||
namespace: tafsirquran
|
namespace: tafsirquran
|
||||||
spec:
|
spec:
|
||||||
accessModes: ["ReadWriteOnce"]
|
accessModes: ["ReadWriteOnce"]
|
||||||
storageClassName: local-path
|
storageClassName: local-path
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
storage: 5Gi
|
storage: 100Gi
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: tafsirquran-postgres
|
|
||||||
namespace: tafsirquran
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: tafsirquran-postgres
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: tafsirquran-postgres
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: postgres
|
|
||||||
image: postgres:16-alpine
|
|
||||||
ports:
|
|
||||||
- containerPort: 5432
|
|
||||||
env:
|
|
||||||
- name: POSTGRES_USER
|
|
||||||
value: tafsir
|
|
||||||
- name: POSTGRES_DB
|
|
||||||
value: tafsirquran
|
|
||||||
- name: POSTGRES_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: tafsirquran-secrets
|
|
||||||
key: POSTGRES_PASSWORD
|
|
||||||
volumeMounts:
|
|
||||||
- name: data
|
|
||||||
mountPath: /var/lib/postgresql/data
|
|
||||||
readinessProbe:
|
|
||||||
exec:
|
|
||||||
command: ["pg_isready", "-U", "tafsir", "-d", "tafsirquran"]
|
|
||||||
initialDelaySeconds: 5
|
|
||||||
periodSeconds: 5
|
|
||||||
volumes:
|
|
||||||
- name: data
|
|
||||||
persistentVolumeClaim:
|
|
||||||
claimName: tafsirquran-pg-data
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: tafsirquran-postgres
|
|
||||||
namespace: tafsirquran
|
|
||||||
spec:
|
|
||||||
selector:
|
|
||||||
app: tafsirquran-postgres
|
|
||||||
ports:
|
|
||||||
- port: 5432
|
|
||||||
targetPort: 5432
|
|
||||||
---
|
---
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@@ -131,18 +76,30 @@ spec:
|
|||||||
secretKeyRef:
|
secretKeyRef:
|
||||||
name: tafsirquran-secrets
|
name: tafsirquran-secrets
|
||||||
key: Jwt__Key
|
key: Jwt__Key
|
||||||
|
- name: ConnectionStrings__Default
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: tafsirquran-secrets
|
||||||
|
key: ConnectionStrings__Default
|
||||||
|
volumeMounts:
|
||||||
|
- name: audio
|
||||||
|
mountPath: /data/audio
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /
|
||||||
port: 5080
|
port: 5080
|
||||||
initialDelaySeconds: 10
|
initialDelaySeconds: 15
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /
|
||||||
port: 5080
|
port: 5080
|
||||||
initialDelaySeconds: 30
|
initialDelaySeconds: 40
|
||||||
periodSeconds: 20
|
periodSeconds: 20
|
||||||
|
volumes:
|
||||||
|
- name: audio
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: tafsirquran-audio
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
@@ -178,21 +135,18 @@ spec:
|
|||||||
image: tafsirquran-web:latest
|
image: tafsirquran-web:latest
|
||||||
imagePullPolicy: Never
|
imagePullPolicy: Never
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 8080
|
- containerPort: 80
|
||||||
envFrom:
|
|
||||||
- configMapRef:
|
|
||||||
name: tafsirquran-web-env
|
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /
|
||||||
port: 8080
|
port: 80
|
||||||
initialDelaySeconds: 10
|
initialDelaySeconds: 5
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /
|
||||||
port: 8080
|
port: 80
|
||||||
initialDelaySeconds: 30
|
initialDelaySeconds: 15
|
||||||
periodSeconds: 20
|
periodSeconds: 20
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
@@ -205,6 +159,6 @@ spec:
|
|||||||
selector:
|
selector:
|
||||||
app: tafsirquran-web
|
app: tafsirquran-web
|
||||||
ports:
|
ports:
|
||||||
- port: 8080
|
- port: 80
|
||||||
targetPort: 8080
|
targetPort: 80
|
||||||
nodePort: 30820
|
nodePort: 30820
|
||||||
|
|||||||
+12
-15
@@ -1,5 +1,4 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Build images, import into k3s, apply manifests, roll out.
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
@@ -30,9 +29,10 @@ fi
|
|||||||
build_with_retry() {
|
build_with_retry() {
|
||||||
local dockerfile="$1"
|
local dockerfile="$1"
|
||||||
local tag="$2"
|
local tag="$2"
|
||||||
|
shift 2
|
||||||
local attempt=1
|
local attempt=1
|
||||||
local max_attempts=3
|
local max_attempts=3
|
||||||
until docker build "${BUILD_ARGS[@]}" -f "${dockerfile}" -t "${tag}" .; do
|
until docker build "${BUILD_ARGS[@]}" "$@" -f "${dockerfile}" -t "${tag}" .; do
|
||||||
if [ "$attempt" -ge "$max_attempts" ]; then
|
if [ "$attempt" -ge "$max_attempts" ]; then
|
||||||
echo "docker build ${tag} failed after ${max_attempts} attempts" >&2
|
echo "docker build ${tag} failed after ${max_attempts} attempts" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -43,9 +43,14 @@ build_with_retry() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "==> build api + web"
|
echo "==> remove legacy postgres resources if present"
|
||||||
|
kubectl -n "${NS}" delete deploy tafsirquran-postgres --ignore-not-found
|
||||||
|
kubectl -n "${NS}" delete svc tafsirquran-postgres --ignore-not-found
|
||||||
|
kubectl -n "${NS}" delete pvc tafsirquran-pg-data --ignore-not-found
|
||||||
|
|
||||||
|
echo "==> build api + web (wasm/nginx)"
|
||||||
build_with_retry Dockerfile.api "${API_IMAGE}"
|
build_with_retry Dockerfile.api "${API_IMAGE}"
|
||||||
build_with_retry Dockerfile.web "${WEB_IMAGE}"
|
build_with_retry Dockerfile.web "${WEB_IMAGE}" --build-arg "API_BASE=${PUBLIC_API}/"
|
||||||
|
|
||||||
echo "==> import images into k3s"
|
echo "==> import images into k3s"
|
||||||
docker save "${API_IMAGE}" | k3s ctr images import -
|
docker save "${API_IMAGE}" | k3s ctr images import -
|
||||||
@@ -54,21 +59,12 @@ docker save "${WEB_IMAGE}" | k3s ctr images import -
|
|||||||
echo "==> apply manifests"
|
echo "==> apply manifests"
|
||||||
kubectl apply -f "${MANIFEST}"
|
kubectl apply -f "${MANIFEST}"
|
||||||
|
|
||||||
# Keep public API URL for the Blazor host in sync with NodePort
|
|
||||||
kubectl -n "${NS}" create configmap tafsirquran-web-env \
|
|
||||||
--from-literal=ASPNETCORE_URLS='http://+:8080' \
|
|
||||||
--from-literal=ApiBaseAddress="${PUBLIC_API}/" \
|
|
||||||
--dry-run=client -o yaml | kubectl apply -f -
|
|
||||||
|
|
||||||
echo "==> wait postgres"
|
|
||||||
kubectl -n "${NS}" rollout status deploy/tafsirquran-postgres --timeout=180s
|
|
||||||
|
|
||||||
echo "==> restart api + web"
|
echo "==> restart api + web"
|
||||||
kubectl -n "${NS}" rollout restart deploy/tafsirquran-api deploy/tafsirquran-web
|
kubectl -n "${NS}" rollout restart deploy/tafsirquran-api deploy/tafsirquran-web
|
||||||
kubectl -n "${NS}" rollout status deploy/tafsirquran-api --timeout=300s
|
kubectl -n "${NS}" rollout status deploy/tafsirquran-api --timeout=360s
|
||||||
kubectl -n "${NS}" rollout status deploy/tafsirquran-web --timeout=240s
|
kubectl -n "${NS}" rollout status deploy/tafsirquran-web --timeout=240s
|
||||||
|
|
||||||
kubectl -n "${NS}" get pods,svc -o wide
|
kubectl -n "${NS}" get pods,svc,pvc -o wide
|
||||||
|
|
||||||
echo "==> smoke"
|
echo "==> smoke"
|
||||||
curl -fsS --max-time 30 "http://127.0.0.1:30821/" | head -c 200; echo
|
curl -fsS --max-time 30 "http://127.0.0.1:30821/" | head -c 200; echo
|
||||||
@@ -77,4 +73,5 @@ curl -fsS --max-time 30 -o /dev/null -w "web:%{http_code}\n" "http://127.0.0.1:3
|
|||||||
|
|
||||||
echo "web → ${PUBLIC_WEB}/"
|
echo "web → ${PUBLIC_WEB}/"
|
||||||
echo "api → ${PUBLIC_API}/"
|
echo "api → ${PUBLIC_API}/"
|
||||||
|
echo "audio PVC: tafsirquran-audio (import via k8s/audio-import-job.yaml)"
|
||||||
echo "deploy ok"
|
echo "deploy ok"
|
||||||
|
|||||||
Executable
+171
@@ -0,0 +1,171 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Download Tafsir Gooya (قطره چکانی) RAR archives, extract with password, delete RARs.
|
||||||
|
# Usage: AUDIO_DIR=/data/audio ./scripts/fetch-audio.sh
|
||||||
|
# or: ./scripts/fetch-audio.sh /path/to/audio
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
AUDIO_DIR="${1:-${AUDIO_DIR:-/data/audio}}"
|
||||||
|
PASSWORD="${RAR_PASSWORD:-www.p30download.com}"
|
||||||
|
BASE_URL="https://pdn.sharezilla.ir/d/audio"
|
||||||
|
WORKDIR="${AUDIO_DIR}/.download"
|
||||||
|
mkdir -p "$AUDIO_DIR" "$WORKDIR"
|
||||||
|
|
||||||
|
need() { command -v "$1" >/dev/null 2>&1 || { echo "missing dependency: $1" >&2; exit 1; }; }
|
||||||
|
need curl
|
||||||
|
if ! command -v unrar >/dev/null 2>&1 && ! command -v 7z >/dev/null 2>&1; then
|
||||||
|
echo "need unrar or 7z" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# slug list matching sharezilla filenames
|
||||||
|
declare -a SLUGS=(
|
||||||
|
"001.Tafsir.Gooya.Hamd"
|
||||||
|
"002.Tafsir.Gooya.Baqara"
|
||||||
|
"003.Tafsir.Gooya.Ale.imran"
|
||||||
|
"004.Tafsir.Gooya.Nisa"
|
||||||
|
"005.Tafsir.Gooya.Maeda"
|
||||||
|
"006.Tafsir.Gooya.Anaam"
|
||||||
|
"007.Tafsir.Gooya.Araf"
|
||||||
|
"008.Tafsir.Gooya.Anfal"
|
||||||
|
"009.Tafsir.Gooya.Tawba"
|
||||||
|
"010.Tafsir.Gooya.Yunus"
|
||||||
|
"011.Tafsir.Gooya.Hud"
|
||||||
|
"012.Tafsir.Gooya.Yusuf"
|
||||||
|
"013.Tafsir.Gooya.Rad"
|
||||||
|
"014.Tafsir.Gooya.Ibrahim"
|
||||||
|
"015.Tafsir.Gooya.Hijr"
|
||||||
|
"016.Tafsir.Gooya.Nahl"
|
||||||
|
"017.Tafsir.Gooya.Isra"
|
||||||
|
"018.Tafsir.Gooya.Kahf"
|
||||||
|
"019.Tafsir.Gooya.Maryam"
|
||||||
|
"020.Tafsir.Gooya.Taha"
|
||||||
|
"021.Tafsir.Gooya.Anbiya"
|
||||||
|
"022.Tafsir.Gooya.Hajj"
|
||||||
|
"023.Tafsir.Gooya.Mumenoon"
|
||||||
|
"024.Tafsir.Gooya.Noor"
|
||||||
|
"025.Tafsir.Gooya.Furqan"
|
||||||
|
"026.Tafsir.Gooya.Shuara"
|
||||||
|
"027.Tafsir.Gooya.Naml"
|
||||||
|
"028.Tafsir.Gooya.Qasas"
|
||||||
|
"029.Tafsir.Gooya.Ankaboot"
|
||||||
|
"030.Tafsir.Gooya.Room"
|
||||||
|
"031.Tafsir.Gooya.Luqman"
|
||||||
|
"032.Tafsir.Gooya.Sajda"
|
||||||
|
"033.Tafsir.Gooya.Ahzab"
|
||||||
|
"034.Tafsir.Gooya.Saba"
|
||||||
|
"035.Tafsir.Gooya.Fatir"
|
||||||
|
"036.Tafsir.Gooya.Yaseen"
|
||||||
|
"037.Tafsir.Gooya.Saaffat"
|
||||||
|
"038.Tafsir.Gooya.Sad"
|
||||||
|
"039.Tafsir.Gooya.Zumar"
|
||||||
|
"040.Tafsir.Gooya.Ghafir"
|
||||||
|
"041.Tafsir.Gooya.Fussilat"
|
||||||
|
"042.Tafsir.Gooya.Shura"
|
||||||
|
"043.Tafsir.Gooya.Zukhruf"
|
||||||
|
"044.Tafsir.Gooya.Dukhan"
|
||||||
|
"045.Tafsir.Gooya.Jathiya"
|
||||||
|
"046.Tafsir.Gooya.Ahqaf"
|
||||||
|
"047.Tafsir.Gooya.Muhammad"
|
||||||
|
"048.Tafsir.Gooya.Fath"
|
||||||
|
"049.Tafsir.Gooya.Hujaraat"
|
||||||
|
"050.Tafsir.Gooya.Qaf"
|
||||||
|
"051.Tafsir.Gooya.Dhariyat"
|
||||||
|
"052.Tafsir.Gooya.Tur"
|
||||||
|
"053.Tafsir.Gooya.Najm"
|
||||||
|
"054.Tafsir.Gooya.Qamar"
|
||||||
|
"055.Tafsir.Gooya.Ar-Rahman"
|
||||||
|
"056.Tafsir.Gooya.Waqia"
|
||||||
|
"057.Tafsir.Gooya.Hadid"
|
||||||
|
"058.Tafsir.Gooya.Mujadila"
|
||||||
|
"059.Tafsir.Gooya.Hashr"
|
||||||
|
"060.Tafsir.Gooya.Mumtahina"
|
||||||
|
"061.Tafsir.Gooya.Saff"
|
||||||
|
"062.Tafsir.Gooya.Jumua"
|
||||||
|
"063.Tafsir.Gooya.Munafiqoon"
|
||||||
|
"064.Tafsir.Gooya.Taghabun"
|
||||||
|
"065.Tafsir.Gooya.Talaq"
|
||||||
|
"066.Tafsir.Gooya.Tahrim"
|
||||||
|
"067.Tafsir.Gooya.Mulk"
|
||||||
|
"068.Tafsir.Gooya.Qalam"
|
||||||
|
"069.Tafsir.Gooya.Haaqqa"
|
||||||
|
"070.Tafsir.Gooya.Maarij"
|
||||||
|
"071.Tafsir.Gooya.Nooh"
|
||||||
|
"072.Tafsir.Gooya.Jinn"
|
||||||
|
"073.Tafsir.Gooya.Muzzammil"
|
||||||
|
"074.Tafsir.Gooya.muddaththir"
|
||||||
|
"075.Tafsir.Gooya.Qiyama"
|
||||||
|
"076.Tafsir.Gooya.Insan"
|
||||||
|
"077.Tafsir.Gooya.Mursalat"
|
||||||
|
"078.Tafsir.Gooya.Naba"
|
||||||
|
"079.Tafsir.Gooya.Naziat"
|
||||||
|
"080.Tafsir.Gooya.Abasa"
|
||||||
|
"081.Tafsir.Gooya.Takwir"
|
||||||
|
"082.Tafsir.Gooya.Infitar"
|
||||||
|
"083.Tafsir.Gooya.Mutaffifin"
|
||||||
|
"084.Tafsir.Gooya.Inshiqaq"
|
||||||
|
"085.Tafsir.Gooya.Brooj"
|
||||||
|
"086.Tafsir.Gooya.Tariq"
|
||||||
|
"087.Tafsir.Gooya.Ala"
|
||||||
|
"088.Tafsir.Gooya.Ghashiya"
|
||||||
|
"089.Tafsir.Gooya.Fajr"
|
||||||
|
"090.Tafsir.Gooya.Balad"
|
||||||
|
"091.Tafsir.Gooya.Shams"
|
||||||
|
"092.Tafsir.Gooya.Lail"
|
||||||
|
"093.Tafsir.Gooya.Dhuha"
|
||||||
|
"094.Tafsir.Gooya.Inshirah"
|
||||||
|
"095.Tafsir.Gooya.Tin"
|
||||||
|
"096.Tafsir.Gooya.Alaq"
|
||||||
|
"097.Tafsir.Gooya.Qadr"
|
||||||
|
"098.Tafsir.Gooya.Bayyina"
|
||||||
|
"099.Tafsir.Gooya.Zalzala"
|
||||||
|
"100.Tafsir.Gooya.Adiyat"
|
||||||
|
"101.Tafsir.Gooya.Qaria"
|
||||||
|
"102.Tafsir.Gooya.Takathur"
|
||||||
|
"103.Tafsir.Gooya.Asr"
|
||||||
|
"104.Tafsir.Gooya.Humaza"
|
||||||
|
"105.Tafsir.Gooya.Fil"
|
||||||
|
"106.Tafsir.Gooya.Quarish"
|
||||||
|
"107.Tafsir.Gooya.Maun"
|
||||||
|
"108.Tafsir.Gooya.kauther"
|
||||||
|
"109.Tafsir.Gooya.Kafiroon"
|
||||||
|
"110.Tafsir.Gooya.Nasr"
|
||||||
|
"111.Tafsir.Gooya.Masad"
|
||||||
|
"112.Tafsir.Gooya.Ikhlas"
|
||||||
|
"113.Tafsir.Gooya.Falaq"
|
||||||
|
"114.Tafsir.Gooya.Nas"
|
||||||
|
)
|
||||||
|
|
||||||
|
extract_one() {
|
||||||
|
local rar="$1"
|
||||||
|
local dest="$2"
|
||||||
|
mkdir -p "$dest"
|
||||||
|
if command -v unrar >/dev/null 2>&1; then
|
||||||
|
unrar x -o+ -p"${PASSWORD}" "$rar" "$dest/"
|
||||||
|
else
|
||||||
|
7z x -y "-p${PASSWORD}" "-o${dest}" "$rar"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
for slug in "${SLUGS[@]}"; do
|
||||||
|
num="${slug%%.*}"
|
||||||
|
out_dir="${AUDIO_DIR}/${num}"
|
||||||
|
# skip if already has audio
|
||||||
|
if find "$out_dir" -type f \( -iname '*.mp3' -o -iname '*.m4a' -o -iname '*.ogg' \) 2>/dev/null | grep -q .; then
|
||||||
|
echo "==> skip ${num} (already extracted)"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
rar="${WORKDIR}/${slug}.rar"
|
||||||
|
url="${BASE_URL}/${slug}.rar"
|
||||||
|
echo "==> download ${slug}"
|
||||||
|
curl -fL --retry 5 --retry-delay 3 -o "$rar" "$url"
|
||||||
|
|
||||||
|
echo "==> extract ${slug} -> ${out_dir}"
|
||||||
|
extract_one "$rar" "$out_dir"
|
||||||
|
rm -f "$rar"
|
||||||
|
echo "==> done ${num}"
|
||||||
|
done
|
||||||
|
|
||||||
|
rmdir "$WORKDIR" 2>/dev/null || true
|
||||||
|
echo "audio import finished under ${AUDIO_DIR}"
|
||||||
|
find "$AUDIO_DIR" -type f \( -iname '*.mp3' -o -iname '*.m4a' \) | wc -l
|
||||||
@@ -56,23 +56,33 @@ public static class TafsirEndpoints
|
|||||||
return Results.Ok(detail);
|
return Results.Ok(detail);
|
||||||
});
|
});
|
||||||
|
|
||||||
group.MapGet("/audio/{episodeId:guid}", async (Guid episodeId, AppDbContext db, IWebHostEnvironment env, CancellationToken ct) =>
|
group.MapGet("/audio/{episodeId:guid}", async (Guid episodeId, AppDbContext db, IConfiguration config, IWebHostEnvironment env, CancellationToken ct) =>
|
||||||
{
|
{
|
||||||
var episode = await db.Episodes.AsNoTracking().FirstOrDefaultAsync(e => e.Id == episodeId, ct);
|
var episode = await db.Episodes.AsNoTracking().FirstOrDefaultAsync(e => e.Id == episodeId, ct);
|
||||||
if (episode is null) return Results.NotFound();
|
if (episode is null) return Results.NotFound();
|
||||||
|
|
||||||
var relative = episode.AudioUrl.TrimStart('/');
|
var audioRoot = config["AudioRoot"] ?? "/data/audio";
|
||||||
var path = Path.Combine(env.ContentRootPath, "wwwroot", relative);
|
var relative = episode.AudioUrl.TrimStart('/').Replace('\\', '/');
|
||||||
if (!File.Exists(path))
|
|
||||||
|
// Prefer PVC mount; fallback to wwwroot for local sample files
|
||||||
|
var candidates = new[]
|
||||||
{
|
{
|
||||||
// Fallback placeholder: return empty audio-friendly response path for demo
|
Path.Combine(audioRoot, relative),
|
||||||
var placeholder = Path.Combine(env.ContentRootPath, "wwwroot", "audio", "silence.mp3");
|
Path.Combine(env.ContentRootPath, "wwwroot", relative),
|
||||||
if (File.Exists(placeholder))
|
Path.Combine(env.ContentRootPath, "wwwroot", "audio", Path.GetFileName(relative))
|
||||||
return Results.File(placeholder, "audio/mpeg", enableRangeProcessing: true);
|
};
|
||||||
return Results.NotFound();
|
|
||||||
|
foreach (var path in candidates)
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
return Results.File(path, "audio/mpeg", enableRangeProcessing: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Results.File(path, "audio/mpeg", enableRangeProcessing: true);
|
var silence = Path.Combine(env.ContentRootPath, "wwwroot", "audio", "silence.mp3");
|
||||||
|
if (File.Exists(silence))
|
||||||
|
return Results.File(silence, "audio/mpeg", enableRangeProcessing: true);
|
||||||
|
|
||||||
|
return Results.NotFound();
|
||||||
});
|
});
|
||||||
|
|
||||||
return group;
|
return group;
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ using (var scope = app.Services.CreateScope())
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await DbSeeder.SeedAsync(db);
|
var audioRoot = app.Configuration["AudioRoot"] ?? "/data/audio";
|
||||||
|
await DbSeeder.SeedAsync(db, audioRoot);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (attempt < 30)
|
catch (Exception ex) when (attempt < 30)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"Default": "Host=localhost;Port=5434;Database=tafsirquran;Username=tafsir;Password=tafsir_dev"
|
"Default": "Server=localhost,31433;Database=TafsirQuran;User Id=sa;Password=87zH26nbqT;TrustServerCertificate=True"
|
||||||
},
|
},
|
||||||
|
"AudioRoot": "/data/audio",
|
||||||
"Jwt": {
|
"Jwt": {
|
||||||
"Key": "TafsirQuran-Dev-Secret-Key-Change-In-Production-32+",
|
"Key": "TafsirQuran-Dev-Secret-Key-Change-In-Production-32+",
|
||||||
"Issuer": "TafsirQuran",
|
"Issuer": "TafsirQuran",
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ public static class DependencyInjection
|
|||||||
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
|
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
var connectionString = configuration.GetConnectionString("Default")
|
var connectionString = configuration.GetConnectionString("Default")
|
||||||
?? "Host=localhost;Port=5434;Database=tafsirquran;Username=tafsir;Password=tafsir_dev";
|
?? "Server=localhost,31433;Database=TafsirQuran;User Id=sa;Password=87zH26nbqT;TrustServerCertificate=True";
|
||||||
|
|
||||||
services.AddDbContext<AppDbContext>(options =>
|
services.AddDbContext<AppDbContext>(options =>
|
||||||
options.UseNpgsql(connectionString));
|
options.UseSqlServer(connectionString));
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,114 +7,154 @@ public static class DbSeeder
|
|||||||
{
|
{
|
||||||
public static readonly Guid GhatrehChekaniId = Guid.Parse("11111111-1111-1111-1111-111111111111");
|
public static readonly Guid GhatrehChekaniId = Guid.Parse("11111111-1111-1111-1111-111111111111");
|
||||||
|
|
||||||
public static async Task SeedAsync(AppDbContext db, CancellationToken ct = default)
|
private static readonly (string Ar, string Fa)[] SurahNames =
|
||||||
|
[
|
||||||
|
("الفاتحة", "فاتحه"), ("البقرة", "بقره"), ("آل عمران", "آل عمران"), ("النساء", "نساء"),
|
||||||
|
("المائدة", "مائده"), ("الأنعام", "انعام"), ("الأعراف", "اعراف"), ("الأنفال", "انفال"),
|
||||||
|
("التوبة", "توبه"), ("يونس", "یونس"), ("هود", "هود"), ("يوسف", "یوسف"),
|
||||||
|
("الرعد", "رعد"), ("إبراهيم", "ابراهیم"), ("الحجر", "حجر"), ("النحل", "نحل"),
|
||||||
|
("الإسراء", "اسراء"), ("الكهف", "کهف"), ("مريم", "مریم"), ("طه", "طه"),
|
||||||
|
("الأنبياء", "انبیاء"), ("الحج", "حج"), ("المؤمنون", "مؤمنون"), ("النور", "نور"),
|
||||||
|
("الفرقان", "فرقان"), ("الشعراء", "شعراء"), ("النمل", "نمل"), ("القصص", "قصص"),
|
||||||
|
("العنكبوت", "عنکبوت"), ("الروم", "روم"), ("لقمان", "لقمان"), ("السجدة", "سجده"),
|
||||||
|
("الأحزاب", "احزاب"), ("سبأ", "سبأ"), ("فاطر", "فاطر"), ("يس", "یس"),
|
||||||
|
("الصافات", "صافات"), ("ص", "ص"), ("الزمر", "زمر"), ("غافر", "غافر"),
|
||||||
|
("فصلت", "فصلت"), ("الشورى", "شوری"), ("الزخرف", "زخرف"), ("الدخان", "دخان"),
|
||||||
|
("الجاثية", "جاثیه"), ("الأحقاف", "احقاف"), ("محمد", "محمد"), ("الفتح", "فتح"),
|
||||||
|
("الحجرات", "حجرات"), ("ق", "ق"), ("الذاريات", "ذاریات"), ("الطور", "طور"),
|
||||||
|
("النجم", "نجم"), ("القمر", "قمر"), ("الرحمن", "رحمن"), ("الواقعة", "واقعه"),
|
||||||
|
("الحديد", "حدید"), ("المجادلة", "مجادله"), ("الحشر", "حشر"), ("الممتحنة", "ممتحنه"),
|
||||||
|
("الصف", "صف"), ("الجمعة", "جمعه"), ("المنافقون", "منافقون"), ("التغابن", "تغابن"),
|
||||||
|
("الطلاق", "طلاق"), ("التحريم", "تحریم"), ("الملك", "ملک"), ("القلم", "قلم"),
|
||||||
|
("الحاقة", "حاقه"), ("المعارج", "معارج"), ("نوح", "نوح"), ("الجن", "جن"),
|
||||||
|
("المزمل", "مزمل"), ("المدثر", "مدثر"), ("القيامة", "قیامه"), ("الإنسان", "انسان"),
|
||||||
|
("المرسلات", "مرسلات"), ("النبأ", "نبأ"), ("النازعات", "نازعات"), ("عبس", "عبس"),
|
||||||
|
("التكوير", "تکویر"), ("الانفطار", "انفطار"), ("المطففين", "مطففین"), ("الانشقاق", "انشقاق"),
|
||||||
|
("البروج", "بروج"), ("الطارق", "طارق"), ("الأعلى", "اعلی"), ("الغاشية", "غاشیه"),
|
||||||
|
("الفجر", "فجر"), ("البلد", "بلد"), ("الشمس", "شمس"), ("الليل", "لیل"),
|
||||||
|
("الضحى", "ضحی"), ("الشرح", "شرح"), ("التين", "تین"), ("العلق", "علق"),
|
||||||
|
("القدر", "قدر"), ("البينة", "بینه"), ("الزلزلة", "زلزله"), ("العاديات", "عادیات"),
|
||||||
|
("القارعة", "قارعه"), ("التكاثر", "تکاثر"), ("العصر", "عصر"), ("الهمزة", "همزه"),
|
||||||
|
("الفيل", "فیل"), ("قريش", "قریش"), ("الماعون", "ماعون"), ("الكوثر", "کوثر"),
|
||||||
|
("الكافرون", "کافرون"), ("النصر", "نصر"), ("المسد", "مسد"), ("الإخلاص", "اخلاص"),
|
||||||
|
("الفلق", "فلق"), ("الناس", "ناس")
|
||||||
|
];
|
||||||
|
|
||||||
|
public static async Task SeedAsync(AppDbContext db, string? audioRoot = null, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
await db.Database.MigrateAsync(ct);
|
await db.Database.MigrateAsync(ct);
|
||||||
|
|
||||||
if (!await db.Surahs.AnyAsync(ct))
|
if (!await db.Surahs.AnyAsync(ct))
|
||||||
{
|
{
|
||||||
db.Surahs.AddRange(
|
for (var i = 0; i < SurahNames.Length; i++)
|
||||||
new Surah { Id = 1, Number = 1, NameAr = "الفاتحة", NameFa = "فاتحه" },
|
|
||||||
new Surah { Id = 2, Number = 2, NameAr = "البقرة", NameFa = "بقره" },
|
|
||||||
new Surah { Id = 112, Number = 112, NameAr = "الإخلاص", NameFa = "اخلاص" }
|
|
||||||
);
|
|
||||||
|
|
||||||
db.Ayahs.AddRange(
|
|
||||||
new Ayah
|
|
||||||
{
|
{
|
||||||
SurahId = 1,
|
var n = i + 1;
|
||||||
Number = 1,
|
db.Surahs.Add(new Surah
|
||||||
TextAr = "بِسْمِ اللَّهِ الرَّحْمَٰنِ الرَّحِيمِ",
|
|
||||||
TextFa = "به نام خداوند بخشنده مهربان"
|
|
||||||
},
|
|
||||||
new Ayah
|
|
||||||
{
|
{
|
||||||
SurahId = 1,
|
Id = n,
|
||||||
Number = 2,
|
Number = n,
|
||||||
TextAr = "الْحَمْدُ لِلَّهِ رَبِّ الْعَالَمِينَ",
|
NameAr = SurahNames[i].Ar,
|
||||||
TextFa = "ستایش مخصوص خداوندی است که پروردگار جهانیان است"
|
NameFa = SurahNames[i].Fa
|
||||||
},
|
});
|
||||||
new Ayah
|
|
||||||
{
|
|
||||||
SurahId = 1,
|
|
||||||
Number = 3,
|
|
||||||
TextAr = "الرَّحْمَٰنِ الرَّحِيمِ",
|
|
||||||
TextFa = "بخشنده مهربان"
|
|
||||||
},
|
|
||||||
new Ayah
|
|
||||||
{
|
|
||||||
SurahId = 112,
|
|
||||||
Number = 1,
|
|
||||||
TextAr = "قُلْ هُوَ اللَّهُ أَحَدٌ",
|
|
||||||
TextFa = "بگو اوست خدای یگانه"
|
|
||||||
}
|
}
|
||||||
);
|
await db.SaveChangesAsync(ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await db.Tafsirs.AnyAsync(ct))
|
if (!await db.Tafsirs.AnyAsync(ct))
|
||||||
{
|
{
|
||||||
var tafsir = new Tafsir
|
db.Tafsirs.Add(new Tafsir
|
||||||
{
|
{
|
||||||
Id = GhatrehChekaniId,
|
Id = GhatrehChekaniId,
|
||||||
Title = "تفسیر قطره چکانی",
|
Title = "تفسیر قطره چکانی",
|
||||||
Author = "قرائتی",
|
Author = "قرائتی",
|
||||||
Slug = "ghatreh-chekani",
|
Slug = "ghatreh-chekani",
|
||||||
IsPublished = true
|
IsPublished = true
|
||||||
};
|
});
|
||||||
db.Tafsirs.Add(tafsir);
|
|
||||||
|
|
||||||
db.Episodes.AddRange(
|
|
||||||
new TafsirEpisode
|
|
||||||
{
|
|
||||||
Id = Guid.Parse("22222222-2222-2222-2222-222222222201"),
|
|
||||||
TafsirId = GhatrehChekaniId,
|
|
||||||
SurahId = 1,
|
|
||||||
AyahNumber = 1,
|
|
||||||
Title = "فاتحه آیه ۱",
|
|
||||||
AudioUrl = "/audio/sample-1.mp3",
|
|
||||||
DurationSeconds = 180,
|
|
||||||
SortOrder = 1,
|
|
||||||
Summary = "تفسیر قطرهچکانی سوره فاتحه آیه اول"
|
|
||||||
},
|
|
||||||
new TafsirEpisode
|
|
||||||
{
|
|
||||||
Id = Guid.Parse("22222222-2222-2222-2222-222222222202"),
|
|
||||||
TafsirId = GhatrehChekaniId,
|
|
||||||
SurahId = 1,
|
|
||||||
AyahNumber = 2,
|
|
||||||
Title = "فاتحه آیه ۲",
|
|
||||||
AudioUrl = "/audio/sample-2.mp3",
|
|
||||||
DurationSeconds = 210,
|
|
||||||
SortOrder = 2,
|
|
||||||
Summary = "تفسیر قطرهچکانی سوره فاتحه آیه دوم"
|
|
||||||
},
|
|
||||||
new TafsirEpisode
|
|
||||||
{
|
|
||||||
Id = Guid.Parse("22222222-2222-2222-2222-222222222203"),
|
|
||||||
TafsirId = GhatrehChekaniId,
|
|
||||||
SurahId = 1,
|
|
||||||
AyahNumber = 3,
|
|
||||||
Title = "فاتحه آیه ۳",
|
|
||||||
AudioUrl = "/audio/sample-3.mp3",
|
|
||||||
DurationSeconds = 195,
|
|
||||||
SortOrder = 3,
|
|
||||||
Summary = "تفسیر قطرهچکانی سوره فاتحه آیه سوم"
|
|
||||||
},
|
|
||||||
new TafsirEpisode
|
|
||||||
{
|
|
||||||
Id = Guid.Parse("22222222-2222-2222-2222-222222222204"),
|
|
||||||
TafsirId = GhatrehChekaniId,
|
|
||||||
SurahId = 112,
|
|
||||||
AyahNumber = 1,
|
|
||||||
Title = "اخلاص آیه ۱",
|
|
||||||
AudioUrl = "/audio/sample-4.mp3",
|
|
||||||
DurationSeconds = 160,
|
|
||||||
SortOrder = 4,
|
|
||||||
Summary = "تفسیر قطرهچکانی سوره اخلاص آیه اول"
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.SaveChangesAsync(ct);
|
await db.SaveChangesAsync(ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await SyncEpisodesFromAudioAsync(db, audioRoot, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task SyncEpisodesFromAudioAsync(AppDbContext db, string? audioRoot, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(audioRoot) || !Directory.Exists(audioRoot))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var tafsir = await db.Tafsirs.FirstOrDefaultAsync(t => t.Id == GhatrehChekaniId, ct);
|
||||||
|
if (tafsir is null) return;
|
||||||
|
|
||||||
|
var existing = await db.Episodes
|
||||||
|
.Where(e => e.TafsirId == GhatrehChekaniId)
|
||||||
|
.Select(e => e.AudioUrl)
|
||||||
|
.ToListAsync(ct);
|
||||||
|
var existingSet = existing.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var audioFiles = Directory.EnumerateFiles(audioRoot, "*.*", SearchOption.AllDirectories)
|
||||||
|
.Where(f => f.EndsWith(".mp3", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| f.EndsWith(".m4a", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| f.EndsWith(".ogg", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| f.EndsWith(".wav", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.OrderBy(f => f, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (audioFiles.Count == 0) return;
|
||||||
|
|
||||||
|
var sort = existing.Count;
|
||||||
|
var added = 0;
|
||||||
|
foreach (var file in audioFiles)
|
||||||
|
{
|
||||||
|
var relative = Path.GetRelativePath(audioRoot, file).Replace('\\', '/');
|
||||||
|
if (existingSet.Contains(relative)) continue;
|
||||||
|
|
||||||
|
var surahNumber = GuessSurahNumber(relative, file);
|
||||||
|
var ayahNumber = GuessAyahNumber(Path.GetFileNameWithoutExtension(file));
|
||||||
|
sort++;
|
||||||
|
db.Episodes.Add(new TafsirEpisode
|
||||||
|
{
|
||||||
|
Id = Guid.NewGuid(),
|
||||||
|
TafsirId = GhatrehChekaniId,
|
||||||
|
SurahId = surahNumber,
|
||||||
|
AyahNumber = ayahNumber,
|
||||||
|
Title = $"{SurahNames[surahNumber - 1].Fa} — {Path.GetFileNameWithoutExtension(file)}",
|
||||||
|
AudioUrl = relative,
|
||||||
|
DurationSeconds = 0,
|
||||||
|
SortOrder = sort,
|
||||||
|
Summary = null
|
||||||
|
});
|
||||||
|
added++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (added > 0)
|
||||||
|
await db.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GuessSurahNumber(string relative, string fullPath)
|
||||||
|
{
|
||||||
|
var parts = relative.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
foreach (var part in parts)
|
||||||
|
{
|
||||||
|
var digits = new string(part.TakeWhile(char.IsDigit).ToArray());
|
||||||
|
if (digits.Length >= 1 && int.TryParse(digits, out var n) && n is >= 1 and <= 114)
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = Path.GetFileName(fullPath);
|
||||||
|
var nameDigits = new string(name.TakeWhile(char.IsDigit).ToArray());
|
||||||
|
if (nameDigits.Length >= 1 && int.TryParse(nameDigits, out var fromName) && fromName is >= 1 and <= 114)
|
||||||
|
return fromName;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GuessAyahNumber(string fileName)
|
||||||
|
{
|
||||||
|
// patterns: ..._001, -1, .1, ayah01
|
||||||
|
var matches = System.Text.RegularExpressions.Regex.Matches(fileName, @"(\d+)");
|
||||||
|
if (matches.Count == 0) return 1;
|
||||||
|
// prefer last number group as ayah when multiple
|
||||||
|
if (int.TryParse(matches[^1].Value, out var n) && n > 0)
|
||||||
|
return n;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+56
-54
@@ -2,9 +2,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|
||||||
using TafsirQuran.Infrastructure.Persistence;
|
using TafsirQuran.Infrastructure.Persistence;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
@@ -12,8 +12,8 @@ using TafsirQuran.Infrastructure.Persistence;
|
|||||||
namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(AppDbContext))]
|
[DbContext(typeof(AppDbContext))]
|
||||||
[Migration("20261009174748_InitialCreate")]
|
[Migration("20261009190049_InitialSqlServer")]
|
||||||
partial class InitialCreate
|
partial class InitialSqlServer
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
@@ -21,32 +21,32 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "9.0.10")
|
.HasAnnotation("ProductVersion", "9.0.10")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
modelBuilder.Entity("TafsirQuran.Domain.Entities.AppUser", b =>
|
modelBuilder.Entity("TafsirQuran.Domain.Entities.AppUser", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("CreatedAt")
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("datetimeoffset");
|
||||||
|
|
||||||
b.Property<string>("DisplayName")
|
b.Property<string>("DisplayName")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(128)
|
.HasMaxLength(128)
|
||||||
.HasColumnType("character varying(128)");
|
.HasColumnType("nvarchar(128)");
|
||||||
|
|
||||||
b.Property<string>("Email")
|
b.Property<string>("Email")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(256)
|
.HasMaxLength(256)
|
||||||
.HasColumnType("character varying(256)");
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
||||||
b.Property<string>("PasswordHash")
|
b.Property<string>("PasswordHash")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -60,20 +60,20 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<int>("Number")
|
b.Property<int>("Number")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<int>("SurahId")
|
b.Property<int>("SurahId")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<string>("TextAr")
|
b.Property<string>("TextAr")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<string>("TextFa")
|
b.Property<string>("TextFa")
|
||||||
.HasColumnType("text");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -87,25 +87,25 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("CreatedAt")
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("datetimeoffset");
|
||||||
|
|
||||||
b.Property<string>("DeviceId")
|
b.Property<string>("DeviceId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("nvarchar(64)");
|
||||||
|
|
||||||
b.Property<Guid>("EpisodeId")
|
b.Property<Guid>("EpisodeId")
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<string>("Note")
|
b.Property<string>("Note")
|
||||||
.HasMaxLength(500)
|
.HasMaxLength(500)
|
||||||
.HasColumnType("character varying(500)");
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
b.Property<Guid?>("UserId")
|
b.Property<Guid?>("UserId")
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -114,7 +114,8 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
b.HasIndex("UserId");
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
b.HasIndex("DeviceId", "UserId", "EpisodeId")
|
b.HasIndex("DeviceId", "UserId", "EpisodeId")
|
||||||
.IsUnique();
|
.IsUnique()
|
||||||
|
.HasFilter("[UserId] IS NOT NULL");
|
||||||
|
|
||||||
b.ToTable("Bookmarks");
|
b.ToTable("Bookmarks");
|
||||||
});
|
});
|
||||||
@@ -123,21 +124,21 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("CreatedAt")
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("datetimeoffset");
|
||||||
|
|
||||||
b.Property<string>("DeviceId")
|
b.Property<string>("DeviceId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("nvarchar(64)");
|
||||||
|
|
||||||
b.Property<Guid>("EpisodeId")
|
b.Property<Guid>("EpisodeId")
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<Guid?>("UserId")
|
b.Property<Guid?>("UserId")
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -146,7 +147,8 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
b.HasIndex("UserId");
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
b.HasIndex("DeviceId", "UserId", "EpisodeId")
|
b.HasIndex("DeviceId", "UserId", "EpisodeId")
|
||||||
.IsUnique();
|
.IsUnique()
|
||||||
|
.HasFilter("[UserId] IS NOT NULL");
|
||||||
|
|
||||||
b.ToTable("Favorites");
|
b.ToTable("Favorites");
|
||||||
});
|
});
|
||||||
@@ -155,24 +157,24 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<string>("DeviceId")
|
b.Property<string>("DeviceId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("nvarchar(64)");
|
||||||
|
|
||||||
b.Property<Guid>("EpisodeId")
|
b.Property<Guid>("EpisodeId")
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<double>("PositionSeconds")
|
b.Property<double>("PositionSeconds")
|
||||||
.HasColumnType("double precision");
|
.HasColumnType("float");
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("UpdatedAt")
|
b.Property<DateTimeOffset>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("datetimeoffset");
|
||||||
|
|
||||||
b.Property<Guid?>("UserId")
|
b.Property<Guid?>("UserId")
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -189,22 +191,22 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("integer");
|
.HasColumnType("int");
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
b.Property<string>("NameAr")
|
b.Property<string>("NameAr")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(80)
|
.HasMaxLength(80)
|
||||||
.HasColumnType("character varying(80)");
|
.HasColumnType("nvarchar(80)");
|
||||||
|
|
||||||
b.Property<string>("NameFa")
|
b.Property<string>("NameFa")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(80)
|
.HasMaxLength(80)
|
||||||
.HasColumnType("character varying(80)");
|
.HasColumnType("nvarchar(80)");
|
||||||
|
|
||||||
b.Property<int>("Number")
|
b.Property<int>("Number")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -218,28 +220,28 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<string>("Author")
|
b.Property<string>("Author")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(120)
|
.HasMaxLength(120)
|
||||||
.HasColumnType("character varying(120)");
|
.HasColumnType("nvarchar(120)");
|
||||||
|
|
||||||
b.Property<string>("CoverUrl")
|
b.Property<string>("CoverUrl")
|
||||||
.HasColumnType("text");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<bool>("IsPublished")
|
b.Property<bool>("IsPublished")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("bit");
|
||||||
|
|
||||||
b.Property<string>("Slug")
|
b.Property<string>("Slug")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(120)
|
.HasMaxLength(120)
|
||||||
.HasColumnType("character varying(120)");
|
.HasColumnType("nvarchar(120)");
|
||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(200)
|
.HasMaxLength(200)
|
||||||
.HasColumnType("character varying(200)");
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -253,34 +255,34 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<string>("AudioUrl")
|
b.Property<string>("AudioUrl")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<int>("AyahNumber")
|
b.Property<int>("AyahNumber")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<int>("DurationSeconds")
|
b.Property<int>("DurationSeconds")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<int>("SortOrder")
|
b.Property<int>("SortOrder")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<string>("Summary")
|
b.Property<string>("Summary")
|
||||||
.HasColumnType("text");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<int>("SurahId")
|
b.Property<int>("SurahId")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<Guid>("TafsirId")
|
b.Property<Guid>("TafsirId")
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(300)
|
.HasMaxLength(300)
|
||||||
.HasColumnType("character varying(300)");
|
.HasColumnType("nvarchar(300)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
+52
-51
@@ -1,13 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public partial class InitialCreate : Migration
|
public partial class InitialSqlServer : Migration
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
@@ -16,11 +15,11 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
name: "Surahs",
|
name: "Surahs",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<int>(type: "integer", nullable: false)
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
.Annotation("SqlServer:Identity", "1, 1"),
|
||||||
Number = table.Column<int>(type: "integer", nullable: false),
|
Number = table.Column<int>(type: "int", nullable: false),
|
||||||
NameAr = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: false),
|
NameAr = table.Column<string>(type: "nvarchar(80)", maxLength: 80, nullable: false),
|
||||||
NameFa = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: false)
|
NameFa = table.Column<string>(type: "nvarchar(80)", maxLength: 80, nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@@ -31,12 +30,12 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
name: "Tafsirs",
|
name: "Tafsirs",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
Title = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
Title = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
|
||||||
Author = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: false),
|
Author = table.Column<string>(type: "nvarchar(120)", maxLength: 120, nullable: false),
|
||||||
Slug = table.Column<string>(type: "character varying(120)", maxLength: 120, nullable: false),
|
Slug = table.Column<string>(type: "nvarchar(120)", maxLength: 120, nullable: false),
|
||||||
CoverUrl = table.Column<string>(type: "text", nullable: true),
|
CoverUrl = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||||
IsPublished = table.Column<bool>(type: "boolean", nullable: false)
|
IsPublished = table.Column<bool>(type: "bit", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@@ -47,11 +46,11 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
name: "Users",
|
name: "Users",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
Email = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
Email = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: false),
|
||||||
PasswordHash = table.Column<string>(type: "text", nullable: false),
|
PasswordHash = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||||
DisplayName = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
DisplayName = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
|
||||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false)
|
CreatedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@@ -62,11 +61,11 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
name: "Ayahs",
|
name: "Ayahs",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
SurahId = table.Column<int>(type: "integer", nullable: false),
|
SurahId = table.Column<int>(type: "int", nullable: false),
|
||||||
Number = table.Column<int>(type: "integer", nullable: false),
|
Number = table.Column<int>(type: "int", nullable: false),
|
||||||
TextAr = table.Column<string>(type: "text", nullable: false),
|
TextAr = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||||
TextFa = table.Column<string>(type: "text", nullable: true)
|
TextFa = table.Column<string>(type: "nvarchar(max)", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@@ -83,15 +82,15 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
name: "Episodes",
|
name: "Episodes",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
TafsirId = table.Column<Guid>(type: "uuid", nullable: false),
|
TafsirId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
SurahId = table.Column<int>(type: "integer", nullable: false),
|
SurahId = table.Column<int>(type: "int", nullable: false),
|
||||||
AyahNumber = table.Column<int>(type: "integer", nullable: false),
|
AyahNumber = table.Column<int>(type: "int", nullable: false),
|
||||||
Title = table.Column<string>(type: "character varying(300)", maxLength: 300, nullable: false),
|
Title = table.Column<string>(type: "nvarchar(300)", maxLength: 300, nullable: false),
|
||||||
AudioUrl = table.Column<string>(type: "text", nullable: false),
|
AudioUrl = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||||
DurationSeconds = table.Column<int>(type: "integer", nullable: false),
|
DurationSeconds = table.Column<int>(type: "int", nullable: false),
|
||||||
SortOrder = table.Column<int>(type: "integer", nullable: false),
|
SortOrder = table.Column<int>(type: "int", nullable: false),
|
||||||
Summary = table.Column<string>(type: "text", nullable: true)
|
Summary = table.Column<string>(type: "nvarchar(max)", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@@ -114,12 +113,12 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
name: "Bookmarks",
|
name: "Bookmarks",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
UserId = table.Column<Guid>(type: "uuid", nullable: true),
|
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
DeviceId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
DeviceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||||
EpisodeId = table.Column<Guid>(type: "uuid", nullable: false),
|
EpisodeId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
Note = table.Column<string>(type: "character varying(500)", maxLength: 500, nullable: true),
|
Note = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false)
|
CreatedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@@ -141,11 +140,11 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
name: "Favorites",
|
name: "Favorites",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
UserId = table.Column<Guid>(type: "uuid", nullable: true),
|
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
DeviceId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
DeviceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||||
EpisodeId = table.Column<Guid>(type: "uuid", nullable: false),
|
EpisodeId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false)
|
CreatedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@@ -167,12 +166,12 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
name: "ListeningProgresses",
|
name: "ListeningProgresses",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
UserId = table.Column<Guid>(type: "uuid", nullable: true),
|
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||||
DeviceId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false),
|
DeviceId = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
|
||||||
EpisodeId = table.Column<Guid>(type: "uuid", nullable: false),
|
EpisodeId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
PositionSeconds = table.Column<double>(type: "double precision", nullable: false),
|
PositionSeconds = table.Column<double>(type: "float", nullable: false),
|
||||||
UpdatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false)
|
UpdatedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@@ -200,7 +199,8 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
name: "IX_Bookmarks_DeviceId_UserId_EpisodeId",
|
name: "IX_Bookmarks_DeviceId_UserId_EpisodeId",
|
||||||
table: "Bookmarks",
|
table: "Bookmarks",
|
||||||
columns: new[] { "DeviceId", "UserId", "EpisodeId" },
|
columns: new[] { "DeviceId", "UserId", "EpisodeId" },
|
||||||
unique: true);
|
unique: true,
|
||||||
|
filter: "[UserId] IS NOT NULL");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Bookmarks_EpisodeId",
|
name: "IX_Bookmarks_EpisodeId",
|
||||||
@@ -226,7 +226,8 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
name: "IX_Favorites_DeviceId_UserId_EpisodeId",
|
name: "IX_Favorites_DeviceId_UserId_EpisodeId",
|
||||||
table: "Favorites",
|
table: "Favorites",
|
||||||
columns: new[] { "DeviceId", "UserId", "EpisodeId" },
|
columns: new[] { "DeviceId", "UserId", "EpisodeId" },
|
||||||
unique: true);
|
unique: true,
|
||||||
|
filter: "[UserId] IS NOT NULL");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Favorites_EpisodeId",
|
name: "IX_Favorites_EpisodeId",
|
||||||
+54
-52
@@ -2,8 +2,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|
||||||
using TafsirQuran.Infrastructure.Persistence;
|
using TafsirQuran.Infrastructure.Persistence;
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
@@ -18,32 +18,32 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "9.0.10")
|
.HasAnnotation("ProductVersion", "9.0.10")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
|
|
||||||
modelBuilder.Entity("TafsirQuran.Domain.Entities.AppUser", b =>
|
modelBuilder.Entity("TafsirQuran.Domain.Entities.AppUser", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("CreatedAt")
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("datetimeoffset");
|
||||||
|
|
||||||
b.Property<string>("DisplayName")
|
b.Property<string>("DisplayName")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(128)
|
.HasMaxLength(128)
|
||||||
.HasColumnType("character varying(128)");
|
.HasColumnType("nvarchar(128)");
|
||||||
|
|
||||||
b.Property<string>("Email")
|
b.Property<string>("Email")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(256)
|
.HasMaxLength(256)
|
||||||
.HasColumnType("character varying(256)");
|
.HasColumnType("nvarchar(256)");
|
||||||
|
|
||||||
b.Property<string>("PasswordHash")
|
b.Property<string>("PasswordHash")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -57,20 +57,20 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<int>("Number")
|
b.Property<int>("Number")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<int>("SurahId")
|
b.Property<int>("SurahId")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<string>("TextAr")
|
b.Property<string>("TextAr")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<string>("TextFa")
|
b.Property<string>("TextFa")
|
||||||
.HasColumnType("text");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -84,25 +84,25 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("CreatedAt")
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("datetimeoffset");
|
||||||
|
|
||||||
b.Property<string>("DeviceId")
|
b.Property<string>("DeviceId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("nvarchar(64)");
|
||||||
|
|
||||||
b.Property<Guid>("EpisodeId")
|
b.Property<Guid>("EpisodeId")
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<string>("Note")
|
b.Property<string>("Note")
|
||||||
.HasMaxLength(500)
|
.HasMaxLength(500)
|
||||||
.HasColumnType("character varying(500)");
|
.HasColumnType("nvarchar(500)");
|
||||||
|
|
||||||
b.Property<Guid?>("UserId")
|
b.Property<Guid?>("UserId")
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -111,7 +111,8 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
b.HasIndex("UserId");
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
b.HasIndex("DeviceId", "UserId", "EpisodeId")
|
b.HasIndex("DeviceId", "UserId", "EpisodeId")
|
||||||
.IsUnique();
|
.IsUnique()
|
||||||
|
.HasFilter("[UserId] IS NOT NULL");
|
||||||
|
|
||||||
b.ToTable("Bookmarks");
|
b.ToTable("Bookmarks");
|
||||||
});
|
});
|
||||||
@@ -120,21 +121,21 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("CreatedAt")
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("datetimeoffset");
|
||||||
|
|
||||||
b.Property<string>("DeviceId")
|
b.Property<string>("DeviceId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("nvarchar(64)");
|
||||||
|
|
||||||
b.Property<Guid>("EpisodeId")
|
b.Property<Guid>("EpisodeId")
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<Guid?>("UserId")
|
b.Property<Guid?>("UserId")
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -143,7 +144,8 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
b.HasIndex("UserId");
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
b.HasIndex("DeviceId", "UserId", "EpisodeId")
|
b.HasIndex("DeviceId", "UserId", "EpisodeId")
|
||||||
.IsUnique();
|
.IsUnique()
|
||||||
|
.HasFilter("[UserId] IS NOT NULL");
|
||||||
|
|
||||||
b.ToTable("Favorites");
|
b.ToTable("Favorites");
|
||||||
});
|
});
|
||||||
@@ -152,24 +154,24 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<string>("DeviceId")
|
b.Property<string>("DeviceId")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(64)
|
.HasMaxLength(64)
|
||||||
.HasColumnType("character varying(64)");
|
.HasColumnType("nvarchar(64)");
|
||||||
|
|
||||||
b.Property<Guid>("EpisodeId")
|
b.Property<Guid>("EpisodeId")
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<double>("PositionSeconds")
|
b.Property<double>("PositionSeconds")
|
||||||
.HasColumnType("double precision");
|
.HasColumnType("float");
|
||||||
|
|
||||||
b.Property<DateTimeOffset>("UpdatedAt")
|
b.Property<DateTimeOffset>("UpdatedAt")
|
||||||
.HasColumnType("timestamp with time zone");
|
.HasColumnType("datetimeoffset");
|
||||||
|
|
||||||
b.Property<Guid?>("UserId")
|
b.Property<Guid?>("UserId")
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -186,22 +188,22 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("integer");
|
.HasColumnType("int");
|
||||||
|
|
||||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
b.Property<string>("NameAr")
|
b.Property<string>("NameAr")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(80)
|
.HasMaxLength(80)
|
||||||
.HasColumnType("character varying(80)");
|
.HasColumnType("nvarchar(80)");
|
||||||
|
|
||||||
b.Property<string>("NameFa")
|
b.Property<string>("NameFa")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(80)
|
.HasMaxLength(80)
|
||||||
.HasColumnType("character varying(80)");
|
.HasColumnType("nvarchar(80)");
|
||||||
|
|
||||||
b.Property<int>("Number")
|
b.Property<int>("Number")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -215,28 +217,28 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<string>("Author")
|
b.Property<string>("Author")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(120)
|
.HasMaxLength(120)
|
||||||
.HasColumnType("character varying(120)");
|
.HasColumnType("nvarchar(120)");
|
||||||
|
|
||||||
b.Property<string>("CoverUrl")
|
b.Property<string>("CoverUrl")
|
||||||
.HasColumnType("text");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<bool>("IsPublished")
|
b.Property<bool>("IsPublished")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("bit");
|
||||||
|
|
||||||
b.Property<string>("Slug")
|
b.Property<string>("Slug")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(120)
|
.HasMaxLength(120)
|
||||||
.HasColumnType("character varying(120)");
|
.HasColumnType("nvarchar(120)");
|
||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(200)
|
.HasMaxLength(200)
|
||||||
.HasColumnType("character varying(200)");
|
.HasColumnType("nvarchar(200)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
@@ -250,34 +252,34 @@ namespace TafsirQuran.Infrastructure.Persistence.Migrations
|
|||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<string>("AudioUrl")
|
b.Property<string>("AudioUrl")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("text");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<int>("AyahNumber")
|
b.Property<int>("AyahNumber")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<int>("DurationSeconds")
|
b.Property<int>("DurationSeconds")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<int>("SortOrder")
|
b.Property<int>("SortOrder")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<string>("Summary")
|
b.Property<string>("Summary")
|
||||||
.HasColumnType("text");
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<int>("SurahId")
|
b.Property<int>("SurahId")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<Guid>("TafsirId")
|
b.Property<Guid>("TafsirId")
|
||||||
.HasColumnType("uuid");
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(300)
|
.HasMaxLength(300)
|
||||||
.HasColumnType("character varying(300)");
|
.HasColumnType("nvarchar(300)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,13 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.10" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.10" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.10" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.10" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.10" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.10" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.10" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.10" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.10" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
@using TafsirQuran.UI
|
||||||
|
@using TafsirQuran.UI.Layout
|
||||||
|
|
||||||
|
<Router AppAssembly="typeof(Routes).Assembly">
|
||||||
|
<Found Context="routeData">
|
||||||
|
<RouteView RouteData="routeData" DefaultLayout="typeof(AppLayout)" />
|
||||||
|
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||||
|
</Found>
|
||||||
|
<NotFound>
|
||||||
|
<LayoutView Layout="typeof(AppLayout)">
|
||||||
|
<p class="muted">صفحه پیدا نشد.</p>
|
||||||
|
</LayoutView>
|
||||||
|
</NotFound>
|
||||||
|
</Router>
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
@inherits LayoutComponentBase
|
|
||||||
|
|
||||||
@Body
|
|
||||||
|
|
||||||
<div id="blazor-error-ui" data-nosnippet>
|
|
||||||
An unhandled error has occurred.
|
|
||||||
<a href="." class="reload">Reload</a>
|
|
||||||
<span class="dismiss">🗙</span>
|
|
||||||
</div>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
#blazor-error-ui {
|
|
||||||
color-scheme: light only;
|
|
||||||
background: lightyellow;
|
|
||||||
bottom: 0;
|
|
||||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: none;
|
|
||||||
left: 0;
|
|
||||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
|
||||||
position: fixed;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
#blazor-error-ui .dismiss {
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
right: 0.75rem;
|
|
||||||
top: 0.5rem;
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
@page "/Error"
|
|
||||||
@using System.Diagnostics
|
|
||||||
|
|
||||||
<PageTitle>Error</PageTitle>
|
|
||||||
|
|
||||||
<h1 class="text-danger">Error.</h1>
|
|
||||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
|
||||||
|
|
||||||
@if (ShowRequestId)
|
|
||||||
{
|
|
||||||
<p>
|
|
||||||
<strong>Request ID:</strong> <code>@RequestId</code>
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
<h3>Development Mode</h3>
|
|
||||||
<p>
|
|
||||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
|
||||||
It can result in displaying sensitive information from exceptions to end users.
|
|
||||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
|
||||||
and restarting the app.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
@code{
|
|
||||||
[CascadingParameter]
|
|
||||||
private HttpContext? HttpContext { get; set; }
|
|
||||||
|
|
||||||
private string? RequestId { get; set; }
|
|
||||||
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
|
||||||
|
|
||||||
protected override void OnInitialized() =>
|
|
||||||
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
|
||||||
}
|
|
||||||
@@ -1,30 +1,13 @@
|
|||||||
using TafsirQuran.UI;
|
using Microsoft.AspNetCore.Components.Web;
|
||||||
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||||
using TafsirQuran.UI.Services;
|
using TafsirQuran.UI.Services;
|
||||||
using TafsirQuran.Web.Components;
|
using TafsirQuran.Web;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||||
|
builder.RootComponents.Add<App>("#app");
|
||||||
|
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||||
|
|
||||||
var apiBase = builder.Configuration["ApiBaseAddress"] ?? "http://localhost:5080/";
|
var apiBase = builder.Configuration["ApiBaseAddress"] ?? "http://localhost:5080/";
|
||||||
|
|
||||||
builder.Services.AddRazorComponents()
|
|
||||||
.AddInteractiveServerComponents();
|
|
||||||
|
|
||||||
builder.Services.AddTafsirUi(apiBase);
|
builder.Services.AddTafsirUi(apiBase);
|
||||||
|
|
||||||
var app = builder.Build();
|
await builder.Build().RunAsync();
|
||||||
|
|
||||||
if (!app.Environment.IsDevelopment())
|
|
||||||
{
|
|
||||||
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP NodePort / reverse-proxy; do not force HTTPS redirect in cluster.
|
|
||||||
app.UseAntiforgery();
|
|
||||||
app.UseStaticFiles();
|
|
||||||
app.MapStaticAssets();
|
|
||||||
|
|
||||||
app.MapRazorComponents<App>()
|
|
||||||
.AddInteractiveServerRenderMode()
|
|
||||||
.AddAdditionalAssemblies(typeof(TafsirQuran.UI.Routes).Assembly);
|
|
||||||
|
|
||||||
app.Run();
|
|
||||||
|
|||||||
@@ -1,23 +1,14 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"http": {
|
"http": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
|
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||||
"applicationUrl": "http://localhost:5299",
|
"applicationUrl": "http://localhost:5299",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"https": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"dotnetRunMessages": true,
|
|
||||||
"launchBrowser": true,
|
|
||||||
"applicationUrl": "https://localhost:7169;http://localhost:5299",
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<ServiceWorkerAssetsManifest>service-worker-assets.js</ServiceWorkerAssetsManifest>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.10" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.10" PrivateAssets="all" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\TafsirQuran.UI\TafsirQuran.UI.csproj" />
|
<ProjectReference Include="..\TafsirQuran.UI\TafsirQuran.UI.csproj" />
|
||||||
<ProjectReference Include="..\TafsirQuran.Shared\TafsirQuran.Shared.csproj" />
|
<ProjectReference Include="..\TafsirQuran.Shared\TafsirQuran.Shared.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
@using System.Net.Http
|
@using System.Net.Http
|
||||||
@using System.Net.Http.Json
|
@using System.Net.Http.Json
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Microsoft.AspNetCore.Components.Routing
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
@using Microsoft.AspNetCore.Components.Web
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
|
||||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||||
|
@using Microsoft.AspNetCore.Components.WebAssembly.Http
|
||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@using TafsirQuran.UI
|
@using TafsirQuran.UI
|
||||||
@using TafsirQuran.UI.Layout
|
@using TafsirQuran.UI.Layout
|
||||||
|
@using TafsirQuran.UI.Pages
|
||||||
|
@using TafsirQuran.UI.Components
|
||||||
@using TafsirQuran.Web
|
@using TafsirQuran.Web
|
||||||
@using TafsirQuran.Web.Components
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"ApiBaseAddress": "http://localhost:5080/",
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*"
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"ApiBaseAddress": "http://localhost:5080/"
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"ApiBaseAddress": "http://185.252.31.44:30821/"
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fa" dir="rtl">
|
<html lang="fa" dir="rtl">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
@@ -6,19 +6,22 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="theme-color" content="#1f6b4a" />
|
<meta name="theme-color" content="#1f6b4a" />
|
||||||
<meta name="description" content="تفسیر قرآن کریم — صوت و متن" />
|
<meta name="description" content="تفسیر قرآن کریم — صوت و متن" />
|
||||||
|
<title>تفسیر قرآن</title>
|
||||||
<base href="/" />
|
<base href="/" />
|
||||||
<link rel="manifest" href="manifest.webmanifest" />
|
<link rel="manifest" href="manifest.webmanifest" />
|
||||||
<link rel="apple-touch-icon" href="icons/icon-192.png" />
|
<link rel="apple-touch-icon" href="icons/icon-192.png" />
|
||||||
<link rel="stylesheet" href="_content/TafsirQuran.UI/css/app.css" />
|
<link rel="stylesheet" href="_content/TafsirQuran.UI/css/app.css" />
|
||||||
<link rel="stylesheet" href="@Assets["app.css"]" />
|
<link rel="stylesheet" href="app.css" />
|
||||||
<HeadOutlet @rendermode="InteractiveServer" />
|
<link href="TafsirQuran.Web.styles.css" rel="stylesheet" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<TafsirQuran.UI.Routes @rendermode="InteractiveServer" />
|
<div id="app">
|
||||||
<script src="_framework/blazor.web.js"></script>
|
<div class="muted" style="padding:2rem;text-align:center">در حال بارگذاری...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="_framework/blazor.webassembly.js"></script>
|
||||||
<script src="_content/TafsirQuran.UI/js/app.js"></script>
|
<script src="_content/TafsirQuran.UI/js/app.js"></script>
|
||||||
<script src="service-worker-register.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
if ('serviceWorker' in navigator) {
|
|
||||||
window.addEventListener('load', () => {
|
|
||||||
navigator.serviceWorker.register('/service-worker.js').catch(() => {});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,25 +1,2 @@
|
|||||||
const CACHE = 'tafsirquran-static-v1';
|
// Development placeholder — published SW is service-worker.published.js
|
||||||
const ASSETS = [
|
self.addEventListener('fetch', () => { });
|
||||||
'/',
|
|
||||||
'/manifest.webmanifest',
|
|
||||||
'/_content/TafsirQuran.UI/css/app.css',
|
|
||||||
'/_content/TafsirQuran.UI/js/app.js'
|
|
||||||
];
|
|
||||||
|
|
||||||
self.addEventListener('install', (event) => {
|
|
||||||
event.waitUntil(caches.open(CACHE).then((cache) => cache.addAll(ASSETS)).then(() => self.skipWaiting()));
|
|
||||||
});
|
|
||||||
|
|
||||||
self.addEventListener('activate', (event) => {
|
|
||||||
event.waitUntil(
|
|
||||||
caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))).then(() => self.clients.claim())
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.addEventListener('fetch', (event) => {
|
|
||||||
const req = event.request;
|
|
||||||
if (req.method !== 'GET') return;
|
|
||||||
event.respondWith(
|
|
||||||
caches.match(req).then((cached) => cached || fetch(req).catch(() => caches.match('/')))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
// Cache static WASM assets only — never HTML navigations.
|
||||||
|
const CACHE = 'tafsirquran-wasm-v1';
|
||||||
|
|
||||||
|
self.addEventListener('install', event => event.waitUntil(self.skipWaiting()));
|
||||||
|
self.addEventListener('activate', event => event.waitUntil(
|
||||||
|
caches.keys().then(keys => Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k))))
|
||||||
|
.then(() => self.clients.claim())
|
||||||
|
));
|
||||||
|
|
||||||
|
self.addEventListener('fetch', event => {
|
||||||
|
const req = event.request;
|
||||||
|
if (req.method !== 'GET') return;
|
||||||
|
const url = new URL(req.url);
|
||||||
|
if (req.mode === 'navigate' || (req.headers.get('accept') || '').includes('text/html')) {
|
||||||
|
event.respondWith(fetch(req));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (url.pathname.startsWith('/api/') || url.origin.includes(':30821')) {
|
||||||
|
event.respondWith(fetch(req));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.respondWith(
|
||||||
|
caches.open(CACHE).then(async cache => {
|
||||||
|
const cached = await cache.match(req);
|
||||||
|
if (cached) return cached;
|
||||||
|
const res = await fetch(req);
|
||||||
|
if (res.ok) cache.put(req, res.clone());
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user