Skip to content

Commit ee4eae7

Browse files
committed
Add minio client
1 parent 2d84a2f commit ee4eae7

30 files changed

+515
-43
lines changed

.env.example

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,12 @@ LOCAL_2=127.0.0.1
2828
# Redis variables
2929
#############################################
3030
REDIS_HOST=redis_server
31-
REDIS_PORT=6379
31+
REDIS_PORT=6379
32+
33+
#############################################
34+
# Minio variables
35+
#############################################
36+
MINIO_URL=storage.localhost
37+
MINIO_BUCKET=fastapi-minio
38+
MINIO_ROOT_USER=minioadmin
39+
MINIO_ROOT_PASSWORD=minioadmin

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ dmypy.json
132132

133133
db_docker
134134
pgadmin
135+
minio
135136

136137
.sonarqube/*
137138
sonarqube

README.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ You can connect to the Database using PGAdmin4 and use the credentials from .env
3030

3131
## ERD Database model
3232
<p align="center">
33-
<img src="static/erd.jpg" align="center"/>
33+
<img src="static/erd.png" align="center"/>
3434
</p>
3535

3636
## Containers architecture
@@ -48,9 +48,6 @@ As this project uses [traefik](https://doc.traefik.io/traefik/routing/routers/)
4848
<p align="center">
4949
<img src="static/2.png" align="center"/>
5050
</p>
51-
<p align="center">
52-
<img src="static/3.png" align="center"/>
53-
</p>
5451

5552
## Traefik Dashboard
5653
Traefik has been configurated as reverse proxy on the ingress of the project; you can access to Traefik Dashboard using the following link [http://traefik.localhost/](http://traefik.localhost/). You should use **username: test** and **pass: test**. If you want to change the password, you can find more information of how to do it [here](https://doc.traefik.io/traefik/operations/api/)
@@ -65,6 +62,14 @@ Traefik has been configurated as reverse proxy on the ingress of the project; yo
6562
## Static files
6663
All files on static folder will be server by nginx container as static files. You can check it with this link [http://nginx.localhost/1.png](http://nginx.localhost/1.png)
6764

65+
## Minio server
66+
This template allows that users can upload their photos. The images are stored and presigned using the open source Object Storage Service (OSS) [minio](https://min.io/), which allows to storade images in buckets in a secure way.
67+
- **Minio credentials ->** *username:* minioadmin and *password:* minioadmin
68+
69+
<p align="center">
70+
<img src="static/minio.png" align="center"/>
71+
</p>
72+
6873
## Run Alembic migrations (Only if you change the DB model)
6974

7075
```sh
@@ -106,7 +111,8 @@ docker compose up --build
106111
- [x] Add cache configuration using fastapi-cache2 and redis
107112
- [x] Create a global database pool of sessions to avoid to pass the session as dependency injection on each handle
108113
- [x] Refactor tablename to Pascal case
109-
- [ ] Add one to one relationship sample
114+
- [x] Add one to one relationship sample
115+
- [x] Add sample to upload images and store them using minio
110116
- [ ] Install pg_trgm by code and add a query for smart search of users by name
111117
- [ ] Add Enum sample column
112118
- [ ] Add jsonb field on table sample

docker-compose-dev.yml

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ services:
1313
env_file: ".env"
1414
depends_on:
1515
- database
16+
links:
17+
- traefik-proxy:storage.localhost
1618
labels:
1719
- "traefik.enable=true"
1820
- "traefik.http.routers.fastapi_local.entrypoints=web"
@@ -46,8 +48,8 @@ services:
4648
- "6379"
4749
env_file: .env
4850

49-
nginx:
50-
container_name: nginx
51+
nginx_server:
52+
container_name: nginx_server
5153
restart: unless-stopped
5254
logging:
5355
driver: none
@@ -65,6 +67,30 @@ services:
6567
- "traefik.http.routers.nginx_local.rule=Host(`nginx.${LOCAL_1}`, `nginx.${LOCAL_2}`, `nginx.${EXT_ENDPOINT1}`)"
6668
- traefik.http.services.nginx_local_service.loadbalancer.server.port=8080
6769

70+
minio_server:
71+
image: minio/minio:latest
72+
container_name: minio_server
73+
volumes:
74+
- ./minio/data:/data
75+
expose:
76+
- "9000"
77+
- "9091"
78+
environment:
79+
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
80+
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
81+
MINIO_BROWSER_REDIRECT_URL: http://stash.localhost
82+
command: "server /data --console-address ':9090'"
83+
labels:
84+
- traefik.enable=true
85+
- traefik.http.routers.minio.entrypoints=web
86+
- traefik.http.routers.minio.service=minio
87+
- traefik.http.routers.minio.rule=Host(`storage.${LOCAL_1}`, `storage.${LOCAL_2}`, `storage.${EXT_ENDPOINT1}`)
88+
- traefik.http.services.minio.loadbalancer.server.port=9000
89+
- traefik.http.routers.minio-console.entrypoints=web
90+
- traefik.http.routers.minio-console.service=minio-console
91+
- traefik.http.routers.minio-console.rule=Host(`stash.${LOCAL_1}`, `stash.${LOCAL_2}`, `stash.${EXT_ENDPOINT1}`)
92+
- traefik.http.services.minio-console.loadbalancer.server.port=9090
93+
6894
traefik-proxy:
6995
container_name: traefik-proxy
7096
restart: always
@@ -73,8 +99,6 @@ services:
7399
- "--providers.docker=true"
74100
ports:
75101
- "80:80"
76-
depends_on:
77-
- fastapi_server
78102
volumes:
79103
- "/var/run/docker.sock:/var/run/docker.sock:ro"
80104
- ./traefik/traefik.yml:/traefik.yml:ro

docker-compose.yml

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ services:
1010
- ./fastapi-alembic-sqlmodel-async:/code
1111
expose:
1212
- "8000"
13-
env_file: ".env"
14-
# depends_on:
15-
# - database
13+
env_file: ".env"
14+
links:
15+
- traefik-proxy:storage.localhost
1616
labels:
1717
- "traefik.enable=true"
1818
- "traefik.http.routers.fastapi_local.entrypoints=web"
@@ -65,6 +65,30 @@ services:
6565
- "traefik.http.routers.nginx_local.rule=Host(`nginx.${LOCAL_1}`, `nginx.${LOCAL_2}`, `nginx.${EXT_ENDPOINT1}`)"
6666
- traefik.http.services.nginx_local_service.loadbalancer.server.port=8080
6767

68+
minio:
69+
image: minio/minio:latest
70+
container_name: minio
71+
volumes:
72+
- ./minio/data:/data
73+
expose:
74+
- "9000"
75+
- "9091"
76+
environment:
77+
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
78+
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
79+
MINIO_BROWSER_REDIRECT_URL: http://stash.localhost
80+
command: "server /data --console-address ':9090'"
81+
labels:
82+
- traefik.enable=true
83+
- traefik.http.routers.minio.entrypoints=web
84+
- traefik.http.routers.minio.service=minio
85+
- traefik.http.routers.minio.rule=Host(`storage.${LOCAL_1}`, `storage.${LOCAL_2}`, `storage.${EXT_ENDPOINT1}`)
86+
- traefik.http.services.minio.loadbalancer.server.port=9000
87+
- traefik.http.routers.minio-console.entrypoints=web
88+
- traefik.http.routers.minio-console.service=minio-console
89+
- traefik.http.routers.minio-console.rule=Host(`stash.${LOCAL_1}`, `stash.${LOCAL_2}`, `stash.${EXT_ENDPOINT1}`)
90+
- traefik.http.services.minio-console.loadbalancer.server.port=9090
91+
6892
traefik-proxy:
6993
container_name: traefik-proxy
7094
restart: always
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""empty message
2+
3+
Revision ID: 3223652d21bd
4+
Revises: 60d49bf413b8
5+
Create Date: 2022-10-03 18:32:05.856873
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlalchemy_utils
11+
import sqlmodel # added
12+
13+
14+
# revision identifiers, used by Alembic.
15+
revision = '3223652d21bd'
16+
down_revision = '60d49bf413b8'
17+
branch_labels = None
18+
depends_on = None
19+
20+
21+
def upgrade():
22+
# ### commands auto generated by Alembic - please adjust! ###
23+
op.create_table('Media',
24+
sa.Column('title', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
25+
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
26+
sa.Column('path', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
27+
sa.Column('id', sqlmodel.sql.sqltypes.GUID(), nullable=False),
28+
sa.Column('updated_at', sa.DateTime(), nullable=True),
29+
sa.Column('created_at', sa.DateTime(), nullable=True),
30+
sa.PrimaryKeyConstraint('id')
31+
)
32+
op.create_index(op.f('ix_Media_id'), 'Media', ['id'], unique=False)
33+
op.create_table('ImageMedia',
34+
sa.Column('file_format', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
35+
sa.Column('width', sa.Integer(), nullable=True),
36+
sa.Column('height', sa.Integer(), nullable=True),
37+
sa.Column('id', sqlmodel.sql.sqltypes.GUID(), nullable=False),
38+
sa.Column('updated_at', sa.DateTime(), nullable=True),
39+
sa.Column('created_at', sa.DateTime(), nullable=True),
40+
sa.Column('media_id', sqlmodel.sql.sqltypes.GUID(), nullable=True),
41+
sa.ForeignKeyConstraint(['media_id'], ['Media.id'], ),
42+
sa.PrimaryKeyConstraint('id')
43+
)
44+
op.create_index(op.f('ix_ImageMedia_id'), 'ImageMedia', ['id'], unique=False)
45+
op.add_column('User', sa.Column('image_id', sqlmodel.sql.sqltypes.GUID(), nullable=True))
46+
op.create_foreign_key(None, 'User', 'ImageMedia', ['image_id'], ['id'])
47+
# ### end Alembic commands ###
48+
49+
50+
def downgrade():
51+
# ### commands auto generated by Alembic - please adjust! ###
52+
op.drop_constraint(None, 'User', type_='foreignkey')
53+
op.drop_column('User', 'image_id')
54+
op.drop_index(op.f('ix_ImageMedia_id'), table_name='ImageMedia')
55+
op.drop_table('ImageMedia')
56+
op.drop_index(op.f('ix_Media_id'), table_name='Media')
57+
op.drop_table('Media')
58+
# ### end Alembic commands ###

fastapi-alembic-sqlmodel-async/app/api/deps.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from typing import AsyncGenerator, List
22
from uuid import UUID
33
from fastapi import Depends, HTTPException, status
4+
from app.schemas.user_schema import IUserRead
5+
from app.utils.minio_client import MinioClient
46
from app.schemas.user_schema import IUserCreate
57
from fastapi.security import OAuth2PasswordBearer
68
from jose import jwt
@@ -12,6 +14,7 @@
1214
from app.db.session import SessionLocal
1315
from sqlmodel.ext.asyncio.session import AsyncSession
1416
from app.schemas.common_schema import IMetaGeneral
17+
from app.utils.minio_client import MinioClient
1518

1619
reusable_oauth2 = OAuth2PasswordBearer(
1720
tokenUrl=f"{settings.API_V1_STR}/login/access-token"
@@ -58,6 +61,14 @@ async def current_user(token: str = Depends(reusable_oauth2)) -> User:
5861

5962
return current_user
6063

64+
def minio_auth() -> MinioClient:
65+
minio_client = MinioClient(
66+
access_key=settings.MINIO_ROOT_USER,
67+
secret_key=settings.MINIO_ROOT_PASSWORD,
68+
bucket_name=settings.MINIO_BUCKET,
69+
minio_url=settings.MINIO_URL
70+
)
71+
return minio_client
6172

6273
async def user_exists(new_user: IUserCreate) -> IUserCreate:
6374
user = await crud.user.get_by_email(email=new_user.email)
@@ -67,10 +78,10 @@ async def user_exists(new_user: IUserCreate) -> IUserCreate:
6778
)
6879
return new_user
6980

70-
async def is_valid_user(user_id: UUID) -> UUID:
81+
async def is_valid_user(user_id: UUID) -> IUserRead:
7182
user = await crud.user.get(id=user_id)
7283
if not user:
7384
raise HTTPException(status_code=404, detail="User no found")
7485

75-
return user_id
86+
return user
7687

0 commit comments

Comments
 (0)