Compare commits
5 Commits
master
...
refactor/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
24c756a9a8
|
|||
|
7ecb64742f
|
|||
|
3a7140f0a6
|
|||
|
42082fbefa
|
|||
|
bc3d030a1e
|
@@ -1,43 +0,0 @@
|
||||
# ActivityPub Testing Environment Variables
|
||||
|
||||
# Solar Network Configuration
|
||||
SOLAR_DOMAIN=solar.local
|
||||
SOLAR_PORT=5000
|
||||
SOLAR_URL=http://solar.local:5000
|
||||
|
||||
# Mastodon (Self-Hosted Test Instance)
|
||||
MASTODON_DOMAIN=mastodon.local
|
||||
MASTODON_PORT=3001
|
||||
MASTODON_STREAMING_PORT=4000
|
||||
MASTODON_URL=http://mastodon.local:3001
|
||||
|
||||
# Database
|
||||
DB_CONNECTION_STRING=Host=localhost;Port=5432;Database=dyson_network;Username=postgres;Password=postgres
|
||||
|
||||
# Test Accounts
|
||||
SOLAR_TEST_USERNAME=solaruser
|
||||
MASTODON_TEST_USERNAME=testuser
|
||||
MASTODON_TEST_PASSWORD=TestPassword123!
|
||||
|
||||
# ActivityPub Settings
|
||||
ACTIVITYPUB_DOMAIN=solar.local
|
||||
ACTIVITYPUB_ENABLE_FEDERATION=true
|
||||
ACTIVITYPUB_SIGNATURE_ALGORITHM=rsa-sha256
|
||||
|
||||
# HTTP Settings
|
||||
HTTP_TIMEOUT=30
|
||||
HTTP_MAX_RETRIES=3
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=Debug
|
||||
ACTIVITYPUB_LOG_LEVEL=Trace
|
||||
|
||||
# Testing
|
||||
TEST_SKIP_DATABASE_RESET=false
|
||||
TEST_SKIP_MASTODON_SETUP=false
|
||||
TEST_AUTO_ACCEPT_FOLLOWS=false
|
||||
|
||||
# Development (only in dev environment)
|
||||
DEV_DISABLE_SIGNATURE_VERIFICATION=false
|
||||
DEV_LOG_HTTP_BODIES=false
|
||||
DEV_DISABLE_CORS=false
|
||||
176
.github/workflows/docker-build.yml
vendored
176
.github/workflows/docker-build.yml
vendored
@@ -1,103 +1,103 @@
|
||||
name: Build and Push Microservices
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
determine-changes:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.changes.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
determine-changes:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.changes.outputs.matrix }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
run: |
|
||||
echo "files=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} | xargs)" >> $GITHUB_OUTPUT
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
run: |
|
||||
echo "files=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} | xargs)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Determine changed services
|
||||
id: changes
|
||||
run: |
|
||||
files="${{ steps.changed-files.outputs.files }}"
|
||||
matrix="{\"include\":[]}"
|
||||
services=("Sphere" "Pass" "Ring" "Drive" "Develop" "Gateway" "Insight" "Zone" "Messager")
|
||||
images=("sphere" "pass" "ring" "drive" "develop" "gateway" "insight" "zone" "messager")
|
||||
changed_services=()
|
||||
- name: Determine changed services
|
||||
id: changes
|
||||
run: |
|
||||
files="${{ steps.changed-files.outputs.files }}"
|
||||
matrix="{\"include\":[]}"
|
||||
services=("Sphere" "Pass" "Ring" "Drive" "Develop" "Gateway" "Insight" "Zone")
|
||||
images=("sphere" "pass" "ring" "drive" "develop" "gateway" "insight" "zone")
|
||||
changed_services=()
|
||||
|
||||
for file in $files; do
|
||||
if [[ "$file" == DysonNetwork.Shared/* ]]; then
|
||||
changed_services=("${services[@]}")
|
||||
break
|
||||
fi
|
||||
for i in "${!services[@]}"; do
|
||||
if [[ "$file" == DysonNetwork.${services[$i]}/* ]]; then
|
||||
# check if service is already in changed_services
|
||||
if [[ ! " ${changed_services[@]} " =~ " ${services[$i]} " ]]; then
|
||||
changed_services+=("${services[$i]}")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
for file in $files; do
|
||||
if [[ "$file" == DysonNetwork.Shared/* ]]; then
|
||||
changed_services=("${services[@]}")
|
||||
break
|
||||
fi
|
||||
for i in "${!services[@]}"; do
|
||||
if [[ "$file" == DysonNetwork.${services[$i]}/* ]]; then
|
||||
# check if service is already in changed_services
|
||||
if [[ ! " ${changed_services[@]} " =~ " ${services[$i]} " ]]; then
|
||||
changed_services+=("${services[$i]}")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
if [ ${#changed_services[@]} -gt 0 ]; then
|
||||
json_objects=""
|
||||
for service in "${changed_services[@]}"; do
|
||||
for i in "${!services[@]}"; do
|
||||
if [[ "${services[$i]}" == "$service" ]]; then
|
||||
image="${images[$i]}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
json_objects+="{\"service\":\"$service\",\"image\":\"$image\"},"
|
||||
done
|
||||
matrix="{\"include\":[${json_objects%,}]}"
|
||||
fi
|
||||
echo "matrix=$matrix" >> $GITHUB_OUTPUT
|
||||
if [ ${#changed_services[@]} -gt 0 ]; then
|
||||
json_objects=""
|
||||
for service in "${changed_services[@]}"; do
|
||||
for i in "${!services[@]}"; do
|
||||
if [[ "${services[$i]}" == "$service" ]]; then
|
||||
image="${images[$i]}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
json_objects+="{\"service\":\"$service\",\"image\":\"$image\"},"
|
||||
done
|
||||
matrix="{\"include\":[${json_objects%,}]}"
|
||||
fi
|
||||
echo "matrix=$matrix" >> $GITHUB_OUTPUT
|
||||
|
||||
build-and-push:
|
||||
needs: determine-changes
|
||||
if: ${{ needs.determine-changes.outputs.matrix != '{"include":[]}' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
matrix: ${{ fromJson(needs.determine-changes.outputs.matrix) }}
|
||||
build-and-push:
|
||||
needs: determine-changes
|
||||
if: ${{ needs.determine-changes.outputs.matrix != '{"include":[]}' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
matrix: ${{ fromJson(needs.determine-changes.outputs.matrix) }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup NBGV
|
||||
uses: dotnet/nbgv@master
|
||||
id: nbgv
|
||||
- name: Setup NBGV
|
||||
uses: dotnet/nbgv@master
|
||||
id: nbgv
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image for ${{ matrix.service }}
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: DysonNetwork.${{ matrix.service }}/Dockerfile
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-${{ matrix.image }}:${{ steps.nbgv.outputs.SimpleVersion }}
|
||||
ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-${{ matrix.image }}:latest
|
||||
platforms: linux/amd64
|
||||
- name: Build and push Docker image for ${{ matrix.service }}
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: DysonNetwork.${{ matrix.service }}/Dockerfile
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-${{ matrix.image }}:${{ steps.nbgv.outputs.SimpleVersion }}
|
||||
ghcr.io/${{ vars.PACKAGE_OWNER }}/dyson-${{ matrix.image }}:latest
|
||||
platforms: linux/amd64
|
||||
|
||||
613
API_WALLET_FUNDS.md
Normal file
613
API_WALLET_FUNDS.md
Normal file
@@ -0,0 +1,613 @@
|
||||
# Wallet Funds API Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The Wallet Funds API provides red packet functionality for the DysonNetwork platform, allowing users to create and distribute funds among multiple recipients with expiration and claiming mechanisms.
|
||||
|
||||
## Authentication
|
||||
|
||||
All endpoints require Bearer token authentication:
|
||||
|
||||
```
|
||||
Authorization: Bearer {jwt_token}
|
||||
```
|
||||
|
||||
## Data Types
|
||||
|
||||
### Enums
|
||||
|
||||
#### FundSplitType
|
||||
```typescript
|
||||
enum FundSplitType {
|
||||
Even = 0, // Equal distribution
|
||||
Random = 1 // Lucky draw distribution
|
||||
}
|
||||
```
|
||||
|
||||
#### FundStatus
|
||||
```typescript
|
||||
enum FundStatus {
|
||||
Created = 0, // Fund created, waiting for claims
|
||||
PartiallyReceived = 1, // Some recipients claimed
|
||||
FullyReceived = 2, // All recipients claimed
|
||||
Expired = 3, // Fund expired, unclaimed amounts refunded
|
||||
Refunded = 4 // Legacy status
|
||||
}
|
||||
```
|
||||
|
||||
### Request/Response Models
|
||||
|
||||
#### CreateFundRequest
|
||||
```typescript
|
||||
interface CreateFundRequest {
|
||||
recipientAccountIds: string[]; // UUIDs of recipients
|
||||
currency: string; // e.g., "points", "golds"
|
||||
totalAmount: number; // Total amount to distribute
|
||||
splitType: FundSplitType; // Even or Random
|
||||
message?: string; // Optional message
|
||||
expirationHours?: number; // Optional: hours until expiration (default: 24)
|
||||
pinCode: string; // Required: 6-digit PIN code for security
|
||||
}
|
||||
```
|
||||
|
||||
#### SnWalletFund
|
||||
```typescript
|
||||
interface SnWalletFund {
|
||||
id: string; // UUID
|
||||
currency: string;
|
||||
totalAmount: number;
|
||||
splitType: FundSplitType;
|
||||
status: FundStatus;
|
||||
message?: string;
|
||||
creatorAccountId: string; // UUID
|
||||
creatorAccount: SnAccount; // Creator account details (includes profile)
|
||||
recipients: SnWalletFundRecipient[];
|
||||
expiredAt: string; // ISO 8601 timestamp
|
||||
createdAt: string; // ISO 8601 timestamp
|
||||
updatedAt: string; // ISO 8601 timestamp
|
||||
}
|
||||
```
|
||||
|
||||
#### SnWalletFundRecipient
|
||||
```typescript
|
||||
interface SnWalletFundRecipient {
|
||||
id: string; // UUID
|
||||
fundId: string; // UUID
|
||||
recipientAccountId: string; // UUID
|
||||
recipientAccount: SnAccount; // Recipient account details (includes profile)
|
||||
amount: number; // Allocated amount
|
||||
isReceived: boolean;
|
||||
receivedAt?: string; // ISO 8601 timestamp (if claimed)
|
||||
createdAt: string; // ISO 8601 timestamp
|
||||
updatedAt: string; // ISO 8601 timestamp
|
||||
}
|
||||
```
|
||||
|
||||
#### SnWalletTransaction
|
||||
```typescript
|
||||
interface SnWalletTransaction {
|
||||
id: string; // UUID
|
||||
payerWalletId?: string; // UUID (null for system transfers)
|
||||
payeeWalletId?: string; // UUID (null for system transfers)
|
||||
currency: string;
|
||||
amount: number;
|
||||
remarks?: string;
|
||||
type: TransactionType;
|
||||
createdAt: string; // ISO 8601 timestamp
|
||||
updatedAt: string; // ISO 8601 timestamp
|
||||
}
|
||||
```
|
||||
|
||||
#### Error Response
|
||||
```typescript
|
||||
interface ErrorResponse {
|
||||
type: string; // Error type
|
||||
title: string; // Error title
|
||||
status: number; // HTTP status code
|
||||
detail: string; // Error details
|
||||
instance?: string; // Request instance
|
||||
}
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### 1. Create Fund
|
||||
|
||||
Creates a new fund (red packet) for distribution among recipients.
|
||||
|
||||
**Endpoint:** `POST /api/wallets/funds`
|
||||
|
||||
**Request Body:** `CreateFundRequest`
|
||||
|
||||
**Response:** `SnWalletFund` (201 Created)
|
||||
|
||||
**Example Request:**
|
||||
```bash
|
||||
curl -X POST "/api/wallets/funds" \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"recipientAccountIds": [
|
||||
"550e8400-e29b-41d4-a716-446655440000",
|
||||
"550e8400-e29b-41d4-a716-446655440001",
|
||||
"550e8400-e29b-41d4-a716-446655440002"
|
||||
],
|
||||
"currency": "points",
|
||||
"totalAmount": 100.00,
|
||||
"splitType": "Even",
|
||||
"message": "Happy New Year! 🎉",
|
||||
"expirationHours": 48,
|
||||
"pinCode": "123456"
|
||||
}'
|
||||
```
|
||||
|
||||
**Example Response:**
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440003",
|
||||
"currency": "points",
|
||||
"totalAmount": 100.00,
|
||||
"splitType": 0,
|
||||
"status": 0,
|
||||
"message": "Happy New Year! 🎉",
|
||||
"creatorAccountId": "550e8400-e29b-41d4-a716-446655440004",
|
||||
"creatorAccount": {
|
||||
"id": "550e8400-e29b-41d4-a716-446655440004",
|
||||
"username": "creator_user"
|
||||
},
|
||||
"recipients": [
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440005",
|
||||
"fundId": "550e8400-e29b-41d4-a716-446655440003",
|
||||
"recipientAccountId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"amount": 33.34,
|
||||
"isReceived": false,
|
||||
"createdAt": "2025-10-03T22:00:00Z",
|
||||
"updatedAt": "2025-10-03T22:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440006",
|
||||
"fundId": "550e8400-e29b-41d4-a716-446655440003",
|
||||
"recipientAccountId": "550e8400-e29b-41d4-a716-446655440001",
|
||||
"amount": 33.33,
|
||||
"isReceived": false,
|
||||
"createdAt": "2025-10-03T22:00:00Z",
|
||||
"updatedAt": "2025-10-03T22:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440007",
|
||||
"fundId": "550e8400-e29b-41d4-a716-446655440003",
|
||||
"recipientAccountId": "550e8400-e29b-41d4-a716-446655440002",
|
||||
"amount": 33.33,
|
||||
"isReceived": false,
|
||||
"createdAt": "2025-10-03T22:00:00Z",
|
||||
"updatedAt": "2025-10-03T22:00:00Z"
|
||||
}
|
||||
],
|
||||
"expiredAt": "2025-10-05T22:00:00Z",
|
||||
"createdAt": "2025-10-03T22:00:00Z",
|
||||
"updatedAt": "2025-10-03T22:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses:**
|
||||
- `400 Bad Request`: Invalid parameters, insufficient funds, invalid recipients
|
||||
- `401 Unauthorized`: Missing or invalid authentication
|
||||
- `403 Forbidden`: Invalid PIN code
|
||||
- `422 Unprocessable Entity`: Business logic violations
|
||||
|
||||
---
|
||||
|
||||
### 2. Get Funds
|
||||
|
||||
Retrieves funds that the authenticated user is involved in (as creator or recipient).
|
||||
|
||||
**Endpoint:** `GET /api/wallets/funds`
|
||||
|
||||
**Query Parameters:**
|
||||
- `offset` (number, optional): Pagination offset (default: 0)
|
||||
- `take` (number, optional): Number of items to return (default: 20, max: 100)
|
||||
- `status` (FundStatus, optional): Filter by fund status
|
||||
|
||||
**Response:** `SnWalletFund[]` (200 OK)
|
||||
|
||||
**Headers:**
|
||||
- `X-Total`: Total number of funds matching the criteria
|
||||
|
||||
**Example Request:**
|
||||
```bash
|
||||
curl -X GET "/api/wallets/funds?offset=0&take=10&status=0" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
**Example Response:**
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440003",
|
||||
"currency": "points",
|
||||
"totalAmount": 100.00,
|
||||
"splitType": 0,
|
||||
"status": 0,
|
||||
"message": "Happy New Year! 🎉",
|
||||
"creatorAccountId": "550e8400-e29b-41d4-a716-446655440004",
|
||||
"creatorAccount": {
|
||||
"id": "550e8400-e29b-41d4-a716-446655440004",
|
||||
"username": "creator_user"
|
||||
},
|
||||
"recipients": [
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440005",
|
||||
"fundId": "550e8400-e29b-41d4-a716-446655440003",
|
||||
"recipientAccountId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"amount": 33.34,
|
||||
"isReceived": false
|
||||
}
|
||||
],
|
||||
"expiredAt": "2025-10-05T22:00:00Z",
|
||||
"createdAt": "2025-10-03T22:00:00Z",
|
||||
"updatedAt": "2025-10-03T22:00:00Z"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
**Error Responses:**
|
||||
- `401 Unauthorized`: Missing or invalid authentication
|
||||
|
||||
---
|
||||
|
||||
### 3. Get Fund
|
||||
|
||||
Retrieves details of a specific fund.
|
||||
|
||||
**Endpoint:** `GET /api/wallets/funds/{id}`
|
||||
|
||||
**Path Parameters:**
|
||||
- `id` (string): Fund UUID
|
||||
|
||||
**Response:** `SnWalletFund` (200 OK)
|
||||
|
||||
**Example Request:**
|
||||
```bash
|
||||
curl -X GET "/api/wallets/funds/550e8400-e29b-41d4-a716-446655440003" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
**Example Response:** (Same as create fund response)
|
||||
|
||||
**Error Responses:**
|
||||
- `401 Unauthorized`: Missing or invalid authentication
|
||||
- `403 Forbidden`: User doesn't have permission to view this fund
|
||||
- `404 Not Found`: Fund not found
|
||||
|
||||
---
|
||||
|
||||
### 4. Receive Fund
|
||||
|
||||
Claims the authenticated user's portion of a fund.
|
||||
|
||||
**Endpoint:** `POST /api/wallets/funds/{id}/receive`
|
||||
|
||||
**Path Parameters:**
|
||||
- `id` (string): Fund UUID
|
||||
|
||||
**Response:** `SnWalletTransaction` (200 OK)
|
||||
|
||||
**Example Request:**
|
||||
```bash
|
||||
curl -X POST "/api/wallets/funds/550e8400-e29b-41d4-a716-446655440003/receive" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
**Example Response:**
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440008",
|
||||
"payerWalletId": null,
|
||||
"payeeWalletId": "550e8400-e29b-41d4-a716-446655440009",
|
||||
"currency": "points",
|
||||
"amount": 33.34,
|
||||
"remarks": "Received fund portion from 550e8400-e29b-41d4-a716-446655440004",
|
||||
"type": 1,
|
||||
"createdAt": "2025-10-03T22:05:00Z",
|
||||
"updatedAt": "2025-10-03T22:05:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses:**
|
||||
- `400 Bad Request`: Fund expired, already claimed, not a recipient
|
||||
- `401 Unauthorized`: Missing or invalid authentication
|
||||
- `404 Not Found`: Fund not found
|
||||
|
||||
---
|
||||
|
||||
### 5. Get Wallet Overview
|
||||
|
||||
Retrieves a summarized overview of wallet transactions grouped by type for graphing/charting purposes.
|
||||
|
||||
**Endpoint:** `GET /api/wallets/overview`
|
||||
|
||||
**Query Parameters:**
|
||||
- `startDate` (string, optional): Start date in ISO 8601 format (e.g., "2025-01-01T00:00:00Z")
|
||||
- `endDate` (string, optional): End date in ISO 8601 format (e.g., "2025-12-31T23:59:59Z")
|
||||
|
||||
**Response:** `WalletOverview` (200 OK)
|
||||
|
||||
**Example Request:**
|
||||
```bash
|
||||
curl -X GET "/api/wallets/overview?startDate=2025-01-01T00:00:00Z&endDate=2025-12-31T23:59:59Z" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
**Example Response:**
|
||||
```json
|
||||
{
|
||||
"accountId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"startDate": "2025-01-01T00:00:00.0000000Z",
|
||||
"endDate": "2025-12-31T23:59:59.0000000Z",
|
||||
"summary": {
|
||||
"System": {
|
||||
"type": "System",
|
||||
"currencies": {
|
||||
"points": {
|
||||
"currency": "points",
|
||||
"income": 150.00,
|
||||
"spending": 0.00,
|
||||
"net": 150.00
|
||||
}
|
||||
}
|
||||
},
|
||||
"Transfer": {
|
||||
"type": "Transfer",
|
||||
"currencies": {
|
||||
"points": {
|
||||
"currency": "points",
|
||||
"income": 25.00,
|
||||
"spending": 75.00,
|
||||
"net": -50.00
|
||||
},
|
||||
"golds": {
|
||||
"currency": "golds",
|
||||
"income": 0.00,
|
||||
"spending": 10.00,
|
||||
"net": -10.00
|
||||
}
|
||||
}
|
||||
},
|
||||
"Order": {
|
||||
"type": "Order",
|
||||
"currencies": {
|
||||
"points": {
|
||||
"currency": "points",
|
||||
"income": 0.00,
|
||||
"spending": 200.00,
|
||||
"net": -200.00
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"totalIncome": 175.00,
|
||||
"totalSpending": 285.00,
|
||||
"netTotal": -110.00
|
||||
}
|
||||
```
|
||||
|
||||
**Response Fields:**
|
||||
- `accountId`: User's account UUID
|
||||
- `startDate`/`endDate`: Date range applied (ISO 8601 format)
|
||||
- `summary`: Object keyed by transaction type
|
||||
- `type`: Transaction type name
|
||||
- `currencies`: Object keyed by currency code
|
||||
- `currency`: Currency name
|
||||
- `income`: Total money received
|
||||
- `spending`: Total money spent
|
||||
- `net`: Income minus spending
|
||||
- `totalIncome`: Sum of all income across all types/currencies
|
||||
- `totalSpending`: Sum of all spending across all types/currencies
|
||||
- `netTotal`: Overall net (totalIncome - totalSpending)
|
||||
|
||||
**Error Responses:**
|
||||
- `401 Unauthorized`: Missing or invalid authentication
|
||||
|
||||
## Error Codes
|
||||
|
||||
### Common Error Types
|
||||
|
||||
#### Validation Errors
|
||||
```json
|
||||
{
|
||||
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
|
||||
"title": "Bad Request",
|
||||
"status": 400,
|
||||
"detail": "At least one recipient is required",
|
||||
"instance": "/api/wallets/funds"
|
||||
}
|
||||
```
|
||||
|
||||
#### Insufficient Funds
|
||||
```json
|
||||
{
|
||||
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
|
||||
"title": "Bad Request",
|
||||
"status": 400,
|
||||
"detail": "Insufficient funds",
|
||||
"instance": "/api/wallets/funds"
|
||||
}
|
||||
```
|
||||
|
||||
#### Fund Not Available
|
||||
```json
|
||||
{
|
||||
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
|
||||
"title": "Bad Request",
|
||||
"status": 400,
|
||||
"detail": "Fund is no longer available",
|
||||
"instance": "/api/wallets/funds/550e8400-e29b-41d4-a716-446655440003/receive"
|
||||
}
|
||||
```
|
||||
|
||||
#### Already Claimed
|
||||
```json
|
||||
{
|
||||
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
|
||||
"title": "Bad Request",
|
||||
"status": 400,
|
||||
"detail": "You have already received this fund",
|
||||
"instance": "/api/wallets/funds/550e8400-e29b-41d4-a716-446655440003/receive"
|
||||
}
|
||||
```
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
- **Create Fund**: 10 requests per minute per user
|
||||
- **Get Funds**: 60 requests per minute per user
|
||||
- **Get Fund**: 60 requests per minute per user
|
||||
- **Receive Fund**: 30 requests per minute per user
|
||||
|
||||
## Webhooks/Notifications
|
||||
|
||||
The system integrates with the platform's notification system:
|
||||
|
||||
- **Fund Created**: Creator receives confirmation
|
||||
- **Fund Claimed**: Creator receives notification when someone claims
|
||||
- **Fund Expired**: Creator receives refund notification
|
||||
|
||||
## SDK Examples
|
||||
|
||||
### JavaScript/TypeScript
|
||||
|
||||
```typescript
|
||||
// Create a fund
|
||||
const createFund = async (fundData: CreateFundRequest): Promise<SnWalletFund> => {
|
||||
const response = await fetch('/api/wallets/funds', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(fundData)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
};
|
||||
|
||||
// Get user's funds
|
||||
const getFunds = async (params?: {
|
||||
offset?: number;
|
||||
take?: number;
|
||||
status?: FundStatus;
|
||||
}): Promise<SnWalletFund[]> => {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (params?.offset) queryParams.set('offset', params.offset.toString());
|
||||
if (params?.take) queryParams.set('take', params.take.toString());
|
||||
if (params?.status !== undefined) queryParams.set('status', params.status.toString());
|
||||
|
||||
const response = await fetch(`/api/wallets/funds?${queryParams}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
};
|
||||
|
||||
// Claim a fund
|
||||
const receiveFund = async (fundId: string): Promise<SnWalletTransaction> => {
|
||||
const response = await fetch(`/api/wallets/funds/${fundId}/receive`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
};
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import requests
|
||||
from typing import List, Optional
|
||||
from enum import Enum
|
||||
|
||||
class FundSplitType(Enum):
|
||||
EVEN = 0
|
||||
RANDOM = 1
|
||||
|
||||
class FundStatus(Enum):
|
||||
CREATED = 0
|
||||
PARTIALLY_RECEIVED = 1
|
||||
FULLY_RECEIVED = 2
|
||||
EXPIRED = 3
|
||||
REFUNDED = 4
|
||||
|
||||
def create_fund(token: str, fund_data: dict) -> dict:
|
||||
"""Create a new fund"""
|
||||
response = requests.post(
|
||||
'/api/wallets/funds',
|
||||
json=fund_data,
|
||||
headers={
|
||||
'Authorization': f'Bearer {token}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def get_funds(
|
||||
token: str,
|
||||
offset: int = 0,
|
||||
take: int = 20,
|
||||
status: Optional[FundStatus] = None
|
||||
) -> List[dict]:
|
||||
"""Get user's funds"""
|
||||
params = {'offset': offset, 'take': take}
|
||||
if status is not None:
|
||||
params['status'] = status.value
|
||||
|
||||
response = requests.get(
|
||||
'/api/wallets/funds',
|
||||
params=params,
|
||||
headers={'Authorization': f'Bearer {token}'}
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def receive_fund(token: str, fund_id: str) -> dict:
|
||||
"""Claim a fund portion"""
|
||||
response = requests.post(
|
||||
f'/api/wallets/funds/{fund_id}/receive',
|
||||
headers={'Authorization': f'Bearer {token}'}
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
```
|
||||
|
||||
## Changelog
|
||||
|
||||
### Version 1.0.0
|
||||
- Initial release with basic red packet functionality
|
||||
- Support for even and random split types
|
||||
- 24-hour expiration with automatic refunds
|
||||
- RESTful API endpoints
|
||||
- Comprehensive error handling
|
||||
|
||||
## Support
|
||||
|
||||
For API support or questions:
|
||||
- Check the main documentation at `README_WALLET_FUNDS.md`
|
||||
- Review error messages for specific guidance
|
||||
- Contact the development team for technical issues
|
||||
@@ -1,91 +0,0 @@
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
var builder = DistributedApplication.CreateBuilder(args);
|
||||
|
||||
var isDev = builder.Environment.IsDevelopment();
|
||||
|
||||
var cache = builder.AddRedis("Cache");
|
||||
var queue = builder.AddNats("Queue").WithJetStream();
|
||||
|
||||
var ringService = builder.AddProject<Projects.DysonNetwork_Ring>("ring");
|
||||
var passService = builder.AddProject<Projects.DysonNetwork_Pass>("pass")
|
||||
.WithReference(ringService);
|
||||
var driveService = builder.AddProject<Projects.DysonNetwork_Drive>("drive")
|
||||
.WithReference(passService)
|
||||
.WithReference(ringService);
|
||||
var sphereService = builder.AddProject<Projects.DysonNetwork_Sphere>("sphere")
|
||||
.WithReference(passService)
|
||||
.WithReference(ringService)
|
||||
.WithReference(driveService);
|
||||
var developService = builder.AddProject<Projects.DysonNetwork_Develop>("develop")
|
||||
.WithReference(passService)
|
||||
.WithReference(ringService)
|
||||
.WithReference(sphereService);
|
||||
var insightService = builder.AddProject<Projects.DysonNetwork_Insight>("insight")
|
||||
.WithReference(passService)
|
||||
.WithReference(ringService)
|
||||
.WithReference(sphereService)
|
||||
.WithReference(developService);
|
||||
var zoneService = builder.AddProject<Projects.DysonNetwork_Zone>("zone")
|
||||
.WithReference(passService)
|
||||
.WithReference(ringService)
|
||||
.WithReference(sphereService)
|
||||
.WithReference(developService)
|
||||
.WithReference(insightService);
|
||||
var messagerService = builder.AddProject<Projects.DysonNetwork_Messager>("messager")
|
||||
.WithReference(passService)
|
||||
.WithReference(ringService)
|
||||
.WithReference(sphereService)
|
||||
.WithReference(developService);
|
||||
|
||||
passService.WithReference(developService).WithReference(driveService);
|
||||
|
||||
List<IResourceBuilder<ProjectResource>> services =
|
||||
[
|
||||
ringService,
|
||||
passService,
|
||||
driveService,
|
||||
sphereService,
|
||||
developService,
|
||||
insightService,
|
||||
zoneService,
|
||||
messagerService
|
||||
];
|
||||
|
||||
for (var idx = 0; idx < services.Count; idx++)
|
||||
{
|
||||
var service = services[idx];
|
||||
|
||||
service.WithReference(cache).WithReference(queue);
|
||||
|
||||
var grpcPort = 7002 + idx;
|
||||
|
||||
if (isDev)
|
||||
{
|
||||
service.WithEnvironment("GRPC_PORT", grpcPort.ToString());
|
||||
|
||||
var httpPort = 8001 + idx;
|
||||
service.WithEnvironment("HTTP_PORTS", httpPort.ToString());
|
||||
service.WithHttpEndpoint(httpPort, targetPort: null, isProxied: false, name: "http");
|
||||
}
|
||||
else
|
||||
{
|
||||
service.WithHttpEndpoint(8080, targetPort: null, isProxied: false, name: "http");
|
||||
}
|
||||
|
||||
service.WithEndpoint(isDev ? grpcPort : 7001, isDev ? null : 7001, "https", name: "grpc", isProxied: false);
|
||||
}
|
||||
|
||||
// Extra double-ended references
|
||||
ringService.WithReference(passService);
|
||||
|
||||
var gateway = builder.AddProject<Projects.DysonNetwork_Gateway>("gateway")
|
||||
.WithEnvironment("HTTP_PORTS", "5001")
|
||||
.WithHttpEndpoint(port: 5001, targetPort: null, isProxied: false, name: "http");
|
||||
|
||||
foreach (var service in services)
|
||||
gateway.WithReference(service);
|
||||
|
||||
builder.AddDockerComposeEnvironment("docker-compose");
|
||||
|
||||
builder.Build().Run();
|
||||
@@ -1,30 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Sdk Name="Aspire.AppHost.Sdk" Version="13.1.0"/>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<UserSecretsId>a68b3195-a00d-40c2-b5ed-d675356b7cde</UserSecretsId>
|
||||
<RootNamespace>DysonNetwork.Control</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aspire.Hosting.AppHost" Version="13.1.0" />
|
||||
<PackageReference Include="Aspire.Hosting.Docker" Version="13.0.0-preview.1.25560.3"/>
|
||||
<PackageReference Include="Aspire.Hosting.Nats" Version="13.1.0"/>
|
||||
<PackageReference Include="Aspire.Hosting.Redis" Version="13.1.0"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DysonNetwork.Develop\DysonNetwork.Develop.csproj"/>
|
||||
<ProjectReference Include="..\DysonNetwork.Drive\DysonNetwork.Drive.csproj"/>
|
||||
<ProjectReference Include="..\DysonNetwork.Pass\DysonNetwork.Pass.csproj"/>
|
||||
<ProjectReference Include="..\DysonNetwork.Ring\DysonNetwork.Ring.csproj"/>
|
||||
<ProjectReference Include="..\DysonNetwork.Sphere\DysonNetwork.Sphere.csproj"/>
|
||||
<ProjectReference Include="..\DysonNetwork.Gateway\DysonNetwork.Gateway.csproj"/>
|
||||
<ProjectReference Include="..\DysonNetwork.Insight\DysonNetwork.Insight.csproj"/>
|
||||
<ProjectReference Include="..\DysonNetwork.Zone\DysonNetwork.Zone.csproj"/>
|
||||
<ProjectReference Include="..\DysonNetwork.Messager\DysonNetwork.Messager.csproj"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:17169;http://localhost:15057",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"DOTNET_ENVIRONMENT": "Development",
|
||||
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21175",
|
||||
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22189",
|
||||
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21260",
|
||||
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22052"
|
||||
}
|
||||
},
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:15057",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"DOTNET_ENVIRONMENT": "Development",
|
||||
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19163",
|
||||
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20185",
|
||||
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:22108"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"cache": "localhost:6379"
|
||||
}
|
||||
}
|
||||
@@ -1,357 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/aspire-8.0.json",
|
||||
"resources": {
|
||||
"cache": {
|
||||
"type": "container.v1",
|
||||
"connectionString": "{cache.bindings.tcp.host}:{cache.bindings.tcp.port},password={cache-password.value}",
|
||||
"image": "docker.io/library/redis:8.2",
|
||||
"entrypoint": "/bin/sh",
|
||||
"args": [
|
||||
"-c",
|
||||
"redis-server --requirepass $REDIS_PASSWORD"
|
||||
],
|
||||
"env": {
|
||||
"REDIS_PASSWORD": "{cache-password.value}"
|
||||
},
|
||||
"bindings": {
|
||||
"tcp": {
|
||||
"scheme": "tcp",
|
||||
"protocol": "tcp",
|
||||
"transport": "tcp",
|
||||
"targetPort": 6379
|
||||
}
|
||||
}
|
||||
},
|
||||
"queue": {
|
||||
"type": "container.v1",
|
||||
"connectionString": "nats://nats:{queue-password.value}@{queue.bindings.tcp.host}:{queue.bindings.tcp.port}",
|
||||
"image": "docker.io/library/nats:2.11",
|
||||
"args": [
|
||||
"--user",
|
||||
"nats",
|
||||
"--pass",
|
||||
"{queue-password.value}",
|
||||
"-js"
|
||||
],
|
||||
"bindings": {
|
||||
"tcp": {
|
||||
"scheme": "tcp",
|
||||
"protocol": "tcp",
|
||||
"transport": "tcp",
|
||||
"targetPort": 4222
|
||||
}
|
||||
}
|
||||
},
|
||||
"ring": {
|
||||
"type": "project.v1",
|
||||
"path": "../DysonNetwork.Ring/DysonNetwork.Ring.csproj",
|
||||
"env": {
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true",
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true",
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY": "in_memory",
|
||||
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
|
||||
"HTTP_PORTS": "8001",
|
||||
"HTTPS_PORTS": "{ring.bindings.grpc.targetPort}",
|
||||
"ConnectionStrings__cache": "{cache.connectionString}",
|
||||
"ConnectionStrings__queue": "{queue.connectionString}",
|
||||
"GRPC_PORT": "7002",
|
||||
"services__pass__http__0": "{pass.bindings.http.url}",
|
||||
"services__pass__grpc__0": "{pass.bindings.grpc.url}",
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "{docker-compose-dashboard.bindings.otlp-grpc.url}",
|
||||
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
|
||||
"OTEL_SERVICE_NAME": "ring"
|
||||
},
|
||||
"bindings": {
|
||||
"http": {
|
||||
"scheme": "http",
|
||||
"protocol": "tcp",
|
||||
"transport": "http",
|
||||
"targetPort": 8001
|
||||
},
|
||||
"grpc": {
|
||||
"scheme": "https",
|
||||
"protocol": "tcp",
|
||||
"transport": "http",
|
||||
"targetPort": 7002
|
||||
}
|
||||
}
|
||||
},
|
||||
"pass": {
|
||||
"type": "project.v1",
|
||||
"path": "../DysonNetwork.Pass/DysonNetwork.Pass.csproj",
|
||||
"env": {
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true",
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true",
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY": "in_memory",
|
||||
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
|
||||
"HTTP_PORTS": "8002",
|
||||
"HTTPS_PORTS": "{pass.bindings.grpc.targetPort}",
|
||||
"services__ring__http__0": "{ring.bindings.http.url}",
|
||||
"services__ring__grpc__0": "{ring.bindings.grpc.url}",
|
||||
"services__develop__http__0": "{develop.bindings.http.url}",
|
||||
"services__develop__grpc__0": "{develop.bindings.grpc.url}",
|
||||
"services__drive__http__0": "{drive.bindings.http.url}",
|
||||
"services__drive__grpc__0": "{drive.bindings.grpc.url}",
|
||||
"ConnectionStrings__cache": "{cache.connectionString}",
|
||||
"ConnectionStrings__queue": "{queue.connectionString}",
|
||||
"GRPC_PORT": "7003",
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "{docker-compose-dashboard.bindings.otlp-grpc.url}",
|
||||
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
|
||||
"OTEL_SERVICE_NAME": "pass"
|
||||
},
|
||||
"bindings": {
|
||||
"http": {
|
||||
"scheme": "http",
|
||||
"protocol": "tcp",
|
||||
"transport": "http",
|
||||
"targetPort": 8002
|
||||
},
|
||||
"grpc": {
|
||||
"scheme": "https",
|
||||
"protocol": "tcp",
|
||||
"transport": "http",
|
||||
"targetPort": 7003
|
||||
}
|
||||
}
|
||||
},
|
||||
"drive": {
|
||||
"type": "project.v1",
|
||||
"path": "../DysonNetwork.Drive/DysonNetwork.Drive.csproj",
|
||||
"env": {
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true",
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true",
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY": "in_memory",
|
||||
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
|
||||
"HTTP_PORTS": "8003",
|
||||
"HTTPS_PORTS": "{drive.bindings.grpc.targetPort}",
|
||||
"services__pass__http__0": "{pass.bindings.http.url}",
|
||||
"services__pass__grpc__0": "{pass.bindings.grpc.url}",
|
||||
"services__ring__http__0": "{ring.bindings.http.url}",
|
||||
"services__ring__grpc__0": "{ring.bindings.grpc.url}",
|
||||
"ConnectionStrings__cache": "{cache.connectionString}",
|
||||
"ConnectionStrings__queue": "{queue.connectionString}",
|
||||
"GRPC_PORT": "7004",
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "{docker-compose-dashboard.bindings.otlp-grpc.url}",
|
||||
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
|
||||
"OTEL_SERVICE_NAME": "drive"
|
||||
},
|
||||
"bindings": {
|
||||
"http": {
|
||||
"scheme": "http",
|
||||
"protocol": "tcp",
|
||||
"transport": "http",
|
||||
"targetPort": 8003
|
||||
},
|
||||
"grpc": {
|
||||
"scheme": "https",
|
||||
"protocol": "tcp",
|
||||
"transport": "http",
|
||||
"targetPort": 7004
|
||||
}
|
||||
}
|
||||
},
|
||||
"sphere": {
|
||||
"type": "project.v1",
|
||||
"path": "../DysonNetwork.Sphere/DysonNetwork.Sphere.csproj",
|
||||
"env": {
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true",
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true",
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY": "in_memory",
|
||||
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
|
||||
"HTTP_PORTS": "8004",
|
||||
"HTTPS_PORTS": "{sphere.bindings.grpc.targetPort}",
|
||||
"services__pass__http__0": "{pass.bindings.http.url}",
|
||||
"services__pass__grpc__0": "{pass.bindings.grpc.url}",
|
||||
"services__ring__http__0": "{ring.bindings.http.url}",
|
||||
"services__ring__grpc__0": "{ring.bindings.grpc.url}",
|
||||
"services__drive__http__0": "{drive.bindings.http.url}",
|
||||
"services__drive__grpc__0": "{drive.bindings.grpc.url}",
|
||||
"ConnectionStrings__cache": "{cache.connectionString}",
|
||||
"ConnectionStrings__queue": "{queue.connectionString}",
|
||||
"GRPC_PORT": "7005",
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "{docker-compose-dashboard.bindings.otlp-grpc.url}",
|
||||
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
|
||||
"OTEL_SERVICE_NAME": "sphere"
|
||||
},
|
||||
"bindings": {
|
||||
"http": {
|
||||
"scheme": "http",
|
||||
"protocol": "tcp",
|
||||
"transport": "http",
|
||||
"targetPort": 8004
|
||||
},
|
||||
"grpc": {
|
||||
"scheme": "https",
|
||||
"protocol": "tcp",
|
||||
"transport": "http",
|
||||
"targetPort": 7005
|
||||
}
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"type": "project.v1",
|
||||
"path": "../DysonNetwork.Develop/DysonNetwork.Develop.csproj",
|
||||
"env": {
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true",
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true",
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY": "in_memory",
|
||||
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
|
||||
"HTTP_PORTS": "8005",
|
||||
"HTTPS_PORTS": "{develop.bindings.grpc.targetPort}",
|
||||
"services__pass__http__0": "{pass.bindings.http.url}",
|
||||
"services__pass__grpc__0": "{pass.bindings.grpc.url}",
|
||||
"services__ring__http__0": "{ring.bindings.http.url}",
|
||||
"services__ring__grpc__0": "{ring.bindings.grpc.url}",
|
||||
"services__sphere__http__0": "{sphere.bindings.http.url}",
|
||||
"services__sphere__grpc__0": "{sphere.bindings.grpc.url}",
|
||||
"ConnectionStrings__cache": "{cache.connectionString}",
|
||||
"ConnectionStrings__queue": "{queue.connectionString}",
|
||||
"GRPC_PORT": "7006",
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "{docker-compose-dashboard.bindings.otlp-grpc.url}",
|
||||
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
|
||||
"OTEL_SERVICE_NAME": "develop"
|
||||
},
|
||||
"bindings": {
|
||||
"http": {
|
||||
"scheme": "http",
|
||||
"protocol": "tcp",
|
||||
"transport": "http",
|
||||
"targetPort": 8005
|
||||
},
|
||||
"grpc": {
|
||||
"scheme": "https",
|
||||
"protocol": "tcp",
|
||||
"transport": "http",
|
||||
"targetPort": 7006
|
||||
}
|
||||
}
|
||||
},
|
||||
"insight": {
|
||||
"type": "project.v1",
|
||||
"path": "../DysonNetwork.Insight/DysonNetwork.Insight.csproj",
|
||||
"env": {
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true",
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true",
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY": "in_memory",
|
||||
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
|
||||
"HTTP_PORTS": "8006",
|
||||
"HTTPS_PORTS": "{insight.bindings.grpc.targetPort}",
|
||||
"services__pass__http__0": "{pass.bindings.http.url}",
|
||||
"services__pass__grpc__0": "{pass.bindings.grpc.url}",
|
||||
"services__ring__http__0": "{ring.bindings.http.url}",
|
||||
"services__ring__grpc__0": "{ring.bindings.grpc.url}",
|
||||
"services__sphere__http__0": "{sphere.bindings.http.url}",
|
||||
"services__sphere__grpc__0": "{sphere.bindings.grpc.url}",
|
||||
"services__develop__http__0": "{develop.bindings.http.url}",
|
||||
"services__develop__grpc__0": "{develop.bindings.grpc.url}",
|
||||
"ConnectionStrings__cache": "{cache.connectionString}",
|
||||
"ConnectionStrings__queue": "{queue.connectionString}",
|
||||
"GRPC_PORT": "7007",
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "{docker-compose-dashboard.bindings.otlp-grpc.url}",
|
||||
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
|
||||
"OTEL_SERVICE_NAME": "insight"
|
||||
},
|
||||
"bindings": {
|
||||
"http": {
|
||||
"scheme": "http",
|
||||
"protocol": "tcp",
|
||||
"transport": "http",
|
||||
"targetPort": 8006
|
||||
},
|
||||
"grpc": {
|
||||
"scheme": "https",
|
||||
"protocol": "tcp",
|
||||
"transport": "http",
|
||||
"targetPort": 7007
|
||||
}
|
||||
}
|
||||
},
|
||||
"gateway": {
|
||||
"type": "project.v1",
|
||||
"path": "../DysonNetwork.Gateway/DysonNetwork.Gateway.csproj",
|
||||
"env": {
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true",
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true",
|
||||
"OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY": "in_memory",
|
||||
"ASPNETCORE_FORWARDEDHEADERS_ENABLED": "true",
|
||||
"HTTP_PORTS": "5001",
|
||||
"services__ring__http__0": "{ring.bindings.http.url}",
|
||||
"services__ring__grpc__0": "{ring.bindings.grpc.url}",
|
||||
"services__pass__http__0": "{pass.bindings.http.url}",
|
||||
"services__pass__grpc__0": "{pass.bindings.grpc.url}",
|
||||
"services__drive__http__0": "{drive.bindings.http.url}",
|
||||
"services__drive__grpc__0": "{drive.bindings.grpc.url}",
|
||||
"services__sphere__http__0": "{sphere.bindings.http.url}",
|
||||
"services__sphere__grpc__0": "{sphere.bindings.grpc.url}",
|
||||
"services__develop__http__0": "{develop.bindings.http.url}",
|
||||
"services__develop__grpc__0": "{develop.bindings.grpc.url}",
|
||||
"services__insight__http__0": "{insight.bindings.http.url}",
|
||||
"services__insight__grpc__0": "{insight.bindings.grpc.url}",
|
||||
"OTEL_EXPORTER_OTLP_ENDPOINT": "{docker-compose-dashboard.bindings.otlp-grpc.url}",
|
||||
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
|
||||
"OTEL_SERVICE_NAME": "gateway"
|
||||
},
|
||||
"bindings": {
|
||||
"http": {
|
||||
"scheme": "http",
|
||||
"protocol": "tcp",
|
||||
"transport": "http",
|
||||
"targetPort": 5001
|
||||
}
|
||||
}
|
||||
},
|
||||
"docker-compose": {
|
||||
"error": "This resource does not support generation in the manifest."
|
||||
},
|
||||
"cache-password": {
|
||||
"type": "parameter.v0",
|
||||
"value": "{cache-password.inputs.value}",
|
||||
"inputs": {
|
||||
"value": {
|
||||
"type": "string",
|
||||
"secret": true,
|
||||
"default": {
|
||||
"generate": {
|
||||
"minLength": 22,
|
||||
"special": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"queue-password": {
|
||||
"type": "parameter.v0",
|
||||
"value": "{queue-password.inputs.value}",
|
||||
"inputs": {
|
||||
"value": {
|
||||
"type": "string",
|
||||
"secret": true,
|
||||
"default": {
|
||||
"generate": {
|
||||
"minLength": 22,
|
||||
"special": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"docker-compose-dashboard": {
|
||||
"type": "container.v1",
|
||||
"image": "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest",
|
||||
"bindings": {
|
||||
"http": {
|
||||
"scheme": "http",
|
||||
"protocol": "tcp",
|
||||
"transport": "http",
|
||||
"targetPort": 18888
|
||||
},
|
||||
"otlp-grpc": {
|
||||
"scheme": "http",
|
||||
"protocol": "tcp",
|
||||
"transport": "http",
|
||||
"targetPort": 18889
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,4 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
libkrb5-3 \
|
||||
libgssapi-krb5-2 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
USER $APP_UID
|
||||
WORKDIR /app
|
||||
EXPOSE 8080
|
||||
|
||||
@@ -8,15 +8,15 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.1">
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" />
|
||||
<PackageReference Include="NodaTime" Version="3.2.3" />
|
||||
<PackageReference Include="NodaTime" Version="3.2.2"/>
|
||||
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0"/>
|
||||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.76.0" />
|
||||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -7,16 +7,15 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddServiceDefaults();
|
||||
builder.AddServiceDefaults("develop");
|
||||
|
||||
builder.Services.Configure<ServiceRegistrationOptions>(opts => { opts.Name = "develop"; });
|
||||
|
||||
builder.ConfigureAppKestrel(builder.Configuration);
|
||||
|
||||
builder.Services.AddAppServices(builder.Configuration);
|
||||
builder.Services.AddAppAuthentication();
|
||||
builder.Services.AddDysonAuth();
|
||||
builder.Services.AddSphereService();
|
||||
builder.Services.AddAccountService();
|
||||
builder.Services.AddDriveService();
|
||||
|
||||
builder.AddSwaggerManifest(
|
||||
"DysonNetwork.Develop",
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace DysonNetwork.Develop.Project;
|
||||
|
||||
[ApiController]
|
||||
[Route("/api/developers/{pubName}/projects")]
|
||||
public class DevProjectController(DevProjectService ps, DeveloperService ds) : ControllerBase
|
||||
public class DevProjectController(DevProjectService projectService, DeveloperService developerService) : ControllerBase
|
||||
{
|
||||
public record DevProjectRequest(
|
||||
[MaxLength(1024)] string? Slug,
|
||||
@@ -19,20 +19,20 @@ public class DevProjectController(DevProjectService ps, DeveloperService ds) : C
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> ListProjects([FromRoute] string pubName)
|
||||
{
|
||||
var developer = await ds.GetDeveloperByName(pubName);
|
||||
var developer = await developerService.GetDeveloperByName(pubName);
|
||||
if (developer is null) return NotFound();
|
||||
|
||||
var projects = await ps.GetProjectsByDeveloperAsync(developer.Id);
|
||||
var projects = await projectService.GetProjectsByDeveloperAsync(developer.Id);
|
||||
return Ok(projects);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetProject([FromRoute] string pubName, Guid id)
|
||||
{
|
||||
var developer = await ds.GetDeveloperByName(pubName);
|
||||
var developer = await developerService.GetDeveloperByName(pubName);
|
||||
if (developer is null) return NotFound();
|
||||
|
||||
var project = await ps.GetProjectAsync(id, developer.Id);
|
||||
var project = await projectService.GetProjectAsync(id, developer.Id);
|
||||
if (project is null) return NotFound();
|
||||
|
||||
return Ok(project);
|
||||
@@ -45,17 +45,17 @@ public class DevProjectController(DevProjectService ps, DeveloperService ds) : C
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
||||
return Unauthorized();
|
||||
|
||||
var developer = await ds.GetDeveloperByName(pubName);
|
||||
var developer = await developerService.GetDeveloperByName(pubName);
|
||||
if (developer is null)
|
||||
return NotFound("Developer not found");
|
||||
|
||||
if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor))
|
||||
if (!await developerService.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), PublisherMemberRole.Editor))
|
||||
return StatusCode(403, "You must be an editor of the developer to create a project");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.Slug) || string.IsNullOrWhiteSpace(request.Name))
|
||||
return BadRequest("Slug and Name are required");
|
||||
|
||||
var project = await ps.CreateProjectAsync(developer, request);
|
||||
var project = await projectService.CreateProjectAsync(developer, request);
|
||||
return CreatedAtAction(
|
||||
nameof(GetProject),
|
||||
new { pubName, id = project.Id },
|
||||
@@ -74,15 +74,12 @@ public class DevProjectController(DevProjectService ps, DeveloperService ds) : C
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
||||
return Unauthorized();
|
||||
|
||||
var developer = await ds.GetDeveloperByName(pubName);
|
||||
var developer = await developerService.GetDeveloperByName(pubName);
|
||||
var accountId = Guid.Parse(currentUser.Id);
|
||||
|
||||
if (developer is null)
|
||||
if (developer is null || developer.Id != accountId)
|
||||
return Forbid();
|
||||
if (!await ds.IsMemberWithRole(developer.PublisherId, accountId, PublisherMemberRole.Manager))
|
||||
return StatusCode(403, "You must be an manager of the developer to update a project");
|
||||
|
||||
var project = await ps.UpdateProjectAsync(id, developer.Id, request);
|
||||
var project = await projectService.UpdateProjectAsync(id, developer.Id, request);
|
||||
if (project is null)
|
||||
return NotFound();
|
||||
|
||||
@@ -96,14 +93,12 @@ public class DevProjectController(DevProjectService ps, DeveloperService ds) : C
|
||||
if (HttpContext.Items["CurrentUser"] is not Account currentUser)
|
||||
return Unauthorized();
|
||||
|
||||
var developer = await ds.GetDeveloperByName(pubName);
|
||||
var developer = await developerService.GetDeveloperByName(pubName);
|
||||
var accountId = Guid.Parse(currentUser.Id);
|
||||
if (developer is null)
|
||||
if (developer is null || developer.Id != accountId)
|
||||
return Forbid();
|
||||
if (!await ds.IsMemberWithRole(developer.PublisherId, accountId, PublisherMemberRole.Manager))
|
||||
return StatusCode(403, "You must be an manager of the developer to delete a project");
|
||||
|
||||
var success = await ps.DeleteProjectAsync(id, developer.Id);
|
||||
var success = await projectService.DeleteProjectAsync(id, developer.Id);
|
||||
if (!success)
|
||||
return NotFound();
|
||||
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_develop;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60"
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_develop;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60",
|
||||
"Registrar": "127.0.0.1:2379",
|
||||
"Cache": "127.0.0.1:6379",
|
||||
"Queue": "127.0.0.1:4222"
|
||||
},
|
||||
"KnownProxies": [
|
||||
"127.0.0.1",
|
||||
@@ -21,5 +24,8 @@
|
||||
},
|
||||
"Cache": {
|
||||
"Serializer": "MessagePack"
|
||||
},
|
||||
"Etcd": {
|
||||
"Insecure": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.3.4" />
|
||||
<PackageReference Include="FFMpegCore" Version="5.4.0" />
|
||||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.76.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.1">
|
||||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
@@ -27,13 +27,14 @@
|
||||
<PackageReference Include="NetVips" Version="3.1.0" />
|
||||
<PackageReference Include="NetVips.Native.linux-x64" Version="8.17.3" />
|
||||
<PackageReference Include="NetVips.Native.osx-arm64" Version="8.17.3" />
|
||||
<PackageReference Include="NodaTime" Version="3.2.3" />
|
||||
<PackageReference Include="NodaTime" Version="3.2.2" />
|
||||
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.2.0" />
|
||||
<PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" />
|
||||
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" />
|
||||
<PackageReference Include="Quartz" Version="3.15.1" />
|
||||
<PackageReference Include="Quartz.AspNetCore" Version="3.15.1" />
|
||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.15.1" />
|
||||
<PackageReference Include="EFCore.BulkExtensions.PostgreSql" Version="9.0.2" />
|
||||
<!-- Pin the SkiaSharp version at the 2.88.9 due to the BlurHash need this specific version -->
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.9" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.9" />
|
||||
|
||||
@@ -1,560 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DysonNetwork.Drive;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NodaTime;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Drive.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDatabase))]
|
||||
[Migration("20260101153809_RemoveUploadTask")]
|
||||
partial class RemoveUploadTask
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.1")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Drive.Billing.QuotaRecord", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<Instant?>("ExpiredAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<long>("Quota")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("quota");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_quota_records");
|
||||
|
||||
b.ToTable("quota_records", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Drive.Storage.Model.PersistentTask", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Instant?>("CompletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("completed_at");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<string>("ErrorMessage")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("error_message");
|
||||
|
||||
b.Property<long?>("EstimatedDurationSeconds")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("estimated_duration_seconds");
|
||||
|
||||
b.Property<Instant?>("ExpiredAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
|
||||
b.Property<Instant>("LastActivity")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_activity");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<Dictionary<string, object>>("Parameters")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("parameters");
|
||||
|
||||
b.Property<int>("Priority")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("priority");
|
||||
|
||||
b.Property<double>("Progress")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("progress");
|
||||
|
||||
b.Property<Dictionary<string, object>>("Results")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("results");
|
||||
|
||||
b.Property<Instant?>("StartedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("started_at");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("status");
|
||||
|
||||
b.Property<string>("TaskId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasColumnName("task_id");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("type");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_tasks");
|
||||
|
||||
b.ToTable("tasks", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.FilePool", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid?>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<BillingConfig>("BillingConfig")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("billing_config");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<bool>("IsHidden")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_hidden");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<PolicyConfig>("PolicyConfig")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("policy_config");
|
||||
|
||||
b.Property<RemoteStorageConfig>("StorageConfig")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("storage_config");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_pools");
|
||||
|
||||
b.ToTable("pools", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFile", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Guid?>("BundleId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("bundle_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<Instant?>("ExpiredAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
|
||||
b.Property<Dictionary<string, object>>("FileMeta")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("file_meta");
|
||||
|
||||
b.Property<bool>("HasCompression")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("has_compression");
|
||||
|
||||
b.Property<bool>("HasThumbnail")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("has_thumbnail");
|
||||
|
||||
b.Property<string>("Hash")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("hash");
|
||||
|
||||
b.Property<bool>("IsEncrypted")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_encrypted");
|
||||
|
||||
b.Property<bool>("IsMarkedRecycle")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_marked_recycle");
|
||||
|
||||
b.Property<string>("MimeType")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("mime_type");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<Guid?>("PoolId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("pool_id");
|
||||
|
||||
b.PrimitiveCollection<string>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
b.Property<long>("Size")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("size");
|
||||
|
||||
b.Property<string>("StorageId")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("storage_id");
|
||||
|
||||
b.Property<string>("StorageUrl")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("storage_url");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<Instant?>("UploadedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("uploaded_at");
|
||||
|
||||
b.Property<Dictionary<string, object>>("UserMeta")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("user_meta");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_files");
|
||||
|
||||
b.HasIndex("BundleId")
|
||||
.HasDatabaseName("ix_files_bundle_id");
|
||||
|
||||
b.HasIndex("PoolId")
|
||||
.HasDatabaseName("ix_files_pool_id");
|
||||
|
||||
b.ToTable("files", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFileIndex", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("FileId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("file_id");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("path");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_file_indexes");
|
||||
|
||||
b.HasIndex("FileId")
|
||||
.HasDatabaseName("ix_file_indexes_file_id");
|
||||
|
||||
b.HasIndex("Path", "AccountId")
|
||||
.HasDatabaseName("ix_file_indexes_path_account_id");
|
||||
|
||||
b.ToTable("file_indexes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFileReference", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<Instant?>("ExpiredAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
|
||||
b.Property<string>("FileId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("file_id");
|
||||
|
||||
b.Property<string>("ResourceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("resource_id");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<string>("Usage")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("usage");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_file_references");
|
||||
|
||||
b.HasIndex("FileId")
|
||||
.HasDatabaseName("ix_file_references_file_id");
|
||||
|
||||
b.ToTable("file_references", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnFileBundle", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<Instant?>("ExpiredAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<string>("Passcode")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("passcode");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("slug");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_bundles");
|
||||
|
||||
b.HasIndex("Slug")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_bundles_slug");
|
||||
|
||||
b.ToTable("bundles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFile", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnFileBundle", "Bundle")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("BundleId")
|
||||
.HasConstraintName("fk_files_bundles_bundle_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.FilePool", "Pool")
|
||||
.WithMany()
|
||||
.HasForeignKey("PoolId")
|
||||
.HasConstraintName("fk_files_pools_pool_id");
|
||||
|
||||
b.Navigation("Bundle");
|
||||
|
||||
b.Navigation("Pool");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFileIndex", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnCloudFile", "File")
|
||||
.WithMany("FileIndexes")
|
||||
.HasForeignKey("FileId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_file_indexes_files_file_id");
|
||||
|
||||
b.Navigation("File");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFileReference", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnCloudFile", "File")
|
||||
.WithMany("References")
|
||||
.HasForeignKey("FileId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_file_references_files_file_id");
|
||||
|
||||
b.Navigation("File");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFile", b =>
|
||||
{
|
||||
b.Navigation("FileIndexes");
|
||||
|
||||
b.Navigation("References");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnFileBundle", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Drive.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveUploadTask : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "bundle_id",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "chunk_size",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "chunks_count",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "chunks_uploaded",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "content_type",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "discriminator",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "encrypt_password",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "file_name",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "file_size",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "hash",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "path",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "pool_id",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "uploaded_chunks",
|
||||
table: "tasks");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "bundle_id",
|
||||
table: "tasks",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "chunk_size",
|
||||
table: "tasks",
|
||||
type: "bigint",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "chunks_count",
|
||||
table: "tasks",
|
||||
type: "integer",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "chunks_uploaded",
|
||||
table: "tasks",
|
||||
type: "integer",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "content_type",
|
||||
table: "tasks",
|
||||
type: "character varying(128)",
|
||||
maxLength: 128,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "discriminator",
|
||||
table: "tasks",
|
||||
type: "character varying(21)",
|
||||
maxLength: 21,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "encrypt_password",
|
||||
table: "tasks",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "file_name",
|
||||
table: "tasks",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "file_size",
|
||||
table: "tasks",
|
||||
type: "bigint",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "hash",
|
||||
table: "tasks",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "path",
|
||||
table: "tasks",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "pool_id",
|
||||
table: "tasks",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<List<int>>(
|
||||
name: "uploaded_chunks",
|
||||
table: "tasks",
|
||||
type: "integer[]",
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,632 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DysonNetwork.Drive;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NodaTime;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Drive.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDatabase))]
|
||||
[Migration("20260101154612_RollbackRemoveUploadTask")]
|
||||
partial class RollbackRemoveUploadTask
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.1")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Drive.Billing.QuotaRecord", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<Instant?>("ExpiredAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<long>("Quota")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("quota");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_quota_records");
|
||||
|
||||
b.ToTable("quota_records", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Drive.Storage.Model.PersistentTask", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Instant?>("CompletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("completed_at");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<string>("Discriminator")
|
||||
.IsRequired()
|
||||
.HasMaxLength(21)
|
||||
.HasColumnType("character varying(21)")
|
||||
.HasColumnName("discriminator");
|
||||
|
||||
b.Property<string>("ErrorMessage")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("error_message");
|
||||
|
||||
b.Property<long?>("EstimatedDurationSeconds")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("estimated_duration_seconds");
|
||||
|
||||
b.Property<Instant?>("ExpiredAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
|
||||
b.Property<Instant>("LastActivity")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_activity");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<Dictionary<string, object>>("Parameters")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("parameters");
|
||||
|
||||
b.Property<int>("Priority")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("priority");
|
||||
|
||||
b.Property<double>("Progress")
|
||||
.HasColumnType("double precision")
|
||||
.HasColumnName("progress");
|
||||
|
||||
b.Property<Dictionary<string, object>>("Results")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("results");
|
||||
|
||||
b.Property<Instant?>("StartedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("started_at");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("status");
|
||||
|
||||
b.Property<string>("TaskId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("character varying(64)")
|
||||
.HasColumnName("task_id");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("type");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_tasks");
|
||||
|
||||
b.ToTable("tasks", (string)null);
|
||||
|
||||
b.HasDiscriminator().HasValue("PersistentTask");
|
||||
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.FilePool", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid?>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<BillingConfig>("BillingConfig")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("billing_config");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<bool>("IsHidden")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_hidden");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<PolicyConfig>("PolicyConfig")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("policy_config");
|
||||
|
||||
b.Property<RemoteStorageConfig>("StorageConfig")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("storage_config");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_pools");
|
||||
|
||||
b.ToTable("pools", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFile", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Guid?>("BundleId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("bundle_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<Instant?>("ExpiredAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
|
||||
b.Property<Dictionary<string, object>>("FileMeta")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("file_meta");
|
||||
|
||||
b.Property<bool>("HasCompression")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("has_compression");
|
||||
|
||||
b.Property<bool>("HasThumbnail")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("has_thumbnail");
|
||||
|
||||
b.Property<string>("Hash")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("hash");
|
||||
|
||||
b.Property<bool>("IsEncrypted")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_encrypted");
|
||||
|
||||
b.Property<bool>("IsMarkedRecycle")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_marked_recycle");
|
||||
|
||||
b.Property<string>("MimeType")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("mime_type");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<Guid?>("PoolId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("pool_id");
|
||||
|
||||
b.PrimitiveCollection<string>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
b.Property<long>("Size")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("size");
|
||||
|
||||
b.Property<string>("StorageId")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("storage_id");
|
||||
|
||||
b.Property<string>("StorageUrl")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("storage_url");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<Instant?>("UploadedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("uploaded_at");
|
||||
|
||||
b.Property<Dictionary<string, object>>("UserMeta")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("user_meta");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_files");
|
||||
|
||||
b.HasIndex("BundleId")
|
||||
.HasDatabaseName("ix_files_bundle_id");
|
||||
|
||||
b.HasIndex("PoolId")
|
||||
.HasDatabaseName("ix_files_pool_id");
|
||||
|
||||
b.ToTable("files", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFileIndex", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("FileId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("file_id");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("path");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_file_indexes");
|
||||
|
||||
b.HasIndex("FileId")
|
||||
.HasDatabaseName("ix_file_indexes_file_id");
|
||||
|
||||
b.HasIndex("Path", "AccountId")
|
||||
.HasDatabaseName("ix_file_indexes_path_account_id");
|
||||
|
||||
b.ToTable("file_indexes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFileReference", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<Instant?>("ExpiredAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
|
||||
b.Property<string>("FileId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("file_id");
|
||||
|
||||
b.Property<string>("ResourceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("resource_id");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<string>("Usage")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("usage");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_file_references");
|
||||
|
||||
b.HasIndex("FileId")
|
||||
.HasDatabaseName("ix_file_references_file_id");
|
||||
|
||||
b.ToTable("file_references", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnFileBundle", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<Instant?>("ExpiredAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<string>("Passcode")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("passcode");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("slug");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_bundles");
|
||||
|
||||
b.HasIndex("Slug")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("ix_bundles_slug");
|
||||
|
||||
b.ToTable("bundles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Drive.Storage.Model.PersistentUploadTask", b =>
|
||||
{
|
||||
b.HasBaseType("DysonNetwork.Drive.Storage.Model.PersistentTask");
|
||||
|
||||
b.Property<Guid?>("BundleId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("bundle_id");
|
||||
|
||||
b.Property<long>("ChunkSize")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("chunk_size");
|
||||
|
||||
b.Property<int>("ChunksCount")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("chunks_count");
|
||||
|
||||
b.Property<int>("ChunksUploaded")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("chunks_uploaded");
|
||||
|
||||
b.Property<string>("ContentType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)")
|
||||
.HasColumnName("content_type");
|
||||
|
||||
b.Property<string>("EncryptPassword")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("encrypt_password");
|
||||
|
||||
b.Property<string>("FileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("file_name");
|
||||
|
||||
b.Property<long>("FileSize")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("file_size");
|
||||
|
||||
b.Property<string>("Hash")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("hash");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("path");
|
||||
|
||||
b.Property<Guid>("PoolId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("pool_id");
|
||||
|
||||
b.PrimitiveCollection<List<int>>("UploadedChunks")
|
||||
.IsRequired()
|
||||
.HasColumnType("integer[]")
|
||||
.HasColumnName("uploaded_chunks");
|
||||
|
||||
b.HasDiscriminator().HasValue("PersistentUploadTask");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFile", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnFileBundle", "Bundle")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("BundleId")
|
||||
.HasConstraintName("fk_files_bundles_bundle_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.FilePool", "Pool")
|
||||
.WithMany()
|
||||
.HasForeignKey("PoolId")
|
||||
.HasConstraintName("fk_files_pools_pool_id");
|
||||
|
||||
b.Navigation("Bundle");
|
||||
|
||||
b.Navigation("Pool");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFileIndex", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnCloudFile", "File")
|
||||
.WithMany("FileIndexes")
|
||||
.HasForeignKey("FileId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_file_indexes_files_file_id");
|
||||
|
||||
b.Navigation("File");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFileReference", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnCloudFile", "File")
|
||||
.WithMany("References")
|
||||
.HasForeignKey("FileId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_file_references_files_file_id");
|
||||
|
||||
b.Navigation("File");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFile", b =>
|
||||
{
|
||||
b.Navigation("FileIndexes");
|
||||
|
||||
b.Navigation("References");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnFileBundle", b =>
|
||||
{
|
||||
b.Navigation("Files");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Drive.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RollbackRemoveUploadTask : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "bundle_id",
|
||||
table: "tasks",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "chunk_size",
|
||||
table: "tasks",
|
||||
type: "bigint",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "chunks_count",
|
||||
table: "tasks",
|
||||
type: "integer",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "chunks_uploaded",
|
||||
table: "tasks",
|
||||
type: "integer",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "content_type",
|
||||
table: "tasks",
|
||||
type: "character varying(128)",
|
||||
maxLength: 128,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "discriminator",
|
||||
table: "tasks",
|
||||
type: "character varying(21)",
|
||||
maxLength: 21,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "encrypt_password",
|
||||
table: "tasks",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "file_name",
|
||||
table: "tasks",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<long>(
|
||||
name: "file_size",
|
||||
table: "tasks",
|
||||
type: "bigint",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "hash",
|
||||
table: "tasks",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "path",
|
||||
table: "tasks",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "pool_id",
|
||||
table: "tasks",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<List<int>>(
|
||||
name: "uploaded_chunks",
|
||||
table: "tasks",
|
||||
type: "integer[]",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "bundle_id",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "chunk_size",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "chunks_count",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "chunks_uploaded",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "content_type",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "discriminator",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "encrypt_password",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "file_name",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "file_size",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "hash",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "path",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "pool_id",
|
||||
table: "tasks");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "uploaded_chunks",
|
||||
table: "tasks");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace DysonNetwork.Drive.Migrations
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.1")
|
||||
.HasAnnotation("ProductVersion", "9.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
@@ -179,6 +179,56 @@ namespace DysonNetwork.Drive.Migrations
|
||||
b.UseTphMappingStrategy();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFileReference", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<Instant?>("ExpiredAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
|
||||
b.Property<string>("FileId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("file_id");
|
||||
|
||||
b.Property<string>("ResourceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("resource_id");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<string>("Usage")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("usage");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_file_references");
|
||||
|
||||
b.HasIndex("FileId")
|
||||
.HasDatabaseName("ix_file_references_file_id");
|
||||
|
||||
b.ToTable("file_references", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.FilePool", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -311,7 +361,7 @@ namespace DysonNetwork.Drive.Migrations
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("pool_id");
|
||||
|
||||
b.PrimitiveCollection<string>("SensitiveMarks")
|
||||
b.Property<List<ContentSensitiveMark>>("SensitiveMarks")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("sensitive_marks");
|
||||
|
||||
@@ -400,56 +450,6 @@ namespace DysonNetwork.Drive.Migrations
|
||||
b.ToTable("file_indexes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFileReference", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<Instant?>("ExpiredAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expired_at");
|
||||
|
||||
b.Property<string>("FileId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("character varying(32)")
|
||||
.HasColumnName("file_id");
|
||||
|
||||
b.Property<string>("ResourceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("resource_id");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<string>("Usage")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("usage");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_file_references");
|
||||
|
||||
b.HasIndex("FileId")
|
||||
.HasDatabaseName("ix_file_references_file_id");
|
||||
|
||||
b.ToTable("file_references", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnFileBundle", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@@ -571,6 +571,18 @@ namespace DysonNetwork.Drive.Migrations
|
||||
b.HasDiscriminator().HasValue("PersistentUploadTask");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFileReference", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnCloudFile", "File")
|
||||
.WithMany("References")
|
||||
.HasForeignKey("FileId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_file_references_files_file_id");
|
||||
|
||||
b.Navigation("File");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFile", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnFileBundle", "Bundle")
|
||||
@@ -600,18 +612,6 @@ namespace DysonNetwork.Drive.Migrations
|
||||
b.Navigation("File");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFileReference", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnCloudFile", "File")
|
||||
.WithMany("References")
|
||||
.HasForeignKey("FileId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_file_references_files_file_id");
|
||||
|
||||
b.Navigation("File");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnCloudFile", b =>
|
||||
{
|
||||
b.Navigation("FileIndexes");
|
||||
|
||||
@@ -7,7 +7,9 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddServiceDefaults();
|
||||
builder.AddServiceDefaults("drive");
|
||||
|
||||
builder.Services.Configure<ServiceRegistrationOptions>(opts => { opts.Name = "drive"; });
|
||||
|
||||
// Configure Kestrel and server options
|
||||
builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxValue);
|
||||
@@ -17,8 +19,6 @@ builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxV
|
||||
builder.Services.AddAppServices(builder.Configuration);
|
||||
builder.Services.AddAppAuthentication();
|
||||
builder.Services.AddDysonAuth();
|
||||
builder.Services.AddRingService();
|
||||
builder.Services.AddAccountService();
|
||||
|
||||
builder.Services.AddAppFlushHandlers();
|
||||
builder.Services.AddAppBusinessServices();
|
||||
|
||||
@@ -3,7 +3,7 @@ using DysonNetwork.Drive.Storage;
|
||||
using DysonNetwork.Drive.Storage.Model;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using DysonNetwork.Shared.Queue;
|
||||
using DysonNetwork.Shared.Stream;
|
||||
using FFMpegCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NATS.Client.Core;
|
||||
|
||||
@@ -9,61 +9,58 @@ namespace DysonNetwork.Drive.Startup;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
extension(IServiceCollection services)
|
||||
public static IServiceCollection AddAppServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
public IServiceCollection AddAppServices(IConfiguration configuration)
|
||||
services.AddDbContext<AppDatabase>(); // Assuming you'll have an AppDatabase
|
||||
services.AddHttpContextAccessor();
|
||||
|
||||
services.AddHttpClient();
|
||||
|
||||
// Register gRPC services
|
||||
services.AddGrpc(options =>
|
||||
{
|
||||
services.AddDbContext<AppDatabase>();
|
||||
services.AddHttpContextAccessor();
|
||||
options.EnableDetailedErrors = true; // Will be adjusted in Program.cs
|
||||
options.MaxReceiveMessageSize = 16 * 1024 * 1024; // 16MB
|
||||
options.MaxSendMessageSize = 16 * 1024 * 1024; // 16MB
|
||||
});
|
||||
services.AddGrpcReflection();
|
||||
|
||||
services.AddHttpClient();
|
||||
|
||||
// Register gRPC services
|
||||
services.AddGrpc(options =>
|
||||
{
|
||||
options.EnableDetailedErrors = true; // Will be adjusted in Program.cs
|
||||
options.MaxReceiveMessageSize = 16 * 1024 * 1024; // 16MB
|
||||
options.MaxSendMessageSize = 16 * 1024 * 1024; // 16MB
|
||||
});
|
||||
services.AddGrpcReflection();
|
||||
|
||||
services.AddControllers().AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals;
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
|
||||
options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower;
|
||||
|
||||
options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public IServiceCollection AddAppAuthentication()
|
||||
services.AddControllers().AddJsonOptions(options =>
|
||||
{
|
||||
services.AddAuthorization();
|
||||
return services;
|
||||
}
|
||||
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals;
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
|
||||
options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower;
|
||||
|
||||
public IServiceCollection AddAppFlushHandlers()
|
||||
{
|
||||
services.AddSingleton<FlushBufferService>();
|
||||
options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
return services;
|
||||
}
|
||||
|
||||
public IServiceCollection AddAppBusinessServices()
|
||||
{
|
||||
services.AddScoped<Storage.FileService>();
|
||||
services.AddScoped<Storage.FileReferenceService>();
|
||||
services.AddScoped<Storage.PersistentTaskService>();
|
||||
services.AddScoped<FileIndexService>();
|
||||
services.AddScoped<Billing.UsageService>();
|
||||
services.AddScoped<Billing.QuotaService>();
|
||||
public static IServiceCollection AddAppAuthentication(this IServiceCollection services)
|
||||
{
|
||||
services.AddAuthorization();
|
||||
return services;
|
||||
}
|
||||
|
||||
services.AddHostedService<BroadcastEventHandler>();
|
||||
public static IServiceCollection AddAppFlushHandlers(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<FlushBufferService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddAppBusinessServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<Storage.FileService>();
|
||||
services.AddScoped<Storage.FileReferenceService>();
|
||||
services.AddScoped<Storage.PersistentTaskService>();
|
||||
services.AddScoped<FileIndexService>();
|
||||
services.AddScoped<Billing.UsageService>();
|
||||
services.AddScoped<Billing.QuotaService>();
|
||||
|
||||
services.AddHostedService<BroadcastEventHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,9 @@ public class FileController(
|
||||
private ActionResult? TryProxyRedirect(SnCloudFile file, RemoteStorageConfig dest, string fileName)
|
||||
{
|
||||
if (dest.ImageProxy is not null && (file.MimeType?.StartsWith("image/") ?? false))
|
||||
{
|
||||
return Redirect(BuildProxyUrl(dest.ImageProxy, fileName));
|
||||
}
|
||||
|
||||
return dest.AccessProxy is not null ? Redirect(BuildProxyUrl(dest.AccessProxy, fileName)) : null;
|
||||
}
|
||||
@@ -180,9 +182,6 @@ public class FileController(
|
||||
.WithHeaders(headers)
|
||||
);
|
||||
|
||||
if (dest.AccessEndpoint is not null)
|
||||
openUrl = openUrl.Replace($"{dest.Endpoint}/{dest.Bucket}", dest.AccessEndpoint);
|
||||
|
||||
return Redirect(openUrl);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using EFCore.BulkExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
@@ -58,25 +59,16 @@ public class FileReferenceService(AppDatabase db, FileService fileService, ICach
|
||||
)
|
||||
{
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
var finalExpiredAt = expiredAt;
|
||||
if (finalExpiredAt == null && duration.HasValue)
|
||||
{
|
||||
finalExpiredAt = now + duration.Value;
|
||||
}
|
||||
|
||||
var data = fileId.Select(id => new SnCloudFileReference
|
||||
{
|
||||
FileId = id,
|
||||
Usage = usage,
|
||||
ResourceId = resourceId,
|
||||
ExpiredAt = finalExpiredAt,
|
||||
CreatedAt = now,
|
||||
UpdatedAt = now
|
||||
})
|
||||
.ToList();
|
||||
|
||||
db.FileReferences.AddRange(data);
|
||||
await db.SaveChangesAsync();
|
||||
{
|
||||
FileId = id,
|
||||
Usage = usage,
|
||||
ResourceId = resourceId,
|
||||
ExpiredAt = expiredAt ?? now + duration,
|
||||
CreatedAt = now,
|
||||
UpdatedAt = now
|
||||
}).ToList();
|
||||
await db.BulkInsertAsync(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@@ -793,7 +793,7 @@ file class UpdatableCloudFile(SnCloudFile file)
|
||||
public Dictionary<string, object?>? UserMeta { get; set; } = file.UserMeta;
|
||||
public bool IsMarkedRecycle { get; set; } = file.IsMarkedRecycle;
|
||||
|
||||
public Action<UpdateSettersBuilder<SnCloudFile>> ToSetPropertyCalls()
|
||||
public Expression<Func<SetPropertyCalls<SnCloudFile>, SetPropertyCalls<SnCloudFile>>> ToSetPropertyCalls()
|
||||
{
|
||||
var userMeta = UserMeta ?? [];
|
||||
return setter => setter
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_drive;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60"
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_drive;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60",
|
||||
"Registrar": "127.0.0.1:2379",
|
||||
"Cache": "127.0.0.1:6379",
|
||||
"Queue": "127.0.0.1:4222"
|
||||
},
|
||||
"Authentication": {
|
||||
"Schemes": {
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
[ApiController]
|
||||
[Route("config")]
|
||||
public class ConfigurationController(IConfiguration configuration) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public IActionResult Get() => Ok(configuration.GetSection("Client").Get<Dictionary<string, object>>());
|
||||
|
||||
[HttpGet("site")]
|
||||
public IActionResult GetSiteUrl() => Ok(configuration["SiteUrl"]);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
|
||||
USER $APP_UID
|
||||
WORKDIR /app
|
||||
EXPOSE 8080
|
||||
EXPOSE 8081
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
COPY ["DysonNetwork.Gateway/DysonNetwork.Gateway.csproj", "DysonNetwork.Gateway/"]
|
||||
RUN dotnet restore "DysonNetwork.Gateway/DysonNetwork.Gateway.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/DysonNetwork.Gateway"
|
||||
RUN dotnet build "./DysonNetwork.Gateway.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "./DysonNetwork.Gateway.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "DysonNetwork.Gateway.dll"]
|
||||
@@ -1,22 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery.Yarp" Version="10.1.0" />
|
||||
<PackageReference Include="Nerdbank.GitVersioning" Version="3.9.50">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Yarp.ReverseProxy" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,25 +0,0 @@
|
||||
namespace DysonNetwork.Gateway.Health;
|
||||
|
||||
public abstract class GatewayConstant
|
||||
{
|
||||
public static readonly string[] ServiceNames =
|
||||
[
|
||||
"ring",
|
||||
"pass",
|
||||
"drive",
|
||||
"sphere",
|
||||
"develop",
|
||||
"insight",
|
||||
"zone",
|
||||
"messager"
|
||||
];
|
||||
|
||||
// Core services stands with w/o these services the functional of entire app will broke.
|
||||
public static readonly string[] CoreServiceNames =
|
||||
[
|
||||
"ring",
|
||||
"pass",
|
||||
"drive",
|
||||
"sphere"
|
||||
];
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Gateway.Health;
|
||||
|
||||
public class GatewayHealthAggregator(IHttpClientFactory httpClientFactory, GatewayReadinessStore store)
|
||||
: BackgroundService
|
||||
{
|
||||
private async Task<ServiceHealthState> CheckService(string serviceName)
|
||||
{
|
||||
var client = httpClientFactory.CreateClient("health");
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
try
|
||||
{
|
||||
// Use the service discovery to lookup service
|
||||
// The service defaults give every single service a health endpoint that we can use here
|
||||
using var response = await client.GetAsync($"http://{serviceName}/health");
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return new ServiceHealthState(
|
||||
serviceName,
|
||||
true,
|
||||
now,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
return new ServiceHealthState(
|
||||
serviceName,
|
||||
false,
|
||||
now,
|
||||
$"StatusCode: {(int)response.StatusCode}"
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new ServiceHealthState(
|
||||
serviceName,
|
||||
false,
|
||||
now,
|
||||
ex.Message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
foreach (var service in GatewayConstant.ServiceNames)
|
||||
{
|
||||
var result = await CheckService(service);
|
||||
store.Update(result);
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
namespace DysonNetwork.Gateway.Health;
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
public sealed class GatewayReadinessMiddleware(RequestDelegate next)
|
||||
{
|
||||
public async Task InvokeAsync(HttpContext context, GatewayReadinessStore store)
|
||||
{
|
||||
if (context.Request.Path.StartsWithSegments("/health"))
|
||||
{
|
||||
await next(context);
|
||||
return;
|
||||
}
|
||||
|
||||
var readiness = store.Current;
|
||||
|
||||
// Only core services participate in readiness gating
|
||||
var notReadyCoreServices = readiness.Services
|
||||
.Where(kv => GatewayConstant.CoreServiceNames.Contains(kv.Key))
|
||||
.Where(kv => !kv.Value.IsHealthy)
|
||||
.Select(kv => kv.Key)
|
||||
.ToArray();
|
||||
|
||||
if (notReadyCoreServices.Length > 0)
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable;
|
||||
var unavailableServices = string.Join(", ", notReadyCoreServices);
|
||||
context.Response.Headers["X-NotReady"] = unavailableServices;
|
||||
await context.Response.WriteAsync("Solar Network is warming up. Try again later please.");
|
||||
return;
|
||||
}
|
||||
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Gateway.Health;
|
||||
|
||||
public record ServiceHealthState(
|
||||
string ServiceName,
|
||||
bool IsHealthy,
|
||||
Instant LastChecked,
|
||||
string? Error
|
||||
);
|
||||
|
||||
public record GatewayReadinessState(
|
||||
bool IsReady,
|
||||
IReadOnlyDictionary<string, ServiceHealthState> Services,
|
||||
Instant LastUpdated
|
||||
);
|
||||
|
||||
public class GatewayReadinessStore
|
||||
{
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
private readonly Dictionary<string, ServiceHealthState> _services = new();
|
||||
|
||||
public GatewayReadinessState Current { get; private set; } = new(
|
||||
IsReady: false,
|
||||
Services: new Dictionary<string, ServiceHealthState>(),
|
||||
LastUpdated: SystemClock.Instance.GetCurrentInstant()
|
||||
);
|
||||
|
||||
public IReadOnlyCollection<string> ServiceNames => _services.Keys;
|
||||
|
||||
public GatewayReadinessStore()
|
||||
{
|
||||
InitializeServices(GatewayConstant.ServiceNames);
|
||||
}
|
||||
|
||||
private void InitializeServices(IEnumerable<string> serviceNames)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_services.Clear();
|
||||
|
||||
foreach (var name in serviceNames)
|
||||
{
|
||||
_services[name] = new ServiceHealthState(
|
||||
name,
|
||||
IsHealthy: false,
|
||||
LastChecked: SystemClock.Instance.GetCurrentInstant(),
|
||||
Error: "Not checked yet"
|
||||
);
|
||||
}
|
||||
|
||||
RecalculateLocked();
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(ServiceHealthState state)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_services[state.ServiceName] = state;
|
||||
RecalculateLocked();
|
||||
}
|
||||
}
|
||||
|
||||
private void RecalculateLocked()
|
||||
{
|
||||
var isReady = _services.Count > 0 && _services.Values.All(s => s.IsHealthy);
|
||||
|
||||
Current = new GatewayReadinessState(
|
||||
IsReady: isReady,
|
||||
Services: new Dictionary<string, ServiceHealthState>(_services),
|
||||
LastUpdated: SystemClock.Instance.GetCurrentInstant()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace DysonNetwork.Gateway.Health;
|
||||
|
||||
[ApiController]
|
||||
[Route("/health")]
|
||||
public class GatewayStatusController(GatewayReadinessStore readinessStore) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public ActionResult<GatewayReadinessState> GetHealthStatus()
|
||||
{
|
||||
return Ok(readinessStore.Current);
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.RateLimiting;
|
||||
using DysonNetwork.Gateway.Health;
|
||||
using DysonNetwork.Shared.Http;
|
||||
using Yarp.ReverseProxy.Configuration;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.SystemTextJson;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddServiceDefaults();
|
||||
|
||||
builder.ConfigureAppKestrel(builder.Configuration, maxRequestBodySize: long.MaxValue, enableGrpc: false);
|
||||
|
||||
builder.Services.AddSingleton<GatewayReadinessStore>();
|
||||
builder.Services.AddHostedService<GatewayHealthAggregator>();
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.SetIsOriginAllowed(origin => true)
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials()
|
||||
.WithExposedHeaders("X-Total", "X-NotReady");
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.AddRateLimiter(options =>
|
||||
{
|
||||
options.AddPolicy("fixed", context =>
|
||||
{
|
||||
var ip = context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
||||
|
||||
return RateLimitPartition.GetFixedWindowLimiter(
|
||||
partitionKey: ip,
|
||||
factory: _ => new FixedWindowRateLimiterOptions
|
||||
{
|
||||
PermitLimit = 120, // 120 requests...
|
||||
Window = TimeSpan.FromMinutes(1), // ...per minute per IP
|
||||
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
|
||||
QueueLimit = 10 // allow short bursts instead of instant 503s
|
||||
});
|
||||
});
|
||||
|
||||
options.OnRejected = async (context, token) =>
|
||||
{
|
||||
// Log the rejected IP
|
||||
var logger = context.HttpContext.RequestServices
|
||||
.GetRequiredService<ILoggerFactory>()
|
||||
.CreateLogger("RateLimiter");
|
||||
|
||||
var ip = context.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
||||
logger.LogWarning("Rate limit exceeded for IP: {IP}", ip);
|
||||
|
||||
// Respond to the client
|
||||
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
|
||||
await context.HttpContext.Response.WriteAsync(
|
||||
"Rate limit exceeded. Try again later.", token);
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
var specialRoutes = new[]
|
||||
{
|
||||
new RouteConfig
|
||||
{
|
||||
RouteId = "ring-ws",
|
||||
ClusterId = "ring",
|
||||
Match = new RouteMatch { Path = "/ws" }
|
||||
},
|
||||
new RouteConfig
|
||||
{
|
||||
RouteId = "pass-openid",
|
||||
ClusterId = "pass",
|
||||
Match = new RouteMatch { Path = "/.well-known/openid-configuration" }
|
||||
},
|
||||
new RouteConfig
|
||||
{
|
||||
RouteId = "pass-jwks",
|
||||
ClusterId = "pass",
|
||||
Match = new RouteMatch { Path = "/.well-known/jwks" }
|
||||
},
|
||||
new RouteConfig
|
||||
{
|
||||
RouteId = "sphere-webfinger",
|
||||
ClusterId = "sphere",
|
||||
Match = new RouteMatch { Path = "/.well-known/webfinger" }
|
||||
},
|
||||
new RouteConfig
|
||||
{
|
||||
RouteId = "sphere-activitypub",
|
||||
ClusterId = "sphere",
|
||||
Match = new RouteMatch { Path = "/activitypub/{**catch-all}" }
|
||||
},
|
||||
};
|
||||
|
||||
var apiRoutes = GatewayConstant.ServiceNames.Select(serviceName =>
|
||||
{
|
||||
var apiPath = serviceName switch
|
||||
{
|
||||
_ => $"/{serviceName}"
|
||||
};
|
||||
return new RouteConfig
|
||||
{
|
||||
RouteId = $"{serviceName}-api",
|
||||
ClusterId = serviceName,
|
||||
Match = new RouteMatch { Path = $"{apiPath}/{{**catch-all}}" },
|
||||
Transforms =
|
||||
[
|
||||
new Dictionary<string, string> { { "PathRemovePrefix", apiPath } },
|
||||
new Dictionary<string, string> { { "PathPrefix", "/api" } }
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
var swaggerRoutes = GatewayConstant.ServiceNames.Select(serviceName => new RouteConfig
|
||||
{
|
||||
RouteId = $"{serviceName}-swagger",
|
||||
ClusterId = serviceName,
|
||||
Match = new RouteMatch { Path = $"/swagger/{serviceName}/{{**catch-all}}" },
|
||||
Transforms =
|
||||
[
|
||||
new Dictionary<string, string> { { "PathRemovePrefix", $"/swagger/{serviceName}" } },
|
||||
new Dictionary<string, string> { { "PathPrefix", "/swagger" } }
|
||||
]
|
||||
});
|
||||
|
||||
var routes = specialRoutes.Concat(apiRoutes).Concat(swaggerRoutes).ToArray();
|
||||
|
||||
var clusters = GatewayConstant.ServiceNames.Select(serviceName => new ClusterConfig
|
||||
{
|
||||
ClusterId = serviceName,
|
||||
HealthCheck = new HealthCheckConfig
|
||||
{
|
||||
Active = new ActiveHealthCheckConfig
|
||||
{
|
||||
Enabled = true,
|
||||
Interval = TimeSpan.FromSeconds(10),
|
||||
Timeout = TimeSpan.FromSeconds(5),
|
||||
Path = "/health"
|
||||
},
|
||||
Passive = new PassiveHealthCheckConfig
|
||||
{
|
||||
Enabled = true
|
||||
}
|
||||
},
|
||||
Destinations = new Dictionary<string, DestinationConfig>
|
||||
{
|
||||
{ "destination1", new DestinationConfig { Address = $"http://{serviceName}" } }
|
||||
}
|
||||
}).ToArray();
|
||||
|
||||
builder.Services
|
||||
.AddReverseProxy()
|
||||
.LoadFromMemory(routes, clusters)
|
||||
.AddServiceDiscoveryDestinationResolver();
|
||||
|
||||
builder.Services.AddControllers().AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals;
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
|
||||
options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower;
|
||||
|
||||
options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
var forwardedHeadersOptions = new ForwardedHeadersOptions
|
||||
{
|
||||
ForwardedHeaders = ForwardedHeaders.All
|
||||
};
|
||||
forwardedHeadersOptions.KnownIPNetworks.Clear();
|
||||
forwardedHeadersOptions.KnownProxies.Clear();
|
||||
app.UseForwardedHeaders(forwardedHeadersOptions);
|
||||
|
||||
app.UseCors();
|
||||
|
||||
app.UseMiddleware<GatewayReadinessMiddleware>();
|
||||
|
||||
app.MapReverseProxy().RequireRateLimiting("fixed");
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using DysonNetwork.Shared.Data;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace DysonNetwork.Gateway;
|
||||
|
||||
[ApiController]
|
||||
[Route("/version")]
|
||||
public class VersionController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public IActionResult Get()
|
||||
{
|
||||
return Ok(new AppVersion
|
||||
{
|
||||
Version = ThisAssembly.AssemblyVersion,
|
||||
Commit = ThisAssembly.GitCommitId,
|
||||
UpdateDate = ThisAssembly.GitCommitDate
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"Cache": {
|
||||
"Serializer": "MessagePack"
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"SiteUrl": "http://localhost:3000",
|
||||
"Client": {
|
||||
"SomeSetting": "SomeValue"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"publicReleaseRefSpec": ["^refs/heads/main$"],
|
||||
"cloudBuild": {
|
||||
"setVersionVariables": true
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,6 @@ public class AppDatabase(
|
||||
public DbSet<SnThinkingThought> ThinkingThoughts { get; set; }
|
||||
public DbSet<SnUnpaidAccount> UnpaidAccounts { get; set; }
|
||||
|
||||
public DbSet<SnWebArticle> WebArticles { get; set; }
|
||||
public DbSet<SnWebFeed> WebFeeds { get; set; }
|
||||
public DbSet<SnWebFeedSubscription> WebFeedSubscriptions { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
optionsBuilder.UseNpgsql(
|
||||
@@ -42,8 +38,6 @@ public class AppDatabase(
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Ignore<SnAccount>();
|
||||
|
||||
modelBuilder.ApplySoftDeleteFilters();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
libkrb5-3 \
|
||||
libgssapi-krb5-2 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
USER app
|
||||
WORKDIR /app
|
||||
EXPOSE 8080
|
||||
|
||||
@@ -7,27 +7,17 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngleSharp" Version="1.4.0" />
|
||||
<PackageReference Include="Google.Protobuf" Version="3.33.2" />
|
||||
<PackageReference Include="Grpc.AspNetCore.Server.ClientFactory" Version="2.76.0" />
|
||||
<PackageReference Include="Grpc.AspNetCore.Server.Reflection" Version="2.76.0" />
|
||||
<PackageReference Include="Grpc.Net.Client" Version="2.76.0" />
|
||||
<PackageReference Include="Grpc.Tools" Version="2.76.0">
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.SemanticKernel" Version="1.68.0" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel" Version="1.67.1" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Ollama" Version="1.66.0-alpha" />
|
||||
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Web" Version="1.66.0-alpha" />
|
||||
<PackageReference Include="Quartz" Version="3.15.1" />
|
||||
<PackageReference Include="Quartz.AspNetCore" Version="3.15.1" />
|
||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.15.1" />
|
||||
<PackageReference Include="System.ServiceModel.Syndication" Version="10.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -38,8 +28,4 @@
|
||||
<Folder Include="Controllers\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Protobuf Remove="..\DysonNetwork.Shared\Proto\**" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,358 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DysonNetwork.Insight;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Models.Embed;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NodaTime;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Insight.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDatabase))]
|
||||
[Migration("20260102075604_AddWebFeed")]
|
||||
partial class AddWebFeed
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.1")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnThinkingSequence", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<bool>("IsPublic")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_public");
|
||||
|
||||
b.Property<long>("PaidToken")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("paid_token");
|
||||
|
||||
b.Property<string>("Topic")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("topic");
|
||||
|
||||
b.Property<long>("TotalToken")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("total_token");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_thinking_sequences");
|
||||
|
||||
b.ToTable("thinking_sequences", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnThinkingThought", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<List<SnCloudFileReferenceObject>>("Files")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("files");
|
||||
|
||||
b.Property<string>("ModelName")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("model_name");
|
||||
|
||||
b.Property<List<SnThinkingMessagePart>>("Parts")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("parts");
|
||||
|
||||
b.Property<int>("Role")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("role");
|
||||
|
||||
b.Property<Guid>("SequenceId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("sequence_id");
|
||||
|
||||
b.Property<long>("TokenCount")
|
||||
.HasColumnType("bigint")
|
||||
.HasColumnName("token_count");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_thinking_thoughts");
|
||||
|
||||
b.HasIndex("SequenceId")
|
||||
.HasDatabaseName("ix_thinking_thoughts_sequence_id");
|
||||
|
||||
b.ToTable("thinking_thoughts", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnUnpaidAccount", b =>
|
||||
{
|
||||
b.Property<Guid>("AccountId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<DateTime>("MarkedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("marked_at");
|
||||
|
||||
b.HasKey("AccountId")
|
||||
.HasName("pk_unpaid_accounts");
|
||||
|
||||
b.ToTable("unpaid_accounts", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnWebArticle", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("Author")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("author");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("content");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<Guid>("FeedId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("feed_id");
|
||||
|
||||
b.Property<Dictionary<string, object>>("Meta")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("meta");
|
||||
|
||||
b.Property<LinkEmbed>("Preview")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("preview");
|
||||
|
||||
b.Property<DateTime?>("PublishedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("published_at");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("title");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("url");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_web_articles");
|
||||
|
||||
b.HasIndex("FeedId")
|
||||
.HasDatabaseName("ix_web_articles_feed_id");
|
||||
|
||||
b.ToTable("web_articles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnWebFeed", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<WebFeedConfig>("Config")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("config");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<LinkEmbed>("Preview")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("preview");
|
||||
|
||||
b.Property<Guid>("PublisherId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("publisher_id");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("title");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("url");
|
||||
|
||||
b.Property<string>("VerificationKey")
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("verification_key");
|
||||
|
||||
b.Property<Instant?>("VerifiedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("verified_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_web_feeds");
|
||||
|
||||
b.ToTable("web_feeds", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnWebFeedSubscription", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<Guid>("FeedId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("feed_id");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_web_feed_subscriptions");
|
||||
|
||||
b.HasIndex("FeedId")
|
||||
.HasDatabaseName("ix_web_feed_subscriptions_feed_id");
|
||||
|
||||
b.ToTable("web_feed_subscriptions", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnThinkingThought", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnThinkingSequence", "Sequence")
|
||||
.WithMany()
|
||||
.HasForeignKey("SequenceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_thinking_thoughts_thinking_sequences_sequence_id");
|
||||
|
||||
b.Navigation("Sequence");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnWebArticle", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnWebFeed", "Feed")
|
||||
.WithMany("Articles")
|
||||
.HasForeignKey("FeedId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_web_articles_web_feeds_feed_id");
|
||||
|
||||
b.Navigation("Feed");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnWebFeedSubscription", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnWebFeed", "Feed")
|
||||
.WithMany()
|
||||
.HasForeignKey("FeedId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_web_feed_subscriptions_web_feeds_feed_id");
|
||||
|
||||
b.Navigation("Feed");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnWebFeed", b =>
|
||||
{
|
||||
b.Navigation("Articles");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Models.Embed;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Insight.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddWebFeed : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "web_feeds",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
url = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: false),
|
||||
title = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
description = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: true),
|
||||
verified_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
verification_key = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: true),
|
||||
preview = table.Column<LinkEmbed>(type: "jsonb", nullable: true),
|
||||
config = table.Column<WebFeedConfig>(type: "jsonb", nullable: false),
|
||||
publisher_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_web_feeds", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "web_articles",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
title = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
url = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: false),
|
||||
author = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
preview = table.Column<LinkEmbed>(type: "jsonb", nullable: true),
|
||||
content = table.Column<string>(type: "text", nullable: true),
|
||||
published_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
feed_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_web_articles", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_web_articles_web_feeds_feed_id",
|
||||
column: x => x.feed_id,
|
||||
principalTable: "web_feeds",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "web_feed_subscriptions",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
feed_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_web_feed_subscriptions", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_web_feed_subscriptions_web_feeds_feed_id",
|
||||
column: x => x.feed_id,
|
||||
principalTable: "web_feeds",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_web_articles_feed_id",
|
||||
table: "web_articles",
|
||||
column: "feed_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_web_feed_subscriptions_feed_id",
|
||||
table: "web_feed_subscriptions",
|
||||
column: "feed_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "web_articles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "web_feed_subscriptions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "web_feeds");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using DysonNetwork.Insight;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Models.Embed;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
@@ -21,7 +20,7 @@ namespace DysonNetwork.Insight.Migrations
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.1")
|
||||
.HasAnnotation("ProductVersion", "9.0.11")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
@@ -144,171 +143,6 @@ namespace DysonNetwork.Insight.Migrations
|
||||
b.ToTable("unpaid_accounts", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnWebArticle", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("Author")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("author");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("content");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<Guid>("FeedId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("feed_id");
|
||||
|
||||
b.Property<Dictionary<string, object>>("Meta")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("meta");
|
||||
|
||||
b.Property<LinkEmbed>("Preview")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("preview");
|
||||
|
||||
b.Property<DateTime?>("PublishedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("published_at");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("title");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("url");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_web_articles");
|
||||
|
||||
b.HasIndex("FeedId")
|
||||
.HasDatabaseName("ix_web_articles_feed_id");
|
||||
|
||||
b.ToTable("web_articles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnWebFeed", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<WebFeedConfig>("Config")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("config");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<LinkEmbed>("Preview")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("preview");
|
||||
|
||||
b.Property<Guid>("PublisherId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("publisher_id");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("title");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("url");
|
||||
|
||||
b.Property<string>("VerificationKey")
|
||||
.HasMaxLength(8192)
|
||||
.HasColumnType("character varying(8192)")
|
||||
.HasColumnName("verification_key");
|
||||
|
||||
b.Property<Instant?>("VerifiedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("verified_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_web_feeds");
|
||||
|
||||
b.ToTable("web_feeds", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnWebFeedSubscription", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<Guid>("FeedId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("feed_id");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_web_feed_subscriptions");
|
||||
|
||||
b.HasIndex("FeedId")
|
||||
.HasDatabaseName("ix_web_feed_subscriptions_feed_id");
|
||||
|
||||
b.ToTable("web_feed_subscriptions", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnThinkingThought", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnThinkingSequence", "Sequence")
|
||||
@@ -320,35 +154,6 @@ namespace DysonNetwork.Insight.Migrations
|
||||
|
||||
b.Navigation("Sequence");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnWebArticle", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnWebFeed", "Feed")
|
||||
.WithMany("Articles")
|
||||
.HasForeignKey("FeedId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_web_articles_web_feeds_feed_id");
|
||||
|
||||
b.Navigation("Feed");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnWebFeedSubscription", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnWebFeed", "Feed")
|
||||
.WithMany()
|
||||
.HasForeignKey("FeedId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_web_feed_subscriptions_web_feeds_feed_id");
|
||||
|
||||
b.Navigation("Feed");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnWebFeed", b =>
|
||||
{
|
||||
b.Navigation("Articles");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using DysonNetwork.Insight;
|
||||
using DysonNetwork.Insight.Reader;
|
||||
using DysonNetwork.Insight.Startup;
|
||||
using DysonNetwork.Shared.Auth;
|
||||
using DysonNetwork.Shared.Http;
|
||||
@@ -8,7 +7,9 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddServiceDefaults();
|
||||
builder.Services.Configure<ServiceRegistrationOptions>(opts => { opts.Name = "insight"; });
|
||||
|
||||
builder.AddServiceDefaults("insight");
|
||||
|
||||
builder.ConfigureAppKestrel(builder.Configuration);
|
||||
|
||||
@@ -20,8 +21,6 @@ builder.Services.AddAppBusinessServices();
|
||||
builder.Services.AddAppScheduledJobs();
|
||||
|
||||
builder.Services.AddDysonAuth();
|
||||
builder.Services.AddAccountService();
|
||||
builder.Services.AddSphereService();
|
||||
builder.Services.AddThinkingServices(builder.Configuration);
|
||||
|
||||
builder.AddSwaggerManifest(
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
using DysonNetwork.Shared.Models.Embed;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using EmbedLinkEmbed = DysonNetwork.Shared.Models.Embed.LinkEmbed;
|
||||
|
||||
namespace DysonNetwork.Insight.Reader;
|
||||
|
||||
public class ScrapedArticle
|
||||
{
|
||||
public EmbedLinkEmbed LinkEmbed { get; set; } = null!;
|
||||
public string? Content { get; set; }
|
||||
|
||||
public Shared.Proto.ScrapedArticle ToProtoValue()
|
||||
{
|
||||
var proto = new Shared.Proto.ScrapedArticle
|
||||
{
|
||||
LinkEmbed = LinkEmbed.ToProtoValue()
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(Content))
|
||||
proto.Content = Content;
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
public static ScrapedArticle FromProtoValue(Shared.Proto.ScrapedArticle proto)
|
||||
{
|
||||
return new ScrapedArticle
|
||||
{
|
||||
LinkEmbed = EmbedLinkEmbed.FromProtoValue(proto.LinkEmbed),
|
||||
Content = proto.Content == "" ? null : proto.Content
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Grpc.Core;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DysonNetwork.Insight.Reader;
|
||||
|
||||
public class WebArticleGrpcService(AppDatabase db) : WebArticleService.WebArticleServiceBase
|
||||
{
|
||||
public override async Task<GetWebArticleResponse> GetWebArticle(
|
||||
GetWebArticleRequest request,
|
||||
ServerCallContext context
|
||||
)
|
||||
{
|
||||
if (!Guid.TryParse(request.Id, out var id))
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "invalid id"));
|
||||
|
||||
var article = await db.WebArticles
|
||||
.Include(a => a.Feed)
|
||||
.FirstOrDefaultAsync(a => a.Id == id);
|
||||
|
||||
return article == null
|
||||
? throw new RpcException(new Status(StatusCode.NotFound, "article not found"))
|
||||
: new GetWebArticleResponse { Article = article.ToProtoValue() };
|
||||
}
|
||||
|
||||
public override async Task<GetWebArticleBatchResponse> GetWebArticleBatch(
|
||||
GetWebArticleBatchRequest request,
|
||||
ServerCallContext context
|
||||
)
|
||||
{
|
||||
var ids = request.Ids
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s) && Guid.TryParse(s, out _))
|
||||
.Select(Guid.Parse)
|
||||
.ToList();
|
||||
|
||||
if (ids.Count == 0)
|
||||
return new GetWebArticleBatchResponse();
|
||||
|
||||
var articles = await db.WebArticles
|
||||
.Include(a => a.Feed)
|
||||
.Where(a => ids.Contains(a.Id))
|
||||
.ToListAsync();
|
||||
|
||||
var response = new GetWebArticleBatchResponse();
|
||||
response.Articles.AddRange(articles.Select(a => a.ToProtoValue()));
|
||||
return response;
|
||||
}
|
||||
|
||||
public override async Task<ListWebArticlesResponse> ListWebArticles(
|
||||
ListWebArticlesRequest request,
|
||||
ServerCallContext context
|
||||
)
|
||||
{
|
||||
if (!Guid.TryParse(request.FeedId, out var feedId))
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "invalid feed_id"));
|
||||
|
||||
var query = db.WebArticles
|
||||
.Include(a => a.Feed)
|
||||
.Where(a => a.FeedId == feedId);
|
||||
|
||||
var articles = await query.ToListAsync();
|
||||
|
||||
var response = new ListWebArticlesResponse
|
||||
{
|
||||
TotalSize = articles.Count
|
||||
};
|
||||
response.Articles.AddRange(articles.Select(a => a.ToProtoValue()));
|
||||
return response;
|
||||
}
|
||||
|
||||
public override async Task<GetRecentArticlesResponse> GetRecentArticles(
|
||||
GetRecentArticlesRequest request,
|
||||
ServerCallContext context
|
||||
)
|
||||
{
|
||||
var limit = request.Limit > 0 ? request.Limit : 20;
|
||||
|
||||
var articles = await db.WebArticles
|
||||
.Include(a => a.Feed)
|
||||
.OrderByDescending(a => a.PublishedAt ?? DateTime.MinValue)
|
||||
.ThenByDescending(a => a.CreatedAt)
|
||||
.Take(limit)
|
||||
.ToListAsync();
|
||||
|
||||
var response = new GetRecentArticlesResponse();
|
||||
response.Articles.AddRange(articles.Select(a => a.ToProtoValue()));
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Grpc.Core;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DysonNetwork.Insight.Reader;
|
||||
|
||||
public class WebFeedGrpcService(WebFeedService service, AppDatabase db)
|
||||
: Shared.Proto.WebFeedService.WebFeedServiceBase
|
||||
{
|
||||
public override async Task<GetWebFeedResponse> GetWebFeed(
|
||||
GetWebFeedRequest request,
|
||||
ServerCallContext context
|
||||
)
|
||||
{
|
||||
SnWebFeed? feed = null;
|
||||
|
||||
switch (request.IdentifierCase)
|
||||
{
|
||||
case GetWebFeedRequest.IdentifierOneofCase.Id:
|
||||
if (!string.IsNullOrWhiteSpace(request.Id) && Guid.TryParse(request.Id, out var id))
|
||||
feed = await service.GetFeedAsync(id);
|
||||
break;
|
||||
case GetWebFeedRequest.IdentifierOneofCase.Url:
|
||||
feed = await db.WebFeeds.FirstOrDefaultAsync(f => f.Url == request.Url);
|
||||
break;
|
||||
case GetWebFeedRequest.IdentifierOneofCase.None:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
return feed == null
|
||||
? throw new RpcException(new Status(StatusCode.NotFound, "feed not found"))
|
||||
: new GetWebFeedResponse { Feed = feed.ToProtoValue() };
|
||||
}
|
||||
|
||||
public override async Task<ListWebFeedsResponse> ListWebFeeds(
|
||||
ListWebFeedsRequest request,
|
||||
ServerCallContext context
|
||||
)
|
||||
{
|
||||
if (!Guid.TryParse(request.PublisherId, out var publisherId))
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "invalid publisher_id"));
|
||||
|
||||
var feeds = await service.GetFeedsByPublisherAsync(publisherId);
|
||||
|
||||
var response = new ListWebFeedsResponse
|
||||
{
|
||||
TotalSize = feeds.Count
|
||||
};
|
||||
response.Feeds.AddRange(feeds.Select(f => f.ToProtoValue()));
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Grpc.Core;
|
||||
|
||||
namespace DysonNetwork.Insight.Reader;
|
||||
|
||||
public class WebReaderGrpcService(WebReaderService service) : Shared.Proto.WebReaderService.WebReaderServiceBase
|
||||
{
|
||||
public override async Task<ScrapeArticleResponse> ScrapeArticle(
|
||||
ScrapeArticleRequest request,
|
||||
ServerCallContext context
|
||||
)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Url))
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "url is required"));
|
||||
|
||||
var scrapedArticle = await service.ScrapeArticleAsync(request.Url, context.CancellationToken);
|
||||
return new ScrapeArticleResponse { Article = scrapedArticle.ToProtoValue() };
|
||||
}
|
||||
|
||||
public override async Task<GetLinkPreviewResponse> GetLinkPreview(
|
||||
GetLinkPreviewRequest request,
|
||||
ServerCallContext context
|
||||
)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Url))
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "url is required"));
|
||||
|
||||
var linkEmbed = await service.GetLinkPreviewAsync(
|
||||
request.Url,
|
||||
context.CancellationToken,
|
||||
bypassCache: request.BypassCache
|
||||
);
|
||||
|
||||
return new GetLinkPreviewResponse { Preview = linkEmbed.ToProtoValue() };
|
||||
}
|
||||
|
||||
public override async Task<InvalidateLinkPreviewCacheResponse> InvalidateLinkPreviewCache(
|
||||
InvalidateLinkPreviewCacheRequest request,
|
||||
ServerCallContext context
|
||||
)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Url))
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument, "url is required"));
|
||||
|
||||
await service.InvalidateCacheForUrlAsync(request.Url);
|
||||
|
||||
return new InvalidateLinkPreviewCacheResponse { Success = true };
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using DysonNetwork.Insight.Reader;
|
||||
using DysonNetwork.Shared.Http;
|
||||
|
||||
namespace DysonNetwork.Insight.Startup;
|
||||
@@ -18,11 +17,6 @@ public static class ApplicationConfiguration
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.MapGrpcService<WebReaderGrpcService>();
|
||||
app.MapGrpcService<WebArticleGrpcService>();
|
||||
app.MapGrpcService<WebFeedGrpcService>();
|
||||
app.MapGrpcReflectionService();
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using DysonNetwork.Insight.Reader;
|
||||
using DysonNetwork.Insight.Thought;
|
||||
using Quartz;
|
||||
|
||||
@@ -19,13 +18,6 @@ public static class ScheduledJobsConfiguration
|
||||
.WithIntervalInMinutes(5)
|
||||
.RepeatForever())
|
||||
);
|
||||
|
||||
q.AddJob<WebFeedScraperJob>(opts => opts.WithIdentity("WebFeedScraper").StoreDurably());
|
||||
q.AddTrigger(opts => opts
|
||||
.ForJob("WebFeedScraper")
|
||||
.WithIdentity("WebFeedScraperTrigger")
|
||||
.WithCronSchedule("0 0 0 * * ?")
|
||||
);
|
||||
});
|
||||
services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Insight.Reader;
|
||||
using DysonNetwork.Insight.Thought;
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Registry;
|
||||
@@ -12,65 +11,60 @@ namespace DysonNetwork.Insight.Startup;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
extension(IServiceCollection services)
|
||||
public static IServiceCollection AddAppServices(this IServiceCollection services)
|
||||
{
|
||||
public IServiceCollection AddAppServices()
|
||||
services.AddDbContext<AppDatabase>();
|
||||
services.AddHttpContextAccessor();
|
||||
|
||||
services.AddHttpClient();
|
||||
|
||||
// Register gRPC services
|
||||
services.AddGrpc(options =>
|
||||
{
|
||||
services.AddDbContext<AppDatabase>();
|
||||
services.AddHttpContextAccessor();
|
||||
options.EnableDetailedErrors = true; // Will be adjusted in Program.cs
|
||||
options.MaxReceiveMessageSize = 16 * 1024 * 1024; // 16MB
|
||||
options.MaxSendMessageSize = 16 * 1024 * 1024; // 16MB
|
||||
});
|
||||
services.AddGrpcReflection();
|
||||
|
||||
services.AddHttpClient();
|
||||
// Register gRPC services
|
||||
|
||||
// Register gRPC services
|
||||
services.AddGrpc(options =>
|
||||
{
|
||||
options.EnableDetailedErrors = true; // Will be adjusted in Program.cs
|
||||
options.MaxReceiveMessageSize = 16 * 1024 * 1024; // 16MB
|
||||
options.MaxSendMessageSize = 16 * 1024 * 1024; // 16MB
|
||||
});
|
||||
services.AddGrpcReflection();
|
||||
|
||||
// Register gRPC services
|
||||
|
||||
// Register OIDC services
|
||||
services.AddControllers().AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals;
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
|
||||
options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower;
|
||||
|
||||
options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public IServiceCollection AddAppAuthentication()
|
||||
// Register OIDC services
|
||||
services.AddControllers().AddJsonOptions(options =>
|
||||
{
|
||||
services.AddAuthorization();
|
||||
return services;
|
||||
}
|
||||
options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals;
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
|
||||
options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower;
|
||||
|
||||
public IServiceCollection AddAppFlushHandlers()
|
||||
{
|
||||
services.AddSingleton<FlushBufferService>();
|
||||
options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
return services;
|
||||
}
|
||||
|
||||
public IServiceCollection AddAppBusinessServices()
|
||||
{
|
||||
return services;
|
||||
}
|
||||
public static IServiceCollection AddAppAuthentication(this IServiceCollection services)
|
||||
{
|
||||
services.AddAuthorization();
|
||||
return services;
|
||||
}
|
||||
|
||||
public IServiceCollection AddThinkingServices(IConfiguration configuration)
|
||||
{
|
||||
services.AddSingleton<ThoughtProvider>();
|
||||
services.AddScoped<ThoughtService>();
|
||||
services.AddScoped<WebFeedService>();
|
||||
services.AddScoped<WebReaderService>();
|
||||
public static IServiceCollection AddAppFlushHandlers(this IServiceCollection services)
|
||||
{
|
||||
services.AddSingleton<FlushBufferService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddAppBusinessServices(this IServiceCollection services)
|
||||
{
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddThinkingServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddSingleton<ThoughtProvider>();
|
||||
services.AddScoped<ThoughtService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.SemanticKernel;
|
||||
|
||||
namespace DysonNetwork.Insight.Thought.Plugins;
|
||||
@@ -23,6 +24,6 @@ public class SnAccountKernelPlugin(
|
||||
var request = new LookupAccountBatchRequest();
|
||||
request.Names.Add(username);
|
||||
var response = await accountClient.LookupAccountBatchAsync(request);
|
||||
return response.Accounts.Count == 0 ? null : SnAccount.FromProtoValue(response.Accounts[0]);
|
||||
return response.Accounts.IsNullOrEmpty() ? null : SnAccount.FromProtoValue(response.Accounts[0]);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,6 @@ public class ThoughtProvider
|
||||
private readonly PostService.PostServiceClient _postClient;
|
||||
private readonly AccountService.AccountServiceClient _accountClient;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<ThoughtProvider> _logger;
|
||||
|
||||
private readonly Dictionary<string, Kernel> _kernels = new();
|
||||
private readonly Dictionary<string, string> _serviceProviders = new();
|
||||
@@ -38,11 +37,9 @@ public class ThoughtProvider
|
||||
public ThoughtProvider(
|
||||
IConfiguration configuration,
|
||||
PostService.PostServiceClient postServiceClient,
|
||||
AccountService.AccountServiceClient accountServiceClient,
|
||||
ILogger<ThoughtProvider> logger
|
||||
AccountService.AccountServiceClient accountServiceClient
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_postClient = postServiceClient;
|
||||
_accountClient = accountServiceClient;
|
||||
_configuration = configuration;
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_insight;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60"
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_insight;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60",
|
||||
"Registrar": "127.0.0.1:2379",
|
||||
"Cache": "127.0.0.1:6379",
|
||||
"Queue": "127.0.0.1:4222"
|
||||
},
|
||||
"KnownProxies": [
|
||||
"127.0.0.1",
|
||||
|
||||
5
DysonNetwork.Messager/.gitignore
vendored
5
DysonNetwork.Messager/.gitignore
vendored
@@ -1,5 +0,0 @@
|
||||
Keys
|
||||
Uploads
|
||||
DataProtection-Keys
|
||||
|
||||
.DS_Store
|
||||
@@ -1,139 +0,0 @@
|
||||
using System.Linq.Expressions;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.EntityFrameworkCore.Query;
|
||||
using NodaTime;
|
||||
using Quartz;
|
||||
|
||||
namespace DysonNetwork.Messager;
|
||||
|
||||
public class AppDatabase(
|
||||
DbContextOptions<AppDatabase> options,
|
||||
IConfiguration configuration
|
||||
) : DbContext(options)
|
||||
{
|
||||
public DbSet<SnChatRoom> ChatRooms { get; set; } = null!;
|
||||
public DbSet<SnChatMember> ChatMembers { get; set; } = null!;
|
||||
public DbSet<SnChatMessage> ChatMessages { get; set; } = null!;
|
||||
public DbSet<SnRealtimeCall> ChatRealtimeCall { get; set; } = null!;
|
||||
public DbSet<SnChatReaction> ChatReactions { get; set; } = null!;
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
optionsBuilder.UseNpgsql(
|
||||
configuration.GetConnectionString("App"),
|
||||
opt => opt
|
||||
.ConfigureDataSource(optSource => optSource.EnableDynamicJson())
|
||||
.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)
|
||||
.UseNodaTime()
|
||||
).UseSnakeCaseNamingConvention();
|
||||
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<SnChatMember>()
|
||||
.HasKey(pm => new { pm.Id });
|
||||
modelBuilder.Entity<SnChatMember>()
|
||||
.HasAlternateKey(pm => new { pm.ChatRoomId, pm.AccountId });
|
||||
modelBuilder.Entity<SnChatMember>()
|
||||
.HasOne(pm => pm.ChatRoom)
|
||||
.WithMany(p => p.Members)
|
||||
.HasForeignKey(pm => pm.ChatRoomId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<SnChatMessage>()
|
||||
.HasOne(m => m.ForwardedMessage)
|
||||
.WithMany()
|
||||
.HasForeignKey(m => m.ForwardedMessageId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
modelBuilder.Entity<SnChatMessage>()
|
||||
.HasOne(m => m.RepliedMessage)
|
||||
.WithMany()
|
||||
.HasForeignKey(m => m.RepliedMessageId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
modelBuilder.Entity<SnRealtimeCall>()
|
||||
.HasOne(m => m.Room)
|
||||
.WithMany()
|
||||
.HasForeignKey(m => m.RoomId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<SnRealtimeCall>()
|
||||
.HasOne(m => m.Sender)
|
||||
.WithMany()
|
||||
.HasForeignKey(m => m.SenderId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.ApplySoftDeleteFilters();
|
||||
}
|
||||
|
||||
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.ApplyAuditableAndSoftDelete();
|
||||
return await base.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public class AppDatabaseRecyclingJob(AppDatabase db, ILogger<AppDatabaseRecyclingJob> logger) : IJob
|
||||
{
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
logger.LogInformation("Deleting soft-deleted records...");
|
||||
|
||||
var threshold = now - Duration.FromDays(7);
|
||||
|
||||
var entityTypes = db.Model.GetEntityTypes()
|
||||
.Where(t => typeof(ModelBase).IsAssignableFrom(t.ClrType) && t.ClrType != typeof(ModelBase))
|
||||
.Select(t => t.ClrType);
|
||||
|
||||
foreach (var entityType in entityTypes)
|
||||
{
|
||||
var set = (IQueryable)db.GetType().GetMethod(nameof(DbContext.Set), Type.EmptyTypes)!
|
||||
.MakeGenericMethod(entityType).Invoke(db, null)!;
|
||||
var parameter = Expression.Parameter(entityType, "e");
|
||||
var property = Expression.Property(parameter, nameof(ModelBase.DeletedAt));
|
||||
var condition = Expression.LessThan(property, Expression.Constant(threshold, typeof(Instant?)));
|
||||
var notNull = Expression.NotEqual(property, Expression.Constant(null, typeof(Instant?)));
|
||||
var finalCondition = Expression.AndAlso(notNull, condition);
|
||||
var lambda = Expression.Lambda(finalCondition, parameter);
|
||||
|
||||
var queryable = set.Provider.CreateQuery(
|
||||
Expression.Call(
|
||||
typeof(Queryable),
|
||||
"Where",
|
||||
[entityType],
|
||||
set.Expression,
|
||||
Expression.Quote(lambda)
|
||||
)
|
||||
);
|
||||
|
||||
var toListAsync = typeof(EntityFrameworkQueryableExtensions)
|
||||
.GetMethod(nameof(EntityFrameworkQueryableExtensions.ToListAsync))!
|
||||
.MakeGenericMethod(entityType);
|
||||
|
||||
var items = await (dynamic)toListAsync.Invoke(null, [queryable, CancellationToken.None])!;
|
||||
db.RemoveRange(items);
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public class AppDatabaseFactory : IDesignTimeDbContextFactory<AppDatabase>
|
||||
{
|
||||
public AppDatabase CreateDbContext(string[] args)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddJsonFile("appsettings.json")
|
||||
.Build();
|
||||
|
||||
var optionsBuilder = new DbContextOptionsBuilder<AppDatabase>();
|
||||
return new AppDatabase(optionsBuilder.Options, configuration);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
# Stage 1: Base runtime image
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
libkrb5-3 \
|
||||
libgssapi-krb5-2 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
USER $APP_UID
|
||||
WORKDIR /app
|
||||
EXPOSE 8080
|
||||
EXPOSE 8081
|
||||
|
||||
# Stage 2: Build .NET application
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
WORKDIR /src
|
||||
|
||||
# First copy only the solution and project files to restore packages
|
||||
COPY ["DysonNetwork.Messager/DysonNetwork.Messager.csproj", "DysonNetwork.Messager/"]
|
||||
COPY ["DysonNetwork.Shared/DysonNetwork.Shared.csproj", "DysonNetwork.Shared/"]
|
||||
|
||||
# Restore packages
|
||||
RUN dotnet restore "DysonNetwork.Messager/DysonNetwork.Messager.csproj"
|
||||
|
||||
# Copy everything else and build
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
WORKDIR "/src/DysonNetwork.Messager"
|
||||
RUN dotnet build "DysonNetwork.Messager.csproj" -c $BUILD_CONFIGURATION -o /app/build
|
||||
|
||||
# Stage 4: Publish
|
||||
FROM build AS publish
|
||||
ARG BUILD_CONFIGURATION=Release
|
||||
RUN dotnet publish "DysonNetwork.Messager.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
|
||||
|
||||
# Final stage: Runtime
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "DysonNetwork.Messager.dll"]
|
||||
@@ -1,40 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<UserSecretsId>cfdec342-d2f2-4a86-800b-93f0a0e4abde</UserSecretsId>
|
||||
<SatelliteResourceLanguages>en-US;zh-Hans</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.76.0" />
|
||||
<PackageReference Include="Livekit.Server.Sdk.Dotnet" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NodaTime" Version="3.2.3" />
|
||||
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" />
|
||||
<PackageReference Include="Quartz" Version="3.15.1" />
|
||||
<PackageReference Include="Quartz.AspNetCore" Version="3.15.1" />
|
||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.15.1" />
|
||||
<PackageReference Include="StackExchange.Redis.Extensions.AspNetCore" Version="11.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="10.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\.dockerignore">
|
||||
<Link>.dockerignore</Link>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DysonNetwork.Shared\DysonNetwork.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,478 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DysonNetwork.Messager;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NodaTime;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Messager.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDatabase))]
|
||||
[Migration("20260101140847_InitialMigration")]
|
||||
partial class InitialMigration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.1")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMember", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Instant?>("BreakUntil")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("break_until");
|
||||
|
||||
b.Property<Guid>("ChatRoomId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("chat_room_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<Guid?>("InvitedById")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("invited_by_id");
|
||||
|
||||
b.Property<Instant?>("JoinedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("joined_at");
|
||||
|
||||
b.Property<Instant?>("LastReadAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_read_at");
|
||||
|
||||
b.Property<Instant?>("LeaveAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("leave_at");
|
||||
|
||||
b.Property<string>("Nick")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("nick");
|
||||
|
||||
b.Property<int>("Notify")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("notify");
|
||||
|
||||
b.Property<ChatTimeoutCause>("TimeoutCause")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("timeout_cause");
|
||||
|
||||
b.Property<Instant?>("TimeoutUntil")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("timeout_until");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_chat_members");
|
||||
|
||||
b.HasAlternateKey("ChatRoomId", "AccountId")
|
||||
.HasName("ak_chat_members_chat_room_id_account_id");
|
||||
|
||||
b.HasIndex("InvitedById")
|
||||
.HasDatabaseName("ix_chat_members_invited_by_id");
|
||||
|
||||
b.ToTable("chat_members", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMessage", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<List<SnCloudFileReferenceObject>>("Attachments")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("attachments");
|
||||
|
||||
b.Property<Guid>("ChatRoomId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("chat_room_id");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("content");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<Instant?>("EditedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("edited_at");
|
||||
|
||||
b.Property<Guid?>("ForwardedMessageId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("forwarded_message_id");
|
||||
|
||||
b.PrimitiveCollection<string>("MembersMentioned")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("members_mentioned");
|
||||
|
||||
b.Property<Dictionary<string, object>>("Meta")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("meta");
|
||||
|
||||
b.Property<string>("Nonce")
|
||||
.IsRequired()
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("character varying(36)")
|
||||
.HasColumnName("nonce");
|
||||
|
||||
b.Property<Guid?>("RepliedMessageId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("replied_message_id");
|
||||
|
||||
b.Property<Guid>("SenderId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("sender_id");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("type");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_chat_messages");
|
||||
|
||||
b.HasIndex("ChatRoomId")
|
||||
.HasDatabaseName("ix_chat_messages_chat_room_id");
|
||||
|
||||
b.HasIndex("ForwardedMessageId")
|
||||
.HasDatabaseName("ix_chat_messages_forwarded_message_id");
|
||||
|
||||
b.HasIndex("RepliedMessageId")
|
||||
.HasDatabaseName("ix_chat_messages_replied_message_id");
|
||||
|
||||
b.HasIndex("SenderId")
|
||||
.HasDatabaseName("ix_chat_messages_sender_id");
|
||||
|
||||
b.ToTable("chat_messages", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatReaction", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<int>("Attitude")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("attitude");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<Guid>("MessageId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("message_id");
|
||||
|
||||
b.Property<Guid>("SenderId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("sender_id");
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("symbol");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_chat_reactions");
|
||||
|
||||
b.HasIndex("MessageId")
|
||||
.HasDatabaseName("ix_chat_reactions_message_id");
|
||||
|
||||
b.HasIndex("SenderId")
|
||||
.HasDatabaseName("ix_chat_reactions_sender_id");
|
||||
|
||||
b.ToTable("chat_reactions", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatRoom", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid?>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<SnCloudFileReferenceObject>("Background")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("background");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<bool>("IsCommunity")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_community");
|
||||
|
||||
b.Property<bool>("IsPublic")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_public");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<SnCloudFileReferenceObject>("Picture")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("picture");
|
||||
|
||||
b.Property<Guid?>("RealmId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("realm_id");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("type");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_chat_rooms");
|
||||
|
||||
b.ToTable("chat_rooms", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnRealtimeCall", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<Instant?>("EndedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("ended_at");
|
||||
|
||||
b.Property<string>("ProviderName")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("provider_name");
|
||||
|
||||
b.Property<Guid>("RoomId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("room_id");
|
||||
|
||||
b.Property<Guid>("SenderId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("sender_id");
|
||||
|
||||
b.Property<string>("SessionId")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("session_id");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<string>("UpstreamConfigJson")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("upstream");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_chat_realtime_call");
|
||||
|
||||
b.HasIndex("RoomId")
|
||||
.HasDatabaseName("ix_chat_realtime_call_room_id");
|
||||
|
||||
b.HasIndex("SenderId")
|
||||
.HasDatabaseName("ix_chat_realtime_call_sender_id");
|
||||
|
||||
b.ToTable("chat_realtime_call", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMember", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatRoom", "ChatRoom")
|
||||
.WithMany("Members")
|
||||
.HasForeignKey("ChatRoomId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_chat_members_chat_rooms_chat_room_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatMember", "InvitedBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("InvitedById")
|
||||
.HasConstraintName("fk_chat_members_chat_members_invited_by_id");
|
||||
|
||||
b.Navigation("ChatRoom");
|
||||
|
||||
b.Navigation("InvitedBy");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMessage", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatRoom", "ChatRoom")
|
||||
.WithMany()
|
||||
.HasForeignKey("ChatRoomId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatMessage", "ForwardedMessage")
|
||||
.WithMany()
|
||||
.HasForeignKey("ForwardedMessageId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatMessage", "RepliedMessage")
|
||||
.WithMany()
|
||||
.HasForeignKey("RepliedMessageId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.HasConstraintName("fk_chat_messages_chat_messages_replied_message_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatMember", "Sender")
|
||||
.WithMany("Messages")
|
||||
.HasForeignKey("SenderId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_chat_messages_chat_members_sender_id");
|
||||
|
||||
b.Navigation("ChatRoom");
|
||||
|
||||
b.Navigation("ForwardedMessage");
|
||||
|
||||
b.Navigation("RepliedMessage");
|
||||
|
||||
b.Navigation("Sender");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatReaction", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatMessage", "Message")
|
||||
.WithMany("Reactions")
|
||||
.HasForeignKey("MessageId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_chat_reactions_chat_messages_message_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatMember", "Sender")
|
||||
.WithMany("Reactions")
|
||||
.HasForeignKey("SenderId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_chat_reactions_chat_members_sender_id");
|
||||
|
||||
b.Navigation("Message");
|
||||
|
||||
b.Navigation("Sender");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnRealtimeCall", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatRoom", "Room")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoomId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatMember", "Sender")
|
||||
.WithMany()
|
||||
.HasForeignKey("SenderId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_chat_realtime_call_chat_members_sender_id");
|
||||
|
||||
b.Navigation("Room");
|
||||
|
||||
b.Navigation("Sender");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMember", b =>
|
||||
{
|
||||
b.Navigation("Messages");
|
||||
|
||||
b.Navigation("Reactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMessage", b =>
|
||||
{
|
||||
b.Navigation("Reactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatRoom", b =>
|
||||
{
|
||||
b.Navigation("Members");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Messager.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialMigration : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "chat_rooms",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
is_community = table.Column<bool>(type: "boolean", nullable: false),
|
||||
is_public = table.Column<bool>(type: "boolean", nullable: false),
|
||||
picture = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
background = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
realm_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_chat_rooms", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "chat_members",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
chat_room_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
nick = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
notify = table.Column<int>(type: "integer", nullable: false),
|
||||
last_read_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
joined_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
leave_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
invited_by_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
break_until = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
timeout_until = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
timeout_cause = table.Column<ChatTimeoutCause>(type: "jsonb", nullable: true),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_chat_members", x => x.id);
|
||||
table.UniqueConstraint("ak_chat_members_chat_room_id_account_id", x => new { x.chat_room_id, x.account_id });
|
||||
table.ForeignKey(
|
||||
name: "fk_chat_members_chat_members_invited_by_id",
|
||||
column: x => x.invited_by_id,
|
||||
principalTable: "chat_members",
|
||||
principalColumn: "id");
|
||||
table.ForeignKey(
|
||||
name: "fk_chat_members_chat_rooms_chat_room_id",
|
||||
column: x => x.chat_room_id,
|
||||
principalTable: "chat_rooms",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "chat_messages",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
type = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
content = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
members_mentioned = table.Column<string>(type: "jsonb", nullable: true),
|
||||
nonce = table.Column<string>(type: "character varying(36)", maxLength: 36, nullable: false),
|
||||
edited_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
attachments = table.Column<List<SnCloudFileReferenceObject>>(type: "jsonb", nullable: false),
|
||||
replied_message_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
forwarded_message_id = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
sender_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
chat_room_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_chat_messages", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_chat_messages_chat_members_sender_id",
|
||||
column: x => x.sender_id,
|
||||
principalTable: "chat_members",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_chat_messages_chat_messages_forwarded_message_id",
|
||||
column: x => x.forwarded_message_id,
|
||||
principalTable: "chat_messages",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "fk_chat_messages_chat_messages_replied_message_id",
|
||||
column: x => x.replied_message_id,
|
||||
principalTable: "chat_messages",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "fk_chat_messages_chat_rooms_chat_room_id",
|
||||
column: x => x.chat_room_id,
|
||||
principalTable: "chat_rooms",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "chat_realtime_call",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
ended_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
sender_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
room_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
provider_name = table.Column<string>(type: "text", nullable: true),
|
||||
session_id = table.Column<string>(type: "text", nullable: true),
|
||||
upstream = table.Column<string>(type: "jsonb", nullable: true),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_chat_realtime_call", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_chat_realtime_call_chat_members_sender_id",
|
||||
column: x => x.sender_id,
|
||||
principalTable: "chat_members",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_chat_realtime_call_chat_rooms_room_id",
|
||||
column: x => x.room_id,
|
||||
principalTable: "chat_rooms",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "chat_reactions",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
message_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
sender_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
symbol = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
||||
attitude = table.Column<int>(type: "integer", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_chat_reactions", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_chat_reactions_chat_members_sender_id",
|
||||
column: x => x.sender_id,
|
||||
principalTable: "chat_members",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "fk_chat_reactions_chat_messages_message_id",
|
||||
column: x => x.message_id,
|
||||
principalTable: "chat_messages",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_chat_members_invited_by_id",
|
||||
table: "chat_members",
|
||||
column: "invited_by_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_chat_messages_chat_room_id",
|
||||
table: "chat_messages",
|
||||
column: "chat_room_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_chat_messages_forwarded_message_id",
|
||||
table: "chat_messages",
|
||||
column: "forwarded_message_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_chat_messages_replied_message_id",
|
||||
table: "chat_messages",
|
||||
column: "replied_message_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_chat_messages_sender_id",
|
||||
table: "chat_messages",
|
||||
column: "sender_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_chat_reactions_message_id",
|
||||
table: "chat_reactions",
|
||||
column: "message_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_chat_reactions_sender_id",
|
||||
table: "chat_reactions",
|
||||
column: "sender_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_chat_realtime_call_room_id",
|
||||
table: "chat_realtime_call",
|
||||
column: "room_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_chat_realtime_call_sender_id",
|
||||
table: "chat_realtime_call",
|
||||
column: "sender_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "chat_reactions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "chat_realtime_call");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "chat_messages");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "chat_members");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "chat_rooms");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,475 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DysonNetwork.Messager;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NodaTime;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Messager.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDatabase))]
|
||||
partial class AppDatabaseModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.1")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMember", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<Instant?>("BreakUntil")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("break_until");
|
||||
|
||||
b.Property<Guid>("ChatRoomId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("chat_room_id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<Guid?>("InvitedById")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("invited_by_id");
|
||||
|
||||
b.Property<Instant?>("JoinedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("joined_at");
|
||||
|
||||
b.Property<Instant?>("LastReadAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("last_read_at");
|
||||
|
||||
b.Property<Instant?>("LeaveAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("leave_at");
|
||||
|
||||
b.Property<string>("Nick")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("nick");
|
||||
|
||||
b.Property<int>("Notify")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("notify");
|
||||
|
||||
b.Property<ChatTimeoutCause>("TimeoutCause")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("timeout_cause");
|
||||
|
||||
b.Property<Instant?>("TimeoutUntil")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("timeout_until");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_chat_members");
|
||||
|
||||
b.HasAlternateKey("ChatRoomId", "AccountId")
|
||||
.HasName("ak_chat_members_chat_room_id_account_id");
|
||||
|
||||
b.HasIndex("InvitedById")
|
||||
.HasDatabaseName("ix_chat_members_invited_by_id");
|
||||
|
||||
b.ToTable("chat_members", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMessage", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<List<SnCloudFileReferenceObject>>("Attachments")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("attachments");
|
||||
|
||||
b.Property<Guid>("ChatRoomId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("chat_room_id");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("content");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<Instant?>("EditedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("edited_at");
|
||||
|
||||
b.Property<Guid?>("ForwardedMessageId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("forwarded_message_id");
|
||||
|
||||
b.PrimitiveCollection<string>("MembersMentioned")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("members_mentioned");
|
||||
|
||||
b.Property<Dictionary<string, object>>("Meta")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("meta");
|
||||
|
||||
b.Property<string>("Nonce")
|
||||
.IsRequired()
|
||||
.HasMaxLength(36)
|
||||
.HasColumnType("character varying(36)")
|
||||
.HasColumnName("nonce");
|
||||
|
||||
b.Property<Guid?>("RepliedMessageId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("replied_message_id");
|
||||
|
||||
b.Property<Guid>("SenderId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("sender_id");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("type");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_chat_messages");
|
||||
|
||||
b.HasIndex("ChatRoomId")
|
||||
.HasDatabaseName("ix_chat_messages_chat_room_id");
|
||||
|
||||
b.HasIndex("ForwardedMessageId")
|
||||
.HasDatabaseName("ix_chat_messages_forwarded_message_id");
|
||||
|
||||
b.HasIndex("RepliedMessageId")
|
||||
.HasDatabaseName("ix_chat_messages_replied_message_id");
|
||||
|
||||
b.HasIndex("SenderId")
|
||||
.HasDatabaseName("ix_chat_messages_sender_id");
|
||||
|
||||
b.ToTable("chat_messages", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatReaction", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<int>("Attitude")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("attitude");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<Guid>("MessageId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("message_id");
|
||||
|
||||
b.Property<Guid>("SenderId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("sender_id");
|
||||
|
||||
b.Property<string>("Symbol")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)")
|
||||
.HasColumnName("symbol");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_chat_reactions");
|
||||
|
||||
b.HasIndex("MessageId")
|
||||
.HasDatabaseName("ix_chat_reactions_message_id");
|
||||
|
||||
b.HasIndex("SenderId")
|
||||
.HasDatabaseName("ix_chat_reactions_sender_id");
|
||||
|
||||
b.ToTable("chat_reactions", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatRoom", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Guid?>("AccountId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("account_id");
|
||||
|
||||
b.Property<SnCloudFileReferenceObject>("Background")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("background");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<bool>("IsCommunity")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_community");
|
||||
|
||||
b.Property<bool>("IsPublic")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_public");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<SnCloudFileReferenceObject>("Picture")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("picture");
|
||||
|
||||
b.Property<Guid?>("RealmId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("realm_id");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("type");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_chat_rooms");
|
||||
|
||||
b.ToTable("chat_rooms", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnRealtimeCall", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<Instant?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("deleted_at");
|
||||
|
||||
b.Property<Instant?>("EndedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("ended_at");
|
||||
|
||||
b.Property<string>("ProviderName")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("provider_name");
|
||||
|
||||
b.Property<Guid>("RoomId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("room_id");
|
||||
|
||||
b.Property<Guid>("SenderId")
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("sender_id");
|
||||
|
||||
b.Property<string>("SessionId")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("session_id");
|
||||
|
||||
b.Property<Instant>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<string>("UpstreamConfigJson")
|
||||
.HasColumnType("jsonb")
|
||||
.HasColumnName("upstream");
|
||||
|
||||
b.HasKey("Id")
|
||||
.HasName("pk_chat_realtime_call");
|
||||
|
||||
b.HasIndex("RoomId")
|
||||
.HasDatabaseName("ix_chat_realtime_call_room_id");
|
||||
|
||||
b.HasIndex("SenderId")
|
||||
.HasDatabaseName("ix_chat_realtime_call_sender_id");
|
||||
|
||||
b.ToTable("chat_realtime_call", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMember", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatRoom", "ChatRoom")
|
||||
.WithMany("Members")
|
||||
.HasForeignKey("ChatRoomId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_chat_members_chat_rooms_chat_room_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatMember", "InvitedBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("InvitedById")
|
||||
.HasConstraintName("fk_chat_members_chat_members_invited_by_id");
|
||||
|
||||
b.Navigation("ChatRoom");
|
||||
|
||||
b.Navigation("InvitedBy");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMessage", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatRoom", "ChatRoom")
|
||||
.WithMany()
|
||||
.HasForeignKey("ChatRoomId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_chat_messages_chat_rooms_chat_room_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatMessage", "ForwardedMessage")
|
||||
.WithMany()
|
||||
.HasForeignKey("ForwardedMessageId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.HasConstraintName("fk_chat_messages_chat_messages_forwarded_message_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatMessage", "RepliedMessage")
|
||||
.WithMany()
|
||||
.HasForeignKey("RepliedMessageId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.HasConstraintName("fk_chat_messages_chat_messages_replied_message_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatMember", "Sender")
|
||||
.WithMany("Messages")
|
||||
.HasForeignKey("SenderId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_chat_messages_chat_members_sender_id");
|
||||
|
||||
b.Navigation("ChatRoom");
|
||||
|
||||
b.Navigation("ForwardedMessage");
|
||||
|
||||
b.Navigation("RepliedMessage");
|
||||
|
||||
b.Navigation("Sender");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatReaction", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatMessage", "Message")
|
||||
.WithMany("Reactions")
|
||||
.HasForeignKey("MessageId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_chat_reactions_chat_messages_message_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatMember", "Sender")
|
||||
.WithMany("Reactions")
|
||||
.HasForeignKey("SenderId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_chat_reactions_chat_members_sender_id");
|
||||
|
||||
b.Navigation("Message");
|
||||
|
||||
b.Navigation("Sender");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnRealtimeCall", b =>
|
||||
{
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatRoom", "Room")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoomId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_chat_realtime_call_chat_rooms_room_id");
|
||||
|
||||
b.HasOne("DysonNetwork.Shared.Models.SnChatMember", "Sender")
|
||||
.WithMany()
|
||||
.HasForeignKey("SenderId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired()
|
||||
.HasConstraintName("fk_chat_realtime_call_chat_members_sender_id");
|
||||
|
||||
b.Navigation("Room");
|
||||
|
||||
b.Navigation("Sender");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMember", b =>
|
||||
{
|
||||
b.Navigation("Messages");
|
||||
|
||||
b.Navigation("Reactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatMessage", b =>
|
||||
{
|
||||
b.Navigation("Reactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DysonNetwork.Shared.Models.SnChatRoom", b =>
|
||||
{
|
||||
b.Navigation("Members");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
namespace DysonNetwork.Messager.Poll;
|
||||
|
||||
public class PollEmbed
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using DysonNetwork.Shared.Auth;
|
||||
using DysonNetwork.Shared.Http;
|
||||
using DysonNetwork.Shared.Registry;
|
||||
using DysonNetwork.Messager;
|
||||
using DysonNetwork.Messager.Startup;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.AddServiceDefaults();
|
||||
|
||||
builder.ConfigureAppKestrel(builder.Configuration);
|
||||
|
||||
builder.Services.AddAppServices();
|
||||
builder.Services.AddAppAuthentication();
|
||||
builder.Services.AddDysonAuth();
|
||||
builder.Services.AddAccountService();
|
||||
builder.Services.AddRingService();
|
||||
builder.Services.AddDriveService();
|
||||
builder.Services.AddSphereService();
|
||||
builder.Services.AddInsightService();
|
||||
|
||||
builder.Services.AddAppBusinessServices(builder.Configuration);
|
||||
builder.Services.AddAppScheduledJobs();
|
||||
|
||||
builder.AddSwaggerManifest(
|
||||
"DysonNetwork.Messager",
|
||||
"The real-time messaging service in the Solar Network."
|
||||
);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.MapDefaultEndpoints();
|
||||
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();
|
||||
await db.Database.MigrateAsync();
|
||||
}
|
||||
|
||||
app.ConfigureAppMiddleware(builder.Configuration);
|
||||
|
||||
app.UseSwaggerManifest("DysonNetwork.Messager");
|
||||
|
||||
app.Run();
|
||||
@@ -1,172 +0,0 @@
|
||||
using System.Globalization;
|
||||
using DysonNetwork.Messager.Chat;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using DysonNetwork.Shared.Registry;
|
||||
using Grpc.Core;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
using PostReactionAttitude = DysonNetwork.Shared.Proto.PostReactionAttitude;
|
||||
|
||||
namespace DysonNetwork.Messager.Rewind;
|
||||
|
||||
public class MessagerRewindServiceGrpc(
|
||||
AppDatabase db,
|
||||
RemoteAccountService remoteAccounts,
|
||||
ChatRoomService crs
|
||||
) : RewindService.RewindServiceBase
|
||||
{
|
||||
public override async Task<RewindEvent> GetRewindEvent(
|
||||
RequestRewindEvent request,
|
||||
ServerCallContext context
|
||||
)
|
||||
{
|
||||
var accountId = Guid.Parse(request.AccountId);
|
||||
var year = request.Year;
|
||||
|
||||
var startDate = new LocalDate(year - 1, 12, 26).AtMidnight().InUtc().ToInstant();
|
||||
var endDate = new LocalDate(year, 12, 26).AtMidnight().InUtc().ToInstant();
|
||||
|
||||
// Chat data
|
||||
var messagesQuery = db
|
||||
.ChatMessages.Include(m => m.Sender)
|
||||
.Include(m => m.ChatRoom)
|
||||
.Where(m => m.CreatedAt >= startDate && m.CreatedAt < endDate)
|
||||
.Where(m => m.Sender.AccountId == accountId)
|
||||
.AsQueryable();
|
||||
var mostMessagedChatInfo = await messagesQuery
|
||||
.Where(m => m.ChatRoom.Type == ChatRoomType.Group)
|
||||
.GroupBy(m => m.ChatRoomId)
|
||||
.OrderByDescending(g => g.Count())
|
||||
.Select(g => new { ChatRoom = g.First().ChatRoom, MessageCount = g.Count() })
|
||||
.FirstOrDefaultAsync();
|
||||
var mostMessagedChat = mostMessagedChatInfo?.ChatRoom;
|
||||
var mostMessagedDirectChatInfo = await messagesQuery
|
||||
.Where(m => m.ChatRoom.Type == ChatRoomType.DirectMessage)
|
||||
.GroupBy(m => m.ChatRoomId)
|
||||
.OrderByDescending(g => g.Count())
|
||||
.Select(g => new { ChatRoom = g.First().ChatRoom, MessageCount = g.Count() })
|
||||
.FirstOrDefaultAsync();
|
||||
var mostMessagedDirectChat = mostMessagedDirectChatInfo is not null
|
||||
? await crs.LoadDirectMessageMembers(mostMessagedDirectChatInfo.ChatRoom, accountId)
|
||||
: null;
|
||||
|
||||
// Call data
|
||||
var callQuery = db
|
||||
.ChatRealtimeCall.Include(c => c.Sender)
|
||||
.Include(c => c.Room)
|
||||
.Where(c => c.CreatedAt >= startDate && c.CreatedAt < endDate)
|
||||
.Where(c => c.Sender.AccountId == accountId)
|
||||
.AsQueryable();
|
||||
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
var groupCallRecords = await callQuery
|
||||
.Where(c => c.Room.Type == ChatRoomType.Group)
|
||||
.Select(c => new
|
||||
{
|
||||
c.RoomId,
|
||||
c.CreatedAt,
|
||||
c.EndedAt,
|
||||
})
|
||||
.ToListAsync();
|
||||
var callDurations = groupCallRecords
|
||||
.Select(c => new { c.RoomId, Duration = (c.EndedAt ?? now).Minus(c.CreatedAt).Seconds })
|
||||
.ToList();
|
||||
var mostCalledRoomInfo = callDurations
|
||||
.GroupBy(c => c.RoomId)
|
||||
.Select(g => new { RoomId = g.Key, TotalDuration = g.Sum(c => c.Duration) })
|
||||
.OrderByDescending(g => g.TotalDuration)
|
||||
.FirstOrDefault();
|
||||
var mostCalledRoom =
|
||||
mostCalledRoomInfo != null && mostCalledRoomInfo.RoomId != Guid.Empty
|
||||
? await db.ChatRooms.FindAsync(mostCalledRoomInfo.RoomId)
|
||||
: null;
|
||||
|
||||
List<SnAccount>? mostCalledChatTopMembers = null;
|
||||
if (mostCalledRoom != null)
|
||||
mostCalledChatTopMembers = await crs.GetTopActiveMembers(
|
||||
mostCalledRoom.Id,
|
||||
startDate,
|
||||
endDate
|
||||
);
|
||||
|
||||
var directCallRecords = await callQuery
|
||||
.Where(c => c.Room.Type == ChatRoomType.DirectMessage)
|
||||
.Select(c => new
|
||||
{
|
||||
c.RoomId,
|
||||
c.CreatedAt,
|
||||
c.EndedAt,
|
||||
c.Room,
|
||||
})
|
||||
.ToListAsync();
|
||||
var directCallDurations = directCallRecords
|
||||
.Select(c => new
|
||||
{
|
||||
c.RoomId,
|
||||
c.Room,
|
||||
Duration = (c.EndedAt ?? now).Minus(c.CreatedAt).Seconds,
|
||||
})
|
||||
.ToList();
|
||||
var mostCalledDirectRooms = directCallDurations
|
||||
.GroupBy(c => c.RoomId)
|
||||
.Select(g => new { ChatRoom = g.First().Room, TotalDuration = g.Sum(c => c.Duration) })
|
||||
.OrderByDescending(g => g.TotalDuration)
|
||||
.Take(3)
|
||||
.ToList();
|
||||
|
||||
var accountIds = new List<Guid>();
|
||||
foreach (var item in mostCalledDirectRooms)
|
||||
{
|
||||
var room = await crs.LoadDirectMessageMembers(item.ChatRoom, accountId);
|
||||
var otherMember = room.DirectMembers.FirstOrDefault(m => m.AccountId != accountId);
|
||||
if (otherMember != null)
|
||||
accountIds.Add(otherMember.AccountId);
|
||||
}
|
||||
|
||||
var accounts = await remoteAccounts.GetAccountBatch(accountIds);
|
||||
var mostCalledAccounts = accounts
|
||||
.Zip(
|
||||
mostCalledDirectRooms,
|
||||
(account, room) =>
|
||||
new Dictionary<string, object?>
|
||||
{
|
||||
["account"] = account,
|
||||
["duration"] = room.TotalDuration,
|
||||
}
|
||||
)
|
||||
.ToList();
|
||||
|
||||
var data = new Dictionary<string, object?>
|
||||
{
|
||||
["most_messaged_chat"] = mostMessagedChatInfo is not null
|
||||
? new Dictionary<string, object?>
|
||||
{
|
||||
["chat"] = mostMessagedChat,
|
||||
["message_counts"] = mostMessagedChatInfo.MessageCount,
|
||||
}
|
||||
: null,
|
||||
["most_messaged_direct_chat"] = mostMessagedDirectChatInfo is not null
|
||||
? new Dictionary<string, object?>
|
||||
{
|
||||
["chat"] = mostMessagedDirectChat,
|
||||
["message_counts"] = mostMessagedDirectChatInfo.MessageCount,
|
||||
}
|
||||
: null,
|
||||
["most_called_chat"] = new Dictionary<string, object?>
|
||||
{
|
||||
["chat"] = mostCalledRoom,
|
||||
["duration"] = mostCalledRoomInfo?.TotalDuration,
|
||||
},
|
||||
["most_called_chat_top_members"] = mostCalledChatTopMembers,
|
||||
["most_called_accounts"] = mostCalledAccounts,
|
||||
};
|
||||
|
||||
return new RewindEvent
|
||||
{
|
||||
ServiceId = "messager",
|
||||
AccountId = request.AccountId,
|
||||
Data = GrpcTypeHelper.ConvertObjectToByteString(data, withoutIgnore: true),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using DysonNetwork.Shared.Auth;
|
||||
using DysonNetwork.Shared.Http;
|
||||
|
||||
namespace DysonNetwork.Messager.Startup;
|
||||
|
||||
public static class ApplicationConfiguration
|
||||
{
|
||||
public static WebApplication ConfigureAppMiddleware(this WebApplication app, IConfiguration configuration)
|
||||
{
|
||||
app.ConfigureForwardedHeaders(configuration);
|
||||
|
||||
app.UseWebSockets();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseMiddleware<RemotePermissionMiddleware>();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.MapGrpcReflectionService();
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
@@ -1,345 +0,0 @@
|
||||
using System.Text.Json;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using DysonNetwork.Shared.Queue;
|
||||
using DysonNetwork.Messager.Chat;
|
||||
using Google.Protobuf;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NATS.Client.Core;
|
||||
using NATS.Client.JetStream.Models;
|
||||
using NATS.Net;
|
||||
using NodaTime;
|
||||
using WebSocketPacket = DysonNetwork.Shared.Models.WebSocketPacket;
|
||||
|
||||
namespace DysonNetwork.Messager.Startup;
|
||||
|
||||
public class BroadcastEventHandler(
|
||||
IServiceProvider serviceProvider,
|
||||
ILogger<BroadcastEventHandler> logger,
|
||||
INatsConnection nats,
|
||||
RingService.RingServiceClient pusher
|
||||
) : BackgroundService
|
||||
{
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
var accountTask = HandleAccountDeletions(stoppingToken);
|
||||
var websocketTask = HandleWebSocketPackets(stoppingToken);
|
||||
var accountStatusTask = HandleAccountStatusUpdates(stoppingToken);
|
||||
|
||||
await Task.WhenAll(accountTask, websocketTask, accountStatusTask);
|
||||
}
|
||||
|
||||
private async Task HandleAccountDeletions(CancellationToken stoppingToken)
|
||||
{
|
||||
var js = nats.CreateJetStreamContext();
|
||||
|
||||
await js.EnsureStreamCreated("account_events", [AccountDeletedEvent.Type]);
|
||||
|
||||
var consumer = await js.CreateOrUpdateConsumerAsync("account_events",
|
||||
new ConsumerConfig("messager_account_deleted_handler"), cancellationToken: stoppingToken);
|
||||
|
||||
await foreach (var msg in consumer.ConsumeAsync<byte[]>(cancellationToken: stoppingToken))
|
||||
{
|
||||
try
|
||||
{
|
||||
var evt = JsonSerializer.Deserialize<AccountDeletedEvent>(msg.Data, GrpcTypeHelper.SerializerOptions);
|
||||
if (evt == null)
|
||||
{
|
||||
await msg.AckAsync(cancellationToken: stoppingToken);
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.LogInformation("Account deleted: {AccountId}", evt.AccountId);
|
||||
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();
|
||||
|
||||
await db.ChatMembers
|
||||
.Where(m => m.AccountId == evt.AccountId)
|
||||
.ExecuteDeleteAsync(cancellationToken: stoppingToken);
|
||||
|
||||
await using var transaction = await db.Database.BeginTransactionAsync(cancellationToken: stoppingToken);
|
||||
try
|
||||
{
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
await db.ChatMessages
|
||||
.Where(m => m.Sender.AccountId == evt.AccountId)
|
||||
.ExecuteUpdateAsync(c => c.SetProperty(p => p.DeletedAt, now), stoppingToken);
|
||||
|
||||
await db.ChatReactions
|
||||
.Where(r => r.Sender.AccountId == evt.AccountId)
|
||||
.ExecuteUpdateAsync(c => c.SetProperty(p => p.DeletedAt, now), stoppingToken);
|
||||
|
||||
await db.ChatMembers
|
||||
.Where(m => m.AccountId == evt.AccountId)
|
||||
.ExecuteUpdateAsync(c => c.SetProperty(p => p.DeletedAt, now), stoppingToken);
|
||||
|
||||
await transaction.CommitAsync(cancellationToken: stoppingToken);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await transaction.RollbackAsync(cancellationToken: stoppingToken);
|
||||
throw;
|
||||
}
|
||||
|
||||
await msg.AckAsync(cancellationToken: stoppingToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error processing AccountDeleted");
|
||||
await msg.NakAsync(cancellationToken: stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleWebSocketPackets(CancellationToken stoppingToken)
|
||||
{
|
||||
await foreach (var msg in nats.SubscribeAsync<byte[]>(
|
||||
WebSocketPacketEvent.SubjectPrefix + "messager", cancellationToken: stoppingToken))
|
||||
{
|
||||
logger.LogDebug("Handling websocket packet...");
|
||||
|
||||
try
|
||||
{
|
||||
var evt = JsonSerializer.Deserialize<WebSocketPacketEvent>(msg.Data, GrpcTypeHelper.SerializerOptions);
|
||||
if (evt == null) throw new ArgumentNullException(nameof(evt));
|
||||
var packet = WebSocketPacket.FromBytes(evt.PacketBytes);
|
||||
logger.LogInformation("Handling websocket packet... {Type}", packet.Type);
|
||||
switch (packet.Type)
|
||||
{
|
||||
case "messages.read":
|
||||
await HandleMessageRead(evt, packet);
|
||||
break;
|
||||
case "messages.typing":
|
||||
await HandleMessageTyping(evt, packet);
|
||||
break;
|
||||
case "messages.subscribe":
|
||||
await HandleMessageSubscribe(evt, packet);
|
||||
break;
|
||||
case "messages.unsubscribe":
|
||||
await HandleMessageUnsubscribe(evt, packet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error processing websocket packet");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleMessageRead(WebSocketPacketEvent evt, WebSocketPacket packet)
|
||||
{
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var cs = scope.ServiceProvider.GetRequiredService<ChatService>();
|
||||
var crs = scope.ServiceProvider.GetRequiredService<ChatRoomService>();
|
||||
|
||||
if (packet.Data == null)
|
||||
{
|
||||
await SendErrorResponse(evt, "Mark message as read requires you to provide the ChatRoomId");
|
||||
return;
|
||||
}
|
||||
|
||||
var requestData = packet.GetData<Chat.ChatController.MarkMessageReadRequest>();
|
||||
if (requestData == null)
|
||||
{
|
||||
await SendErrorResponse(evt, "Invalid request data");
|
||||
return;
|
||||
}
|
||||
|
||||
var sender = await crs.GetRoomMember(evt.AccountId, requestData.ChatRoomId);
|
||||
if (sender == null)
|
||||
{
|
||||
await SendErrorResponse(evt, "User is not a member of the chat room.");
|
||||
return;
|
||||
}
|
||||
|
||||
await cs.ReadChatRoomAsync(requestData.ChatRoomId, evt.AccountId);
|
||||
}
|
||||
|
||||
private async Task HandleMessageTyping(WebSocketPacketEvent evt, WebSocketPacket packet)
|
||||
{
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var crs = scope.ServiceProvider.GetRequiredService<ChatRoomService>();
|
||||
|
||||
if (packet.Data == null)
|
||||
{
|
||||
await SendErrorResponse(evt, "messages.typing requires you to provide the ChatRoomId");
|
||||
return;
|
||||
}
|
||||
|
||||
var requestData = packet.GetData<Chat.ChatController.ChatRoomWsUniversalRequest>();
|
||||
if (requestData == null)
|
||||
{
|
||||
await SendErrorResponse(evt, "Invalid request data");
|
||||
return;
|
||||
}
|
||||
|
||||
var sender = await crs.GetRoomMember(evt.AccountId, requestData.ChatRoomId);
|
||||
if (sender == null)
|
||||
{
|
||||
await SendErrorResponse(evt, "User is not a member of the chat room.");
|
||||
return;
|
||||
}
|
||||
|
||||
var responsePacket = new WebSocketPacket
|
||||
{
|
||||
Type = "messages.typing",
|
||||
Data = new
|
||||
{
|
||||
room_id = sender.ChatRoomId,
|
||||
sender_id = sender.Id,
|
||||
sender
|
||||
}
|
||||
};
|
||||
|
||||
// Broadcast typing indicator to subscribed room members only
|
||||
var subscribedMemberIds = await crs.GetSubscribedMembers(requestData.ChatRoomId);
|
||||
var roomMembers = await crs.ListRoomMembers(requestData.ChatRoomId);
|
||||
|
||||
// Filter to subscribed members excluding the current user
|
||||
var subscribedMembers = roomMembers
|
||||
.Where(m => subscribedMemberIds.Contains(m.Id) && m.AccountId != evt.AccountId)
|
||||
.Select(m => m.AccountId.ToString())
|
||||
.ToList();
|
||||
|
||||
if (subscribedMembers.Count > 0)
|
||||
{
|
||||
var respRequest = new PushWebSocketPacketToUsersRequest { Packet = responsePacket.ToProtoValue() };
|
||||
respRequest.UserIds.AddRange(subscribedMembers);
|
||||
await pusher.PushWebSocketPacketToUsersAsync(respRequest);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleMessageSubscribe(WebSocketPacketEvent evt, WebSocketPacket packet)
|
||||
{
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var crs = scope.ServiceProvider.GetRequiredService<ChatRoomService>();
|
||||
|
||||
if (packet.Data == null)
|
||||
{
|
||||
await SendErrorResponse(evt, "messages.subscribe requires you to provide the ChatRoomId");
|
||||
return;
|
||||
}
|
||||
|
||||
var requestData = packet.GetData<Chat.ChatController.ChatRoomWsUniversalRequest>();
|
||||
if (requestData == null)
|
||||
{
|
||||
await SendErrorResponse(evt, "Invalid request data");
|
||||
return;
|
||||
}
|
||||
|
||||
var sender = await crs.GetRoomMember(evt.AccountId, requestData.ChatRoomId);
|
||||
if (sender == null)
|
||||
{
|
||||
await SendErrorResponse(evt, "User is not a member of the chat room.");
|
||||
return;
|
||||
}
|
||||
|
||||
await crs.SubscribeChatRoom(sender);
|
||||
}
|
||||
|
||||
private async Task HandleMessageUnsubscribe(WebSocketPacketEvent evt, WebSocketPacket packet)
|
||||
{
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var crs = scope.ServiceProvider.GetRequiredService<ChatRoomService>();
|
||||
|
||||
if (packet.Data == null)
|
||||
{
|
||||
await SendErrorResponse(evt, "messages.unsubscribe requires you to provide the ChatRoomId");
|
||||
return;
|
||||
}
|
||||
|
||||
var requestData = packet.GetData<Chat.ChatController.ChatRoomWsUniversalRequest>();
|
||||
if (requestData == null)
|
||||
{
|
||||
await SendErrorResponse(evt, "Invalid request data");
|
||||
return;
|
||||
}
|
||||
|
||||
var sender = await crs.GetRoomMember(evt.AccountId, requestData.ChatRoomId);
|
||||
if (sender == null)
|
||||
{
|
||||
await SendErrorResponse(evt, "User is not a member of the chat room.");
|
||||
return;
|
||||
}
|
||||
|
||||
await crs.UnsubscribeChatRoom(sender);
|
||||
}
|
||||
|
||||
private async Task HandleAccountStatusUpdates(CancellationToken stoppingToken)
|
||||
{
|
||||
await foreach (var msg in nats.SubscribeAsync<byte[]>(AccountStatusUpdatedEvent.Type,
|
||||
cancellationToken: stoppingToken))
|
||||
{
|
||||
try
|
||||
{
|
||||
var evt =
|
||||
GrpcTypeHelper.ConvertByteStringToObject<AccountStatusUpdatedEvent>(ByteString.CopyFrom(msg.Data));
|
||||
if (evt == null)
|
||||
continue;
|
||||
|
||||
logger.LogInformation("Account status updated: {AccountId}", evt.AccountId);
|
||||
|
||||
await using var scope = serviceProvider.CreateAsyncScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();
|
||||
var chatRoomService = scope.ServiceProvider.GetRequiredService<ChatRoomService>();
|
||||
|
||||
// Get user's joined chat rooms
|
||||
var userRooms = await db.ChatMembers
|
||||
.Where(m => m.AccountId == evt.AccountId && m.JoinedAt != null && m.LeaveAt == null)
|
||||
.Select(m => m.ChatRoomId)
|
||||
.ToListAsync(cancellationToken: stoppingToken);
|
||||
|
||||
// Send WebSocket packet to subscribed users per room
|
||||
foreach (var roomId in userRooms)
|
||||
{
|
||||
var members = await chatRoomService.ListRoomMembers(roomId);
|
||||
var subscribedMemberIds = await chatRoomService.GetSubscribedMembers(roomId);
|
||||
var subscribedUsers = members
|
||||
.Where(m => subscribedMemberIds.Contains(m.Id))
|
||||
.Select(m => m.AccountId.ToString())
|
||||
.ToList();
|
||||
|
||||
if (subscribedUsers.Count == 0) continue;
|
||||
var packet = new WebSocketPacket
|
||||
{
|
||||
Type = "accounts.status.update",
|
||||
Data = new Dictionary<string, object>
|
||||
{
|
||||
["status"] = evt.Status,
|
||||
["chat_room_id"] = roomId
|
||||
}
|
||||
};
|
||||
|
||||
var request = new PushWebSocketPacketToUsersRequest
|
||||
{
|
||||
Packet = packet.ToProtoValue()
|
||||
};
|
||||
request.UserIds.AddRange(subscribedUsers);
|
||||
|
||||
await pusher.PushWebSocketPacketToUsersAsync(request, cancellationToken: stoppingToken);
|
||||
|
||||
logger.LogInformation("Sent status update for room {roomId} to {count} subscribed users", roomId,
|
||||
subscribedUsers.Count);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error processing AccountStatusUpdated");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendErrorResponse(WebSocketPacketEvent evt, string message)
|
||||
{
|
||||
await pusher.PushWebSocketPacketToDeviceAsync(new PushWebSocketPacketToDeviceRequest
|
||||
{
|
||||
DeviceId = evt.DeviceId,
|
||||
Packet = new WebSocketPacket
|
||||
{
|
||||
Type = "error",
|
||||
ErrorMessage = message
|
||||
}.ToProtoValue()
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Quartz;
|
||||
|
||||
namespace DysonNetwork.Messager.Startup;
|
||||
|
||||
public static class ScheduledJobsConfiguration
|
||||
{
|
||||
public static IServiceCollection AddAppScheduledJobs(this IServiceCollection services)
|
||||
{
|
||||
services.AddQuartz(q =>
|
||||
{
|
||||
q.AddJob<AppDatabaseRecyclingJob>(opts => opts.WithIdentity("AppDatabaseRecycling"));
|
||||
q.AddTrigger(opts => opts
|
||||
.ForJob("AppDatabaseRecycling")
|
||||
.WithIdentity("AppDatabaseRecyclingTrigger")
|
||||
.WithCronSchedule("0 0 0 * * ?"));
|
||||
});
|
||||
services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using DysonNetwork.Messager.Chat;
|
||||
using DysonNetwork.Messager.Chat.Realtime;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.SystemTextJson;
|
||||
|
||||
namespace DysonNetwork.Messager.Startup;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
extension(IServiceCollection services)
|
||||
{
|
||||
public IServiceCollection AddAppServices()
|
||||
{
|
||||
services.AddDbContext<AppDatabase>();
|
||||
services.AddHttpContextAccessor();
|
||||
|
||||
services.AddHttpClient();
|
||||
|
||||
services
|
||||
.AddControllers()
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.NumberHandling =
|
||||
JsonNumberHandling.AllowNamedFloatingPointLiterals;
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy =
|
||||
JsonNamingPolicy.SnakeCaseLower;
|
||||
|
||||
options.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);
|
||||
});
|
||||
|
||||
services.AddGrpc(options =>
|
||||
{
|
||||
options.EnableDetailedErrors = true;
|
||||
});
|
||||
services.AddGrpcReflection();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public IServiceCollection AddAppAuthentication()
|
||||
{
|
||||
services.AddAuthorization();
|
||||
return services;
|
||||
}
|
||||
|
||||
public IServiceCollection AddAppBusinessServices(IConfiguration configuration
|
||||
)
|
||||
{
|
||||
services.AddScoped<ChatRoomService>();
|
||||
services.AddScoped<ChatService>();
|
||||
services.AddScoped<IRealtimeService, LiveKitRealtimeService>();
|
||||
|
||||
services.AddHostedService<BroadcastEventHandler>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
using DysonNetwork.Shared.Models;
|
||||
|
||||
namespace DysonNetwork.Messager.Wallet;
|
||||
|
||||
public class FundEmbed
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"Debug": true,
|
||||
"BaseUrl": "http://localhost:5072",
|
||||
"SiteUrl": "https://solian.app",
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"App": "Host=localhost;Port=5432;Database=dyson_messager;Username=postgres;Password=postgres;Include Error Detail=True;Maximum Pool Size=20;Connection Idle Lifetime=60"
|
||||
},
|
||||
"KnownProxies": [
|
||||
"127.0.0.1",
|
||||
"::1"
|
||||
],
|
||||
"Etcd": {
|
||||
"Insecure": true
|
||||
},
|
||||
"Cache": {
|
||||
"Serializer": "MessagePack"
|
||||
},
|
||||
"Service": {
|
||||
"Name": "DysonNetwork.Messager",
|
||||
"Url": "https://localhost:7100"
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using DysonNetwork.Pass.Wallet;
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using DysonNetwork.Shared.Queue;
|
||||
using DysonNetwork.Shared.Stream;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using NATS.Client.Core;
|
||||
@@ -25,10 +25,9 @@ public class AccountEventService(
|
||||
{
|
||||
private static readonly Random Random = new();
|
||||
private const string StatusCacheKey = "account:status:";
|
||||
private const string PreviousStatusCacheKey = "account:status:prev:";
|
||||
private const string ActivityCacheKey = "account:activities:";
|
||||
|
||||
private async Task<bool> GetAccountIsConnected(Guid userId)
|
||||
public async Task<bool> GetAccountIsConnected(Guid userId)
|
||||
{
|
||||
var resp = await pusher.GetWebsocketConnectionStatusAsync(
|
||||
new GetWebsocketConnectionStatusRequest { UserId = userId.ToString() }
|
||||
@@ -50,8 +49,6 @@ public class AccountEventService(
|
||||
{
|
||||
var cacheKey = $"{StatusCacheKey}{userId}";
|
||||
cache.RemoveAsync(cacheKey);
|
||||
var prevCacheKey = $"{PreviousStatusCacheKey}{userId}";
|
||||
cache.RemoveAsync(prevCacheKey);
|
||||
}
|
||||
|
||||
public void PurgeActivityCache(Guid userId)
|
||||
@@ -73,71 +70,51 @@ public class AccountEventService(
|
||||
);
|
||||
}
|
||||
|
||||
private static bool StatusesEqual(SnAccountStatus a, SnAccountStatus b)
|
||||
{
|
||||
return a.Attitude == b.Attitude &&
|
||||
a.IsOnline == b.IsOnline &&
|
||||
a.IsCustomized == b.IsCustomized &&
|
||||
a.Label == b.Label &&
|
||||
a.IsInvisible == b.IsInvisible &&
|
||||
a.IsNotDisturb == b.IsNotDisturb;
|
||||
}
|
||||
|
||||
public async Task<SnAccountStatus> GetStatus(Guid userId)
|
||||
{
|
||||
var cacheKey = $"{StatusCacheKey}{userId}";
|
||||
var cachedStatus = await cache.GetAsync<SnAccountStatus>(cacheKey);
|
||||
SnAccountStatus status;
|
||||
if (cachedStatus is not null)
|
||||
{
|
||||
cachedStatus!.IsOnline = !cachedStatus.IsInvisible && await GetAccountIsConnected(userId);
|
||||
status = cachedStatus;
|
||||
return cachedStatus;
|
||||
}
|
||||
else
|
||||
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
var status = await db.AccountStatuses
|
||||
.Where(e => e.AccountId == userId)
|
||||
.Where(e => e.ClearedAt == null || e.ClearedAt > now)
|
||||
.OrderByDescending(e => e.CreatedAt)
|
||||
.FirstOrDefaultAsync();
|
||||
var isOnline = await GetAccountIsConnected(userId);
|
||||
if (status is not null)
|
||||
{
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
status = await db.AccountStatuses
|
||||
.Where(e => e.AccountId == userId)
|
||||
.Where(e => e.ClearedAt == null || e.ClearedAt > now)
|
||||
.OrderByDescending(e => e.CreatedAt)
|
||||
.FirstOrDefaultAsync();
|
||||
var isOnline = await GetAccountIsConnected(userId);
|
||||
if (status is not null)
|
||||
{
|
||||
status.IsOnline = !status.IsInvisible && isOnline;
|
||||
await cache.SetWithGroupsAsync(cacheKey, status, [$"{AccountService.AccountCachePrefix}{status.AccountId}"],
|
||||
TimeSpan.FromMinutes(5));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isOnline)
|
||||
{
|
||||
status = new SnAccountStatus
|
||||
{
|
||||
Attitude = Shared.Models.StatusAttitude.Neutral,
|
||||
IsOnline = true,
|
||||
IsCustomized = false,
|
||||
Label = "Online",
|
||||
AccountId = userId,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
status = new SnAccountStatus
|
||||
{
|
||||
Attitude = Shared.Models.StatusAttitude.Neutral,
|
||||
IsOnline = false,
|
||||
IsCustomized = false,
|
||||
Label = "Offline",
|
||||
AccountId = userId,
|
||||
};
|
||||
}
|
||||
}
|
||||
status.IsOnline = !status.IsInvisible && isOnline;
|
||||
await cache.SetWithGroupsAsync(cacheKey, status, [$"{AccountService.AccountCachePrefix}{status.AccountId}"],
|
||||
TimeSpan.FromMinutes(5));
|
||||
return status;
|
||||
}
|
||||
|
||||
await cache.SetAsync($"{PreviousStatusCacheKey}{userId}", status, TimeSpan.FromMinutes(5));
|
||||
if (isOnline)
|
||||
{
|
||||
return new SnAccountStatus
|
||||
{
|
||||
Attitude = Shared.Models.StatusAttitude.Neutral,
|
||||
IsOnline = true,
|
||||
IsCustomized = false,
|
||||
Label = "Online",
|
||||
AccountId = userId,
|
||||
};
|
||||
}
|
||||
|
||||
return status;
|
||||
return new SnAccountStatus
|
||||
{
|
||||
Attitude = Shared.Models.StatusAttitude.Neutral,
|
||||
IsOnline = false,
|
||||
IsCustomized = false,
|
||||
Label = "Offline",
|
||||
AccountId = userId,
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<Dictionary<Guid, SnAccountStatus>> GetStatuses(List<Guid> userIds)
|
||||
|
||||
@@ -8,7 +8,7 @@ using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Data;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using DysonNetwork.Shared.Queue;
|
||||
using DysonNetwork.Shared.Stream;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using NATS.Client.Core;
|
||||
@@ -54,14 +54,6 @@ public class AccountService(
|
||||
await cache.RemoveGroupAsync($"{AccountCachePrefix}{account.Id}");
|
||||
}
|
||||
|
||||
public async Task<SnAccount?> GetAccount(Guid id)
|
||||
{
|
||||
return await db.Accounts
|
||||
.Where(a => a.Id == id)
|
||||
.Include(a => a.Profile)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<SnAccount?> LookupAccount(string probe)
|
||||
{
|
||||
var account = await db.Accounts.Where(a => EF.Functions.ILike(a.Name, probe)).FirstOrDefaultAsync();
|
||||
|
||||
@@ -53,8 +53,7 @@ public class AccountServiceGrpc(
|
||||
.FirstOrDefaultAsync(a => a.AutomatedId == automatedId);
|
||||
|
||||
if (account == null)
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound,
|
||||
$"Account with automated ID {request.AutomatedId} not found"));
|
||||
throw new RpcException(new Grpc.Core.Status(StatusCode.NotFound, $"Account with automated ID {request.AutomatedId} not found"));
|
||||
|
||||
var perk = await subscriptions.GetPerkSubscriptionAsync(account.Id);
|
||||
account.PerkSubscription = perk?.ToReference();
|
||||
@@ -88,8 +87,8 @@ public class AccountServiceGrpc(
|
||||
response.Accounts.AddRange(accounts.Select(a => a.ToProtoValue()));
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public override async Task<GetAccountBatchResponse> GetBotAccountBatch(GetBotAccountBatchRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
@@ -160,8 +159,7 @@ public class AccountServiceGrpc(
|
||||
return response;
|
||||
}
|
||||
|
||||
public override async Task<GetAccountBatchResponse> SearchAccount(SearchAccountRequest request,
|
||||
ServerCallContext context)
|
||||
public override async Task<GetAccountBatchResponse> SearchAccount(SearchAccountRequest request, ServerCallContext context)
|
||||
{
|
||||
var accounts = await _db.Accounts
|
||||
.AsNoTracking()
|
||||
@@ -234,48 +232,21 @@ public class AccountServiceGrpc(
|
||||
public override async Task<ListRelationshipSimpleResponse> ListFriends(
|
||||
ListRelationshipSimpleRequest request, ServerCallContext context)
|
||||
{
|
||||
var accountId = Guid.Parse(request.AccountId);
|
||||
var relationship = await relationships.ListAccountFriends(accountId);
|
||||
var resp = new ListRelationshipSimpleResponse();
|
||||
switch (request.RelationIdentifierCase)
|
||||
{
|
||||
case ListRelationshipSimpleRequest.RelationIdentifierOneofCase.AccountId:
|
||||
var accountId = Guid.Parse(request.AccountId);
|
||||
var relationship = await relationships.ListAccountFriends(accountId);
|
||||
resp.AccountsId.AddRange(relationship.Select(x => x.ToString()));
|
||||
return resp;
|
||||
case ListRelationshipSimpleRequest.RelationIdentifierOneofCase.RelatedId:
|
||||
var relatedId = Guid.Parse(request.RelatedId);
|
||||
var relatedRelationship = await relationships.ListAccountFriends(relatedId, true);
|
||||
resp.AccountsId.AddRange(relatedRelationship.Select(x => x.ToString()));
|
||||
return resp;
|
||||
break;
|
||||
case ListRelationshipSimpleRequest.RelationIdentifierOneofCase.None:
|
||||
default:
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument,
|
||||
$"The relationship identifier must be provided."));
|
||||
}
|
||||
resp.AccountsId.AddRange(relationship.Select(x => x.ToString()));
|
||||
return resp;
|
||||
}
|
||||
|
||||
public override async Task<ListRelationshipSimpleResponse> ListBlocked(
|
||||
ListRelationshipSimpleRequest request, ServerCallContext context)
|
||||
{
|
||||
var accountId = Guid.Parse(request.AccountId);
|
||||
var relationship = await relationships.ListAccountBlocked(accountId);
|
||||
var resp = new ListRelationshipSimpleResponse();
|
||||
switch (request.RelationIdentifierCase)
|
||||
{
|
||||
case ListRelationshipSimpleRequest.RelationIdentifierOneofCase.AccountId:
|
||||
var accountId = Guid.Parse(request.AccountId);
|
||||
var relationship = await relationships.ListAccountBlocked(accountId);
|
||||
resp.AccountsId.AddRange(relationship.Select(x => x.ToString()));
|
||||
return resp;
|
||||
case ListRelationshipSimpleRequest.RelationIdentifierOneofCase.RelatedId:
|
||||
var relatedId = Guid.Parse(request.RelatedId);
|
||||
var relatedRelationship = await relationships.ListAccountBlocked(relatedId, true);
|
||||
resp.AccountsId.AddRange(relatedRelationship.Select(x => x.ToString()));
|
||||
return resp;
|
||||
case ListRelationshipSimpleRequest.RelationIdentifierOneofCase.None:
|
||||
default:
|
||||
throw new RpcException(new Status(StatusCode.InvalidArgument,
|
||||
$"The relationship identifier must be provided."));
|
||||
}
|
||||
resp.AccountsId.AddRange(relationship.Select(x => x.ToString()));
|
||||
return resp;
|
||||
}
|
||||
|
||||
public override async Task<GetRelationshipResponse> GetRelationship(GetRelationshipRequest request,
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace DysonNetwork.Pass.Account;
|
||||
|
||||
public record FortuneSaying(
|
||||
string Content,
|
||||
string Source,
|
||||
string Language
|
||||
);
|
||||
|
||||
[ApiController]
|
||||
[Route("/api/fortune")]
|
||||
public class FortuneSayingController : ControllerBase
|
||||
{
|
||||
private static readonly FortuneSaying[] Sayings =
|
||||
[
|
||||
// Chinese sayings
|
||||
new("天行健,君子以自强不息。", "周易", "zh"),
|
||||
new("地势坤,君子以厚德载物。", "周易", "zh"),
|
||||
new("天道酬勤。", "谚语", "zh"),
|
||||
new("天时不如地利,地利不如人和。", "孟子", "zh"),
|
||||
new("君子有三畏:畏天命,畏大人,畏圣人之言。", "论语", "zh"),
|
||||
new("己所不欲,勿施于人。", "孔子", "zh"),
|
||||
new("学而时习之,不亦说乎?", "论语", "zh"),
|
||||
new("有朋自远方来,不亦乐乎?", "论语", "zh"),
|
||||
new("人不知而不愠,不亦君子乎?", "论语", "zh"),
|
||||
new("不愤不启,不悱不发。", "论语", "zh"),
|
||||
new("温故而知新,可以为师矣。", "论语", "zh"),
|
||||
new("见贤思齐焉,见不贤而内自省也。", "论语", "zh"),
|
||||
new("君子坦荡荡,小人长戚戚。", "论语", "zh"),
|
||||
new("道不远人。人之为道而远人,不可以为道。", "论语", "zh"),
|
||||
new("巧言令色,鲜矣仁。", "论语", "zh"),
|
||||
new("岁寒,然后知松柏之后凋也。", "孔子", "zh"),
|
||||
new("志于道,据于德,依于仁,游于艺。", "论语", "zh"),
|
||||
new("博学而笃志,切问而近思。", "论语", "zh"),
|
||||
new("知之者不如好之者,好之者不如乐之者。", "论语", "zh"),
|
||||
new("君子欲讷于言而敏于行。", "论语", "zh"),
|
||||
new("君子周而不比,小人比而不周。", "论语", "zh"),
|
||||
new("君子喻于义,小人喻于利。", "论语", "zh"),
|
||||
new("君子怀德,小人怀土。", "论语", "zh"),
|
||||
new("君子矜而不争,群而不党。", "论语", "zh"),
|
||||
new("君子和而不同,小人同而不和。", "论语", "zh"),
|
||||
new("君子泰而不骄,小人骄而不泰。", "论语", "zh"),
|
||||
new("君子谋道不谋食。", "论语", "zh"),
|
||||
new("君子食无求饱,居无求安。", "论语", "zh"),
|
||||
new("君子学以致其道。", "论语", "zh"),
|
||||
new("君子耻其言而过其行。", "论语", "zh"),
|
||||
new("君子敬而无失,与人恭而有礼。", "论语", "zh"),
|
||||
new("君子求诸己,小人求诸人。", "论语", "zh"),
|
||||
new("君子慎独。", "论语", "zh"),
|
||||
new("君子不以言举人,不以人废言。", "论语", "zh"),
|
||||
new("君子不器。", "论语", "zh"),
|
||||
new("君子有终身之忧,无一朝之患。", "论语", "zh"),
|
||||
new("君子固穷,小人穷斯滥矣。", "论语", "zh"),
|
||||
new("君子疾没世而名不称焉。", "论语", "zh"),
|
||||
new("君子而不仁者有矣夫,未有小人而仁者也。", "论语", "zh"),
|
||||
new("君子义以为质,礼以行之,逊以出之,信以成之。", "论语", "zh"),
|
||||
new("君子之德风,小人之德草。", "论语", "zh"),
|
||||
new("君子之过也,如日月之食焉。", "论语", "zh"),
|
||||
new("君子之言,寡而实;小人之言,多而虚。", "论语", "zh"),
|
||||
new("君子之行,静以修身;小人之行,躁以求名。", "论语", "zh"),
|
||||
new("君子之交淡若水,小人之交甘若醴。", "论语", "zh"),
|
||||
new("君子之泽,五世而斩;小人之泽,亦五世而斩。", "孟子", "zh"),
|
||||
new("君子有三乐,而王天下不与存焉。", "孟子", "zh"),
|
||||
new("君子有三戒:少之时,血气未定,戒之在色;及其壮也,血气方刚,戒之在斗;及其老也,血气既衰,戒之在得。", "论语", "zh"),
|
||||
new("君子莫大乎与人为善。", "孟子", "zh"),
|
||||
new("君子远庖厨。", "孟子", "zh"),
|
||||
// English sayings
|
||||
new("The only way to do great work is to love what you do.", "Steve Jobs", "en"),
|
||||
new("Believe you can and you're halfway there.", "Theodore Roosevelt", "en"),
|
||||
new("The future belongs to those who believe in the beauty of their dreams.", "Eleanor Roosevelt", "en"),
|
||||
new("You miss 100% of the shots you don't take.", "Wayne Gretzky", "en"),
|
||||
new("The best way to predict the future is to create it.", "Peter Drucker", "en"),
|
||||
new("Fortune favors the bold.", "Virgil", "en"),
|
||||
new("Luck is what happens when preparation meets opportunity.", "Seneca", "en"),
|
||||
new("The harder you work, the luckier you get.", "Gary Player", "en"),
|
||||
new("Success is not final, failure is not fatal: It is the courage to continue that counts.", "Winston Churchill", "en"),
|
||||
new("The pessimist complains about the wind; the optimist expects it to change; the realist adjusts the sails.", "William Arthur Ward", "en"),
|
||||
new("The road to success is dotted with many tempting parking spaces.", "Will Rogers", "en"),
|
||||
new("Don't watch the clock; do what it does. Keep going.", "Sam Levenson", "en"),
|
||||
new("The only limit to our realization of tomorrow will be our doubts of today.", "Franklin D. Roosevelt", "en"),
|
||||
new("Your time is limited, so don't waste it living someone else's life.", "Steve Jobs", "en"),
|
||||
new("The way to get started is to quit talking and begin doing.", "Walt Disney", "en"),
|
||||
new("If you look at what you have in life, you'll always have more.", "Oprah Winfrey", "en"),
|
||||
new("The best revenge is massive success.", "Frank Sinatra", "en"),
|
||||
new("You must do the things you think you cannot do.", "Eleanor Roosevelt", "en"),
|
||||
new("Keep your face always toward the sunshine—and shadows will fall behind you.", "Walt Whitman", "en"),
|
||||
new("The greatest glory in living lies not in never falling, but in rising every time we fall.", "Nelson Mandela", "en"),
|
||||
new("Life is what happens to you while you're busy making other plans.", "John Lennon", "en"),
|
||||
new("The secret of getting ahead is getting started.", "Mark Twain", "en"),
|
||||
new("Believe in yourself and all that you are.", "Christian D. Larson", "en"),
|
||||
new("The only person you are destined to become is the person you decide to be.", "Ralph Waldo Emerson", "en"),
|
||||
new("Dream big and dare to fail.", "Norman Vaughan", "en"),
|
||||
new("What lies behind us and what lies before us are tiny matters compared to what lies within us.", "Ralph Waldo Emerson", "en"),
|
||||
new("You can't use up creativity. The more you use, the more you have.", "Maya Angelou", "en"),
|
||||
new("The mind is everything. What you think you become.", "Buddha", "en"),
|
||||
new("The best time to plant a tree was 20 years ago. The second best time is now.", "Chinese Proverb", "en"),
|
||||
new("Fall seven times, stand up eight.", "Japanese Proverb", "en"),
|
||||
new("The journey of a thousand miles begins with a single step.", "Lao Tzu", "en"),
|
||||
new("Be not afraid of growing slowly, be afraid only of standing still.", "Chinese Proverb", "en"),
|
||||
new("A bird does not sing because it has an answer. It sings because it has a song.", "Chinese Proverb", "en"),
|
||||
new("Do not dwell in the past, do not dream of the future, concentrate the mind on the present moment.", "Buddha", "en"),
|
||||
new("The best and most beautiful things in the world cannot be seen or even touched - they must be felt with the heart.", "Helen Keller", "en"),
|
||||
new("Keep your eyes on the stars, and your feet on the ground.", "Theodore Roosevelt", "en"),
|
||||
new("The only true wisdom is in knowing you know nothing.", "Socrates", "en"),
|
||||
new("In the middle of every difficulty lies opportunity.", "Albert Einstein", "en"),
|
||||
new("What you get by achieving your goals is not as important as what you become by achieving your goals.", "Zig Ziglar", "en"),
|
||||
new("The purpose of life is a life of purpose.", "Robert Byrne", "en"),
|
||||
new("You become what you believe.", "Oprah Winfrey", "en"),
|
||||
new("The difference between a successful person and others is not a lack of strength, not a lack of knowledge, but rather a lack in will.", "Vince Lombardi", "en"),
|
||||
new("The only way to make sense out of change is to plunge into it, move with it, and join the dance.", "Alan Watts", "en"),
|
||||
new("Your work is going to fill a large part of your life, and the only way to be truly satisfied is to do what you believe is great work.", "Steve Jobs", "en"),
|
||||
new("The man who has confidence in himself gains the confidence of others.", "Hasidic Proverb", "en"),
|
||||
new("Courage is not the absence of fear, but rather the assessment that something else is more important than fear.", "Franklin D. Roosevelt", "en"),
|
||||
new("The best preparation for tomorrow is doing your best today.", "H. Jackson Brown Jr.", "en"),
|
||||
new("Believe in the power of your own voice. The more you use it, the stronger it becomes.", "Unknown", "en"),
|
||||
new("Opportunities don't happen, you create them.", "Chris Grosser", "en")
|
||||
];
|
||||
|
||||
[HttpGet]
|
||||
public ActionResult<List<FortuneSaying>> ListFortunes()
|
||||
{
|
||||
return Ok(Sayings);
|
||||
}
|
||||
|
||||
[HttpGet("random")]
|
||||
public ActionResult<List<FortuneSaying>> GetRandomFortunes([FromQuery] string? language)
|
||||
{
|
||||
var filteredSayings = string.IsNullOrEmpty(language)
|
||||
? Sayings
|
||||
: Sayings.Where(s => s.Language.Equals(language, StringComparison.OrdinalIgnoreCase)).ToArray();
|
||||
|
||||
if (filteredSayings.Length == 0)
|
||||
return NotFound("No fortunes found for the specified language.");
|
||||
|
||||
var random = new Random();
|
||||
var randomSaying = filteredSayings[random.Next(filteredSayings.Length)];
|
||||
|
||||
return Ok(new List<FortuneSaying> { randomSaying });
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,6 @@ public class NotableDay
|
||||
public Instant Date { get; set; }
|
||||
public string? LocalName { get; set; }
|
||||
public string? GlobalName { get; set; }
|
||||
public string? LocalizableKey { get; set; }
|
||||
public string? CountryCode { get; set; }
|
||||
public NotableHolidayType[] Holidays { get; set; } = [];
|
||||
|
||||
|
||||
@@ -77,52 +77,4 @@ public class NotableDaysController(NotableDaysService days) : ControllerBase
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{regionCode}/current")]
|
||||
public async Task<ActionResult<NotableDay?>> GetCurrentHoliday(string regionCode)
|
||||
{
|
||||
var result = await days.GetCurrentHoliday(regionCode);
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound("No holiday today");
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("me/current")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<NotableDay?>> GetAccountCurrentHoliday()
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
|
||||
|
||||
var region = currentUser.Region;
|
||||
if (string.IsNullOrWhiteSpace(region)) region = "us";
|
||||
|
||||
var result = await days.GetCurrentHoliday(region);
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound("No holiday today");
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{regionCode}/recent")]
|
||||
public async Task<ActionResult<List<NotableDay>>> GetRecentNotableDay(string regionCode)
|
||||
{
|
||||
var result = await days.GetCurrentAndNextHoliday(regionCode);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("me/recent")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<List<NotableDay>>> GetAccountRecentNotableDay()
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not SnAccount currentUser) return Unauthorized();
|
||||
|
||||
var region = currentUser.Region;
|
||||
if (string.IsNullOrWhiteSpace(region)) region = "us";
|
||||
|
||||
var result = await days.GetCurrentAndNextHoliday(region);
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,123 +27,12 @@ public class NotableDaysService(ICacheService cache)
|
||||
var holidays = await holidayClient.GetHolidaysAsync(year.Value, regionCode);
|
||||
var days = holidays?.Select(NotableDay.FromNagerHoliday).ToList() ?? [];
|
||||
|
||||
// Add global holidays that are available for all regions
|
||||
var globalDays = GetGlobalHolidays(year.Value);
|
||||
foreach (var globalDay in globalDays.Where(globalDay =>
|
||||
!days.Any(d => d.Date.Equals(globalDay.Date) && d.GlobalName == globalDay.GlobalName)))
|
||||
{
|
||||
days.Add(globalDay);
|
||||
}
|
||||
|
||||
// Cache the result for 1 day (holiday data doesn't change frequently)
|
||||
await cache.SetAsync(cacheKey, days, TimeSpan.FromDays(1));
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
private static List<NotableDay> GetGlobalHolidays(int year)
|
||||
{
|
||||
var globalDays = new List<NotableDay>();
|
||||
|
||||
// Christmas Day - December 25
|
||||
var christmas = new NotableDay
|
||||
{
|
||||
Date = Instant.FromDateTimeUtc(new DateTime(year, 12, 25, 0, 0, 0, DateTimeKind.Utc)),
|
||||
LocalName = "Christmas",
|
||||
GlobalName = "Christmas",
|
||||
LocalizableKey = "Christmas",
|
||||
CountryCode = null,
|
||||
Holidays = [NotableHolidayType.Public]
|
||||
};
|
||||
globalDays.Add(christmas);
|
||||
|
||||
// New Year's Day - January 1
|
||||
var newYear = new NotableDay
|
||||
{
|
||||
Date = Instant.FromDateTimeUtc(new DateTime(year, 1, 1, 0, 0, 0, DateTimeKind.Utc)),
|
||||
LocalName = "New Year's Day",
|
||||
GlobalName = "New Year's Day",
|
||||
LocalizableKey = "NewYear",
|
||||
CountryCode = null,
|
||||
Holidays = [NotableHolidayType.Public]
|
||||
};
|
||||
globalDays.Add(newYear);
|
||||
|
||||
// Valentine's Day - February 14
|
||||
var valentine = new NotableDay
|
||||
{
|
||||
Date = Instant.FromDateTimeUtc(new DateTime(year, 2, 14, 0, 0, 0, DateTimeKind.Utc)),
|
||||
LocalName = "Valentine's Day",
|
||||
GlobalName = "Valentine's Day",
|
||||
LocalizableKey = "ValentineDay",
|
||||
CountryCode = null,
|
||||
Holidays = [NotableHolidayType.Observance]
|
||||
};
|
||||
globalDays.Add(valentine);
|
||||
|
||||
// April Fools' Day - April 1
|
||||
var aprilFools = new NotableDay
|
||||
{
|
||||
Date = Instant.FromDateTimeUtc(new DateTime(year, 4, 1, 0, 0, 0, DateTimeKind.Utc)),
|
||||
LocalName = "April Fools' Day",
|
||||
GlobalName = "April Fools' Day",
|
||||
LocalizableKey = "AprilFoolsDay",
|
||||
CountryCode = null,
|
||||
Holidays = [NotableHolidayType.Observance]
|
||||
};
|
||||
globalDays.Add(aprilFools);
|
||||
|
||||
// International Workers' Day - May 1
|
||||
var workersDay = new NotableDay
|
||||
{
|
||||
Date = Instant.FromDateTimeUtc(new DateTime(year, 5, 1, 0, 0, 0, DateTimeKind.Utc)),
|
||||
LocalName = "International Workers' Day",
|
||||
GlobalName = "International Workers' Day",
|
||||
LocalizableKey = "WorkersDay",
|
||||
CountryCode = null,
|
||||
Holidays = [NotableHolidayType.Public]
|
||||
};
|
||||
globalDays.Add(workersDay);
|
||||
|
||||
// Children's Day - June 1
|
||||
var childrenDay = new NotableDay
|
||||
{
|
||||
Date = Instant.FromDateTimeUtc(new DateTime(year, 6, 1, 0, 0, 0, DateTimeKind.Utc)),
|
||||
LocalName = "Children's Day",
|
||||
GlobalName = "Children's Day",
|
||||
LocalizableKey = "ChildrenDay",
|
||||
CountryCode = null,
|
||||
Holidays = [NotableHolidayType.Public]
|
||||
};
|
||||
globalDays.Add(childrenDay);
|
||||
|
||||
// World Environment Day - June 5
|
||||
var environmentDay = new NotableDay
|
||||
{
|
||||
Date = Instant.FromDateTimeUtc(new DateTime(year, 6, 5, 0, 0, 0, DateTimeKind.Utc)),
|
||||
LocalName = "World Environment Day",
|
||||
GlobalName = "World Environment Day",
|
||||
LocalizableKey = "EnvironmentDay",
|
||||
CountryCode = null,
|
||||
Holidays = [NotableHolidayType.Observance]
|
||||
};
|
||||
globalDays.Add(environmentDay);
|
||||
|
||||
// Halloween - October 31
|
||||
var halloween = new NotableDay
|
||||
{
|
||||
Date = Instant.FromDateTimeUtc(new DateTime(year, 10, 31, 0, 0, 0, DateTimeKind.Utc)),
|
||||
LocalName = "Halloween",
|
||||
GlobalName = "Halloween",
|
||||
LocalizableKey = "Halloween",
|
||||
CountryCode = null,
|
||||
Holidays = [NotableHolidayType.Observance]
|
||||
};
|
||||
globalDays.Add(halloween);
|
||||
|
||||
return globalDays;
|
||||
}
|
||||
|
||||
public async Task<NotableDay?> GetNextHoliday(string regionCode)
|
||||
{
|
||||
var currentDate = SystemClock.Instance.GetCurrentInstant();
|
||||
@@ -163,37 +52,4 @@ public class NotableDaysService(ICacheService cache)
|
||||
|
||||
return nextHoliday;
|
||||
}
|
||||
|
||||
public async Task<NotableDay?> GetCurrentHoliday(string regionCode)
|
||||
{
|
||||
var currentDate = SystemClock.Instance.GetCurrentInstant();
|
||||
var currentYear = currentDate.InUtc().Year;
|
||||
|
||||
var currentYearHolidays = await GetNotableDays(currentYear, regionCode);
|
||||
|
||||
// Find the holiday that is today
|
||||
var todayHoliday = currentYearHolidays
|
||||
.FirstOrDefault(day => day.Date.InUtc().Date == currentDate.InUtc().Date);
|
||||
|
||||
return todayHoliday;
|
||||
}
|
||||
|
||||
public async Task<List<NotableDay>> GetCurrentAndNextHoliday(string regionCode)
|
||||
{
|
||||
var result = new List<NotableDay>();
|
||||
|
||||
var current = await GetCurrentHoliday(regionCode);
|
||||
if (current != null)
|
||||
{
|
||||
result.Add(current);
|
||||
}
|
||||
|
||||
var next = await GetNextHoliday(regionCode);
|
||||
if (next != null && (current == null || !next.Date.Equals(current.Date)))
|
||||
{
|
||||
result.Add(next);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
|
||||
var userId = currentUser.Id;
|
||||
|
||||
var query = db.AccountRelationships.AsQueryable()
|
||||
.OrderByDescending(r => r.CreatedAt)
|
||||
.Where(r => r.RelatedId == userId);
|
||||
var totalCount = await query.CountAsync();
|
||||
var relationships = await query
|
||||
@@ -36,9 +35,8 @@ public class RelationshipController(AppDatabase db, RelationshipService rels) :
|
||||
.Where(r => r.AccountId == userId)
|
||||
.ToDictionaryAsync(r => r.RelatedId);
|
||||
foreach (var relationship in relationships)
|
||||
relationship.Status = statuses.TryGetValue(relationship.AccountId, out var status)
|
||||
? status.Status
|
||||
: RelationshipStatus.Pending;
|
||||
if (statuses.TryGetValue(relationship.RelatedId, out var status))
|
||||
relationship.Status = status.Status;
|
||||
|
||||
Response.Headers["X-Total"] = totalCount.ToString();
|
||||
|
||||
|
||||
@@ -52,8 +52,7 @@ public class RelationshipService(
|
||||
return relationship;
|
||||
}
|
||||
|
||||
public async Task<SnAccountRelationship> CreateRelationship(SnAccount sender, SnAccount target,
|
||||
RelationshipStatus status)
|
||||
public async Task<SnAccountRelationship> CreateRelationship(SnAccount sender, SnAccount target, RelationshipStatus status)
|
||||
{
|
||||
if (status == RelationshipStatus.Pending)
|
||||
throw new InvalidOperationException(
|
||||
@@ -170,14 +169,12 @@ public class RelationshipService(
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
await PurgeRelationshipCache(relationship.AccountId, relationship.RelatedId, RelationshipStatus.Friends,
|
||||
status);
|
||||
await PurgeRelationshipCache(relationship.AccountId, relationship.RelatedId, RelationshipStatus.Friends, status);
|
||||
|
||||
return relationshipBackward;
|
||||
}
|
||||
|
||||
public async Task<SnAccountRelationship> UpdateRelationship(Guid accountId, Guid relatedId,
|
||||
RelationshipStatus status)
|
||||
public async Task<SnAccountRelationship> UpdateRelationship(Guid accountId, Guid relatedId, RelationshipStatus status)
|
||||
{
|
||||
var relationship = await GetRelationship(accountId, relatedId);
|
||||
if (relationship is null) throw new ArgumentException("There is no relationship between you and the user.");
|
||||
@@ -192,26 +189,24 @@ public class RelationshipService(
|
||||
return relationship;
|
||||
}
|
||||
|
||||
public async Task<List<Guid>> ListAccountFriends(SnAccount account, bool isRelated = false)
|
||||
public async Task<List<Guid>> ListAccountFriends(SnAccount account)
|
||||
{
|
||||
return await ListAccountFriends(account.Id, isRelated);
|
||||
return await ListAccountFriends(account.Id);
|
||||
}
|
||||
|
||||
public async Task<List<Guid>> ListAccountFriends(Guid accountId, bool isRelated = false)
|
||||
public async Task<List<Guid>> ListAccountFriends(Guid accountId)
|
||||
{
|
||||
return await GetCachedRelationships(accountId, RelationshipStatus.Friends, UserFriendsCacheKeyPrefix,
|
||||
isRelated);
|
||||
return await GetCachedRelationships(accountId, RelationshipStatus.Friends, UserFriendsCacheKeyPrefix);
|
||||
}
|
||||
|
||||
public async Task<List<Guid>> ListAccountBlocked(SnAccount account, bool isRelated = false)
|
||||
public async Task<List<Guid>> ListAccountBlocked(SnAccount account)
|
||||
{
|
||||
return await ListAccountBlocked(account.Id, isRelated);
|
||||
return await ListAccountBlocked(account.Id);
|
||||
}
|
||||
|
||||
public async Task<List<Guid>> ListAccountBlocked(Guid accountId, bool isRelated = false)
|
||||
public async Task<List<Guid>> ListAccountBlocked(Guid accountId)
|
||||
{
|
||||
return await GetCachedRelationships(accountId, RelationshipStatus.Blocked, UserBlockedCacheKeyPrefix,
|
||||
isRelated);
|
||||
return await GetCachedRelationships(accountId, RelationshipStatus.Blocked, UserBlockedCacheKeyPrefix);
|
||||
}
|
||||
|
||||
public async Task<bool> HasRelationshipWithStatus(Guid accountId, Guid relatedId,
|
||||
@@ -221,32 +216,28 @@ public class RelationshipService(
|
||||
return relationship is not null;
|
||||
}
|
||||
|
||||
private async Task<List<Guid>> GetCachedRelationships(
|
||||
Guid accountId,
|
||||
RelationshipStatus status,
|
||||
string cachePrefix,
|
||||
bool isRelated = false
|
||||
)
|
||||
private async Task<List<Guid>> GetCachedRelationships(Guid accountId, RelationshipStatus status, string cachePrefix)
|
||||
{
|
||||
if (accountId == Guid.Empty)
|
||||
throw new ArgumentException("Account ID cannot be empty.");
|
||||
|
||||
var cacheKey = $"{cachePrefix}{accountId}:{isRelated}";
|
||||
var cacheKey = $"{cachePrefix}{accountId}";
|
||||
var relationships = await cache.GetAsync<List<Guid>>(cacheKey);
|
||||
|
||||
if (relationships != null) return relationships;
|
||||
var now = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
||||
var query = db.AccountRelationships
|
||||
.Where(r => isRelated ? r.RelatedId == accountId : r.AccountId == accountId)
|
||||
.Where(r => r.Status == status)
|
||||
.Where(r => r.ExpiredAt == null || r.ExpiredAt > now)
|
||||
.Select(r => isRelated ? r.AccountId : r.RelatedId);
|
||||
if (relationships == null)
|
||||
{
|
||||
var now = Instant.FromDateTimeUtc(DateTime.UtcNow);
|
||||
relationships = await db.AccountRelationships
|
||||
.Where(r => r.RelatedId == accountId)
|
||||
.Where(r => r.Status == status)
|
||||
.Where(r => r.ExpiredAt == null || r.ExpiredAt > now)
|
||||
.Select(r => r.AccountId)
|
||||
.ToListAsync();
|
||||
|
||||
relationships = await query.ToListAsync();
|
||||
await cache.SetAsync(cacheKey, relationships, CacheExpiration);
|
||||
}
|
||||
|
||||
await cache.SetAsync(cacheKey, relationships, CacheExpiration);
|
||||
|
||||
return relationships;
|
||||
return relationships ?? new List<Guid>();
|
||||
}
|
||||
|
||||
private async Task PurgeRelationshipCache(Guid accountId, Guid relatedId, params RelationshipStatus[] statuses)
|
||||
@@ -273,4 +264,4 @@ public class RelationshipService(
|
||||
var removeTasks = keysToRemove.Select(key => cache.RemoveAsync(key));
|
||||
await Task.WhenAll(removeTasks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +63,6 @@ public class AppDatabase(
|
||||
|
||||
public DbSet<SnAffiliationSpell> AffiliationSpells { get; set; } = null!;
|
||||
public DbSet<SnAffiliationResult> AffiliationResults { get; set; } = null!;
|
||||
|
||||
public DbSet<SnRewindPoint> RewindPoints { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
|
||||
@@ -8,9 +8,9 @@ using System.Text.Json.Serialization;
|
||||
using System.Web;
|
||||
using DysonNetwork.Pass.Auth.OidcProvider.Options;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using NodaTime;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace DysonNetwork.Pass.Auth.OidcProvider.Controllers;
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using NodaTime;
|
||||
using AccountContactType = DysonNetwork.Shared.Models.AccountContactType;
|
||||
using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames;
|
||||
|
||||
namespace DysonNetwork.Pass.Auth.OidcProvider.Services;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace DysonNetwork.Pass.Auth.OpenId;
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
# Stage 1: Base runtime image
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
libkrb5-3 \
|
||||
libgssapi-krb5-2 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
USER $APP_UID
|
||||
WORKDIR /app
|
||||
EXPOSE 8080
|
||||
|
||||
@@ -7,23 +7,24 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.76.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.1">
|
||||
<PackageReference Include="Grpc.AspNetCore.Server" Version="2.71.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Nager.Holiday" Version="1.0.1" />
|
||||
<PackageReference Include="NodaTime" Version="3.2.3" />
|
||||
<PackageReference Include="NodaTime" Version="3.2.2" />
|
||||
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.2.0" />
|
||||
<PackageReference Include="NodaTime.Serialization.Protobuf" Version="2.0.2" />
|
||||
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.3.0" />
|
||||
<PackageReference Include="OpenGraph-Net" Version="4.0.1" />
|
||||
<PackageReference Include="Otp.NET" Version="1.4.1" />
|
||||
<PackageReference Include="Otp.NET" Version="1.4.0" />
|
||||
<PackageReference Include="Quartz" Version="3.15.1" />
|
||||
<PackageReference Include="Quartz.AspNetCore" Version="3.15.1" />
|
||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.15.1" />
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="EFCore.BulkExtensions.PostgreSql" Version="9.0.2" />
|
||||
<PackageReference Include="SpotifyAPI.Web" Version="7.2.1" />
|
||||
<PackageReference Include="SteamWebAPI2" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
@@ -131,8 +132,4 @@
|
||||
<AdditionalFiles Include="Resources\Emails\PasswordResetEmail.razor" />
|
||||
<AdditionalFiles Include="Resources\Emails\RegistrationConfirmEmail.razor" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Migrations\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using EFCore.BulkExtensions;
|
||||
using NodaTime;
|
||||
using Quartz;
|
||||
|
||||
@@ -13,13 +14,12 @@ public class ActionLogFlushHandler(IServiceProvider sp) : IFlushHandler<SnAction
|
||||
var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();
|
||||
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
foreach (var item in items)
|
||||
await db.BulkInsertAsync(items.Select(x =>
|
||||
{
|
||||
item.CreatedAt = now;
|
||||
item.UpdatedAt = now;
|
||||
}
|
||||
db.ActionLogs.AddRange(items);
|
||||
await db.SaveChangesAsync();
|
||||
x.CreatedAt = now;
|
||||
x.UpdatedAt = x.CreatedAt;
|
||||
return x;
|
||||
}), config => config.ConflictOption = ConflictOption.Ignore);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using DysonNetwork.Pass.Wallet;
|
||||
using DysonNetwork.Shared.Cache;
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ReinitalMigration : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "background_id",
|
||||
table: "account_profiles");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "picture_id",
|
||||
table: "account_profiles");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "background_id",
|
||||
table: "account_profiles",
|
||||
type: "character varying(32)",
|
||||
maxLength: 32,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "picture_id",
|
||||
table: "account_profiles",
|
||||
type: "character varying(32)",
|
||||
maxLength: 32,
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveNotification : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "notification_push_subscriptions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "notifications");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "notification_push_subscriptions",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
device_id = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
device_token = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
last_used_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
provider = table.Column<int>(type: "integer", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_notification_push_subscriptions", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_notification_push_subscriptions_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "notifications",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
content = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
priority = table.Column<int>(type: "integer", nullable: false),
|
||||
subtitle = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
||||
title = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
topic = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
viewed_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_notifications", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_notifications_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_notification_push_subscriptions_account_id",
|
||||
table: "notification_push_subscriptions",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_notification_push_subscriptions_device_token_device_id_acco",
|
||||
table: "notification_push_subscriptions",
|
||||
columns: new[] { "device_token", "device_id", "account_id" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_notifications_account_id",
|
||||
table: "notifications",
|
||||
column: "account_id");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCheckInBackdated : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Instant>(
|
||||
name: "backdated_from",
|
||||
table: "account_check_in_results",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "backdated_from",
|
||||
table: "account_check_in_results");
|
||||
}
|
||||
}
|
||||
}
|
||||
130
DysonNetwork.Pass/Migrations/20250807162919_RemoveDevelopers.cs
Normal file
130
DysonNetwork.Pass/Migrations/20250807162919_RemoveDevelopers.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using DysonNetwork.Shared.Models;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveDevelopers : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "fk_auth_sessions_custom_apps_app_id",
|
||||
table: "auth_sessions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "custom_app_secrets");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "custom_apps");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "ix_auth_sessions_app_id",
|
||||
table: "auth_sessions");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "punishments",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
reason = table.Column<string>(type: "character varying(8192)", maxLength: 8192, nullable: false),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
blocked_permissions = table.Column<List<string>>(type: "jsonb", nullable: true),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_punishments", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_punishments_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_punishments_account_id",
|
||||
table: "punishments",
|
||||
column: "account_id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "punishments");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "custom_apps",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
background = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
picture = table.Column<SnCloudFileReferenceObject>(type: "jsonb", nullable: true),
|
||||
slug = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
status = table.Column<int>(type: "integer", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
verification = table.Column<SnVerificationMark>(type: "jsonb", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_custom_apps", x => x.id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "custom_app_secrets",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
app_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: true),
|
||||
expired_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
is_oidc = table.Column<bool>(type: "boolean", nullable: false),
|
||||
secret = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_custom_app_secrets", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_custom_app_secrets_custom_apps_app_id",
|
||||
column: x => x.app_id,
|
||||
principalTable: "custom_apps",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_sessions_app_id",
|
||||
table: "auth_sessions",
|
||||
column: "app_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_custom_app_secrets_app_id",
|
||||
table: "custom_app_secrets",
|
||||
column: "app_id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "fk_auth_sessions_custom_apps_app_id",
|
||||
table: "auth_sessions",
|
||||
column: "app_id",
|
||||
principalTable: "custom_apps",
|
||||
principalColumn: "id");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddProfileLinks : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Dictionary<string, string>>(
|
||||
name: "links",
|
||||
table: "account_profiles",
|
||||
type: "jsonb",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "links",
|
||||
table: "account_profiles");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPublicContact : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "is_public",
|
||||
table: "account_contacts",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "is_public",
|
||||
table: "account_contacts");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddAuthorizeDevice : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "device_id",
|
||||
table: "auth_challenges",
|
||||
type: "character varying(1024)",
|
||||
maxLength: 1024,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(256)",
|
||||
oldMaxLength: 256,
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "client_id",
|
||||
table: "auth_challenges",
|
||||
type: "uuid",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "auth_clients",
|
||||
columns: table => new
|
||||
{
|
||||
id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
device_name = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
device_label = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
|
||||
device_id = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: false),
|
||||
account_id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
deleted_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("pk_auth_clients", x => x.id);
|
||||
table.ForeignKey(
|
||||
name: "fk_auth_clients_accounts_account_id",
|
||||
column: x => x.account_id,
|
||||
principalTable: "accounts",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_challenges_client_id",
|
||||
table: "auth_challenges",
|
||||
column: "client_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_clients_account_id",
|
||||
table: "auth_clients",
|
||||
column: "account_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_clients_device_id",
|
||||
table: "auth_clients",
|
||||
column: "device_id",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "fk_auth_challenges_auth_clients_client_id",
|
||||
table: "auth_challenges",
|
||||
column: "client_id",
|
||||
principalTable: "auth_clients",
|
||||
principalColumn: "id");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "fk_auth_challenges_auth_clients_client_id",
|
||||
table: "auth_challenges");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "auth_clients");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "ix_auth_challenges_client_id",
|
||||
table: "auth_challenges");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "client_id",
|
||||
table: "auth_challenges");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "device_id",
|
||||
table: "auth_challenges",
|
||||
type: "character varying(256)",
|
||||
maxLength: 256,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(1024)",
|
||||
oldMaxLength: 1024,
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddAuthDevicePlatform : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "platform",
|
||||
table: "auth_challenges");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "platform",
|
||||
table: "auth_clients",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "platform",
|
||||
table: "auth_clients");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "platform",
|
||||
table: "auth_challenges",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveAuthClientIndex : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "ix_auth_clients_device_id",
|
||||
table: "auth_clients");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "ix_auth_clients_device_id",
|
||||
table: "auth_clients",
|
||||
column: "device_id",
|
||||
unique: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Pass.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveChallengeOldDeviceId : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "device_id",
|
||||
table: "auth_challenges");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "device_id",
|
||||
table: "auth_challenges",
|
||||
type: "character varying(1024)",
|
||||
maxLength: 1024,
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user