Compare commits

1 Commits
Author SHA1 Message Date
dev 6e70c65e14 Initial commit 2026-10-18 12:17:00 +08:00
1136 changed files with 2 additions and 22820 deletions
Vendored
BIN
View File
Binary file not shown.
-11
View File
@@ -1,11 +0,0 @@
.git
.gitea
.idea
.vscode
.DS_Store
*.log
tests
docs
web/node_modules
web/dist
coverage
-68
View File
@@ -1,68 +0,0 @@
name: Deploy olons-server production
on:
push:
branches:
- pro
workflow_dispatch:
concurrency:
group: production-olons-server
cancel-in-progress: false
permissions:
contents: read
env:
REGISTRY: git.olns.shop
IMAGE: git.olns.shop/pro/olons_server
SERVICE: olons-server
DEPLOY_HOST: 145.223.69.9
DEPLOY_USER: olns-deploy
jobs:
deploy:
runs-on: deploy-host
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- name: Log in to Gitea Container Registry
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
set -euo pipefail
printf '%s' "$REGISTRY_TOKEN" | docker login "$REGISTRY" -u admin --password-stdin
- name: Build and push image
env:
GITHUB_SHA: ${{ github.sha }}
run: |
set -euo pipefail
SHORT_SHA="$(printf '%.12s' "$GITHUB_SHA")"
docker build -t "${IMAGE}:pro" -t "${IMAGE}:${SHORT_SHA}" .
docker push "$IMAGE:pro"
docker push "$IMAGE:$SHORT_SHA"
- name: Deploy Compose service
env:
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
DEPLOY_KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
set -euo pipefail
install -d -m 700 "$HOME/.ssh"
KEY_FILE="$(mktemp "$HOME/.ssh/olns-deploy.XXXXXX")"
cleanup() {
shred -u "$KEY_FILE" 2>/dev/null || true
}
trap cleanup EXIT
printf '%s\n' "$DEPLOY_SSH_KEY" > "$KEY_FILE"
chmod 600 "$KEY_FILE"
printf '%s\n' "$DEPLOY_KNOWN_HOSTS" > "$HOME/.ssh/known_hosts"
chmod 600 "$HOME/.ssh/known_hosts"
SSH_ARGS=(-i "$KEY_FILE" -o BatchMode=yes -o StrictHostKeyChecking=yes)
printf '%s' "$REGISTRY_TOKEN" | ssh "${SSH_ARGS[@]}" "$DEPLOY_USER@$DEPLOY_HOST" \
"docker login git.olns.shop -u admin --password-stdin"
ssh "${SSH_ARGS[@]}" "$DEPLOY_USER@$DEPLOY_HOST" \
"cd /opt/olns && docker compose pull olons-server && docker compose up -d --no-deps --wait olons-server"
+1 -4
View File
@@ -3,7 +3,6 @@
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
bin/
*.exe
*.exe~
*.dll
@@ -25,9 +24,6 @@ go.work.sum
# env file
.env
.idea/
.DS_Store
storage/uploads/
# ---> Node
# Logs
@@ -166,3 +162,4 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
-32
View File
@@ -1,32 +0,0 @@
# ---- Stage 1: build the Vue frontend ----
FROM node:22-alpine AS web-builder
WORKDIR /web
RUN corepack enable
COPY web/package.json web/pnpm-lock.yaml web/pnpm-workspace.yaml ./
RUN pnpm install --frozen-lockfile
COPY web/ ./
ENV VITE_PUBLIC_PATH=/
RUN pnpm run build
# ---- Stage 2: build the Go backend ----
FROM golang:1.26 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
COPY --from=web-builder /web/dist ./web/dist
RUN CGO_ENABLED=0 GOOS=linux go build -o /out/olons_server ./cmd/olons_server
FROM alpine:3.22
WORKDIR /app
RUN apk add --no-cache tzdata
ENV OLONS_SERVER_CONFIG=/app/configs/olons_server.test.json
COPY --from=builder /out/olons_server /app/olons_server
COPY configs /app/configs
COPY --from=web-builder /web/dist /app/web/dist
ENV OLONS_SERVER_FRONTEND_DIR=/app/web/dist
EXPOSE 6061
ENTRYPOINT ["/app/olons_server"]
-16
View File
@@ -1,16 +0,0 @@
.PHONY: build test run web-install web-build
build:
go build -o bin/olons_server ./cmd/olons_server
test:
go test ./...
run:
OLONS_SERVER_CONFIG=configs/olons_server.local.json go run ./cmd/olons_server
web-install:
cd web && pnpm install --frozen-lockfile
web-build:
cd web && pnpm run build
+1 -82
View File
@@ -1,83 +1,2 @@
# Olons Server
# olons_server
移动端任务与账户平台后端。项目是独立应用,业务数据以 MySQL `olons_admin` 库中的 `hg_*` 表为唯一事实源。
项目现在采用前后端一体目录结构:`web/` 是 Vue 3 + Vite 移动端,Go 服务在生产环境通过网站根路径 `/` 提供构建后的静态资源,`/v1/` 继续提供业务 API。
## 当前接口
- `POST /v1/auth/register`
- `POST /v1/auth/login`
- `POST /v1/auth/logout`
- `GET /v1/me`
- `PATCH /v1/me/avatar`
- `PUT /v1/me/password`
- `PUT /v1/me/trade-password`
- `GET /v1/home`
- `GET /v1/banners`
- `GET /v1/contents/{content_key}`
- `GET /v1/member-levels`
- `GET /v1/task-halls`
- `GET /v1/tasks/eligibility`
- `GET /v1/tasks/stats`
- `POST /v1/task-orders/claim`
- `GET /v1/task-orders`
- `GET /v1/task-orders/{order_id}`
- `POST /v1/task-orders/{order_id}/submissions`
- `GET /v1/manual-products`
- `GET /v1/manual-products/{product_id}`
- `GET /v1/payment-accounts`
- `POST /v1/payment-accounts`
- `PUT /v1/payment-accounts`
- `GET /v1/payment-accounts/details`
- `POST /v1/withdrawal-orders/quote`
- `POST /v1/withdrawal-orders`
- `GET /v1/withdrawal-orders`
- `GET /v1/withdrawal-orders/{order_id}`
- `GET /healthz`
- `GET /metrics`
注册使用 `hg_app_member` 作为账户主档,并在同一事务中创建 `hg_app_member_profile` 和 `hg_wallet_account` 记录。登录按完整 `mobile` 查找会员,校验 bcrypt 密码后在 Redis 中创建独立会话。
## 启动配置
核心环境变量:
```bash
OLONS_SERVER_CONFIG=configs/olons_server.local.json
OLONS_SERVER_MYSQL_DSN='user:password@tcp(host:3306)/olons_admin?parseTime=true&charset=utf8mb4&loc=Local'
OLONS_SERVER_REDIS_ADDR='127.0.0.1:6379'
OLONS_SERVER_FINANCE_MASTER_KEY='<至少 32 个字符的固定资金安全密钥>'
OLONS_SERVER_API_KEYS='<API Key,多个以逗号分隔>'
```
`finance_master_key` 已在 JSON 配置中提供固定值,也可由 `OLONS_SERVER_FINANCE_MASTER_KEY` 覆盖。服务会从这个主密钥派生数据加密键和检索键,用于保护收款账户姓名、账号、银行卡预留手机号及取出快照等支付敏感信息。密钥必须与已有数据保持一致;会员登录手机号直接保存在 `hg_app_member.mobile`。
前端静态目录可通过 `OLONS_SERVER_FRONTEND_DIR` 覆盖,默认值为 `web/dist`。本地开发可分别启动前后端:
```bash
make web-install
make web-build
make run
```
生产镜像使用 Docker 多阶段构建自动安装前端依赖、构建 `web/`,并将 C 端产物部署到网站根路径 `/`。
## 请求头
```http
X-API-Key: <api key>
X-Request-ID: <request id>
X-Device-ID: <optional device id>
```
服务采用单租户模式,登录仅按手机号查找账号,不读取或接受租户环境变量。现有数据库中的 `tenant_id` 字段仅作为兼容字段保留,新注册数据固定写入 `0`。
## 验证
```bash
make test
make build
```
前后端完整对接契约见 [`docs/移动端任务与账户平台前后端对接文档.md`](docs/移动端任务与账户平台前后端对接文档.md),已实现的注册登录简版契约见 [`docs/注册登录对接文档.md`](docs/注册登录对接文档.md)。
-268
View File
@@ -1,268 +0,0 @@
package main
import (
"context"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha256"
"database/sql"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"log"
"os"
"regexp"
"strings"
"git.118im.com/im_team/olons_server/internal/config"
_ "github.com/go-sql-driver/mysql"
)
var phonePattern = regexp.MustCompile(`^1[3-9][0-9]{9}$`)
type memberPhone struct {
id int64
tenantID int64
ciphertext string
hash string
mask string
phone string
}
func main() {
cfg, err := config.Load()
if err != nil {
log.Fatal(err)
}
db, err := sql.Open("mysql", strings.TrimPrefix(cfg.MySQL.DSN, "mysql://"))
if err != nil {
log.Fatal(err)
}
defer db.Close()
ctx := context.Background()
hasMobile, err := columnExists(ctx, db, "mobile")
if err != nil {
log.Fatal(err)
}
hasCipher, err := columnExists(ctx, db, "mobile_cipher")
if err != nil {
log.Fatal(err)
}
if hasMobile && !hasCipher {
log.Print("hg_app_member already uses the mobile column")
return
}
if !hasCipher {
log.Fatal("mobile_cipher is missing before migration")
}
aeads := newAEADs(append(splitKeys(os.Getenv("OLONS_SERVER_MEMBER_MIGRATION_DATA_KEYS")), cfg.Auth.DataEncryptionKey))
lookupKeys := decodeKeys(append(splitKeys(os.Getenv("OLONS_SERVER_MEMBER_MIGRATION_LOOKUP_KEYS")), cfg.Auth.LookupHMACKey))
overrides, err := parseOverrides(os.Getenv("OLONS_SERVER_MEMBER_MIGRATION_OVERRIDES"))
if err != nil {
log.Fatal(err)
}
if len(aeads) == 0 && len(lookupKeys) == 0 {
log.Fatal("no valid historical phone keys were provided")
}
phones, err := loadPhones(ctx, db, aeads, lookupKeys, overrides)
if err != nil {
log.Fatal(err)
}
if err := validateUnique(phones); err != nil {
log.Fatal(err)
}
if !hasMobile {
if _, err := db.ExecContext(ctx, `ALTER TABLE hg_app_member ADD COLUMN mobile varchar(20) NULL AFTER username`); err != nil {
log.Fatal(err)
}
}
if err := updatePhones(ctx, db, phones); err != nil {
log.Fatal(err)
}
if _, err := db.ExecContext(ctx, `ALTER TABLE hg_app_member
MODIFY COLUMN mobile varchar(20) NOT NULL,
DROP INDEX uk_app_member_mobile_hash,
ADD UNIQUE KEY uk_app_member_mobile (tenant_id,mobile),
DROP COLUMN mobile_cipher,
DROP COLUMN mobile_hash,
DROP COLUMN mobile_mask`); err != nil {
log.Fatal(err)
}
log.Printf("migrated %d member phone numbers", len(phones))
}
func columnExists(ctx context.Context, db *sql.DB, column string) (bool, error) {
var count int
err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='hg_app_member' AND COLUMN_NAME=?`, column).Scan(&count)
return count == 1, err
}
func loadPhones(ctx context.Context, db *sql.DB, aeads []cipher.AEAD, lookupKeys [][]byte, overrides map[int64]string) ([]memberPhone, error) {
rows, err := db.QueryContext(ctx, `SELECT id,tenant_id,mobile_cipher,mobile_hash,mobile_mask FROM hg_app_member ORDER BY id`)
if err != nil {
return nil, err
}
defer rows.Close()
phones := make([]memberPhone, 0)
failed := make([]string, 0)
for rows.Next() {
var item memberPhone
if err := rows.Scan(&item.id, &item.tenantID, &item.ciphertext, &item.hash, &item.mask); err != nil {
return nil, err
}
item.phone = recoverPhone(item, aeads, lookupKeys)
if item.phone == "" {
item.phone = overrides[item.id]
}
if !phonePattern.MatchString(item.phone) {
failed = append(failed, fmt.Sprintf("%d", item.id))
} else if len(item.mask) == 11 && item.phone[:3]+"****"+item.phone[7:] != item.mask {
return nil, fmt.Errorf("member %d override does not match its phone mask", item.id)
}
phones = append(phones, item)
}
if err := rows.Err(); err != nil {
return nil, err
}
if len(failed) > 0 {
return nil, fmt.Errorf("phone recovery failed for member IDs: %s", strings.Join(failed, ","))
}
return phones, nil
}
func validateUnique(phones []memberPhone) error {
seen := make(map[string]int64, len(phones))
for _, item := range phones {
key := fmt.Sprintf("%d:%s", item.tenantID, item.phone)
if otherID, ok := seen[key]; ok {
return fmt.Errorf("members %d and %d have duplicate phone numbers", otherID, item.id)
}
seen[key] = item.id
}
return nil
}
func updatePhones(ctx context.Context, db *sql.DB, phones []memberPhone) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
for _, item := range phones {
result, err := tx.ExecContext(ctx, `UPDATE hg_app_member SET mobile=? WHERE id=? AND tenant_id=?`, item.phone, item.id, item.tenantID)
if err != nil {
return err
}
affected, err := result.RowsAffected()
if err != nil {
return err
}
if affected != 1 {
return fmt.Errorf("member %d update affected %d rows", item.id, affected)
}
}
return tx.Commit()
}
func splitKeys(value string) []string {
return strings.FieldsFunc(value, func(r rune) bool { return r == ',' || r == '\n' })
}
func parseOverrides(value string) (map[int64]string, error) {
result := make(map[int64]string)
for _, pair := range splitKeys(value) {
var id int64
var phone string
if _, err := fmt.Sscanf(pair, "%d=%s", &id, &phone); err != nil || id <= 0 || !phonePattern.MatchString(phone) {
return nil, fmt.Errorf("invalid member phone override")
}
result[id] = phone
}
return result, nil
}
func newAEADs(encodedKeys []string) []cipher.AEAD {
result := make([]cipher.AEAD, 0, len(encodedKeys))
for _, encodedKey := range encodedKeys {
key, err := decodeKey(encodedKey)
if err != nil || len(key) != 32 {
continue
}
block, err := aes.NewCipher(key)
if err != nil {
continue
}
aead, err := cipher.NewGCM(block)
if err == nil {
result = append(result, aead)
}
}
return result
}
func decodeKeys(encodedKeys []string) [][]byte {
result := make([][]byte, 0, len(encodedKeys))
for _, encodedKey := range encodedKeys {
key, err := decodeKey(encodedKey)
if err == nil && len(key) >= 32 {
result = append(result, key)
}
}
return result
}
func decodeKey(value string) ([]byte, error) {
value = strings.TrimSpace(value)
if decoded, err := base64.StdEncoding.DecodeString(value); err == nil {
return decoded, nil
}
return base64.RawStdEncoding.DecodeString(value)
}
func recoverPhone(item memberPhone, aeads []cipher.AEAD, lookupKeys [][]byte) string {
for _, aead := range aeads {
for _, encoding := range []*base64.Encoding{base64.RawStdEncoding, base64.RawURLEncoding} {
plain, err := decrypt(aead, encoding, item.ciphertext)
phone := strings.TrimPrefix(plain, "+86")
if err == nil && phonePattern.MatchString(phone) {
return phone
}
}
}
if len(item.mask) != 11 || item.mask[3:7] != "****" {
return ""
}
for middle := 0; middle < 10000; middle++ {
phone := fmt.Sprintf("%s%04d%s", item.mask[:3], middle, item.mask[7:])
for _, value := range []string{phone, "+86" + phone} {
digest := sha256.Sum256([]byte(value))
if hmac.Equal([]byte(strings.ToLower(item.hash)), []byte(hex.EncodeToString(digest[:]))) {
return phone
}
}
for _, key := range lookupKeys {
for _, value := range []string{phone, "+86" + phone} {
mac := hmac.New(sha256.New, key)
_, _ = mac.Write([]byte(value))
if hmac.Equal([]byte(strings.ToLower(item.hash)), []byte(hex.EncodeToString(mac.Sum(nil)))) {
return phone
}
}
}
}
return ""
}
func decrypt(aead cipher.AEAD, encoding *base64.Encoding, value string) (string, error) {
raw, err := encoding.DecodeString(value)
if err != nil || len(raw) < aead.NonceSize() {
return "", errors.New("invalid ciphertext")
}
plain, err := aead.Open(nil, raw[:aead.NonceSize()], raw[aead.NonceSize():], nil)
return string(plain), err
}
-58
View File
@@ -1,58 +0,0 @@
package main
import (
"context"
"errors"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"git.118im.com/im_team/olons_server/internal/app"
"git.118im.com/im_team/olons_server/internal/config"
)
func main() {
cfg, err := config.Load()
if err != nil {
log.Fatalf("load config: %v", err)
}
server, err := app.New(cfg)
if err != nil {
log.Fatalf("init app: %v", err)
}
defer server.Close()
httpServer := &http.Server{
Addr: cfg.Listen,
Handler: server.Handler(),
ReadHeaderTimeout: 5 * time.Second,
}
errCh := make(chan error, 1)
go func() {
log.Printf("olons_server listening on %s", cfg.Listen)
if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
errCh <- err
}
}()
stopCh := make(chan os.Signal, 1)
signal.Notify(stopCh, syscall.SIGINT, syscall.SIGTERM)
select {
case err := <-errCh:
log.Fatalf("serve: %v", err)
case <-stopCh:
log.Printf("shutdown requested")
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := httpServer.Shutdown(ctx); err != nil {
log.Printf("shutdown error: %v", err)
}
}
-25
View File
@@ -1,25 +0,0 @@
{
"listen": ":6061",
"frontend_dir": "web/dist",
"mysql": {
"dsn": "root:root@tcp(192.168.1.135:3306)/olons_admin?parseTime=true&charset=utf8mb4&loc=Local",
"max_open_conns": 15,
"max_idle_conns": 5
},
"redis": {
"addr": "127.0.0.1:6379",
"db": 0
},
"upload": {
"dir": "storage/uploads",
"public_path": "/uploads"
},
"auth": {
"api_keys": [],
"session_key_pattern": "olons_server:session:token:%s",
"session_ttl_seconds": 1209600,
"finance_master_key": "olons-finance-master-key-v1-20261013",
"allow_dev_header_auth": true,
"required_api_key": false
}
}
-24
View File
@@ -1,24 +0,0 @@
{
"listen": ":6061",
"frontend_dir": "web/dist",
"mysql": {
"dsn": "",
"max_open_conns": 15,
"max_idle_conns": 5
},
"redis": {
"addr": "",
"db": 0
},
"upload": {
"dir": "storage/uploads",
"public_path": "/uploads"
},
"auth": {
"api_keys": [],
"session_key_pattern": "olons_server:session:token:%s",
"session_ttl_seconds": 1209600,
"finance_master_key": "olons-finance-master-key-v1-20261013",
"required_api_key": true
}
}
BIN
View File
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 685 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 842 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 700 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 476 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1018 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 730 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 810 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 479 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 848 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 887 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 562 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 549 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 455 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 522 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 645 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Some files were not shown because too many files have changed in this diff Show More