From abee8e7e60e5deaf0218578b104384bd258b0ef8 Mon Sep 17 00:00:00 2001
From: "amy.yang" <amy.yang@topibd.com>
Date: Fri, 9 Apr 2021 17:07:08 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=8E=E7=AB=AF=E7=AE=80=E5=8D=95?=
 =?UTF-8?q?=E6=8E=A5=E5=8F=A3v0.1(=E8=B7=9F=E5=89=8D=E7=AB=AF=E6=97=A0?=
 =?UTF-8?q?=E5=85=B3=E8=81=94=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 backend-demo/alembic.ini                      |  85 ++++++++++++++++++
 backend-demo/alembic/README                   |   1 +
 .../alembic/__pycache__/env.cpython-37.pyc    | Bin 0 -> 1738 bytes
 backend-demo/alembic/env.py                   |  83 +++++++++++++++++
 backend-demo/alembic/script.py.mako           |  24 +++++
 .../ac95fea8c6b2_message.cpython-37.pyc       | Bin 0 -> 1497 bytes
 .../alembic/versions/ac95fea8c6b2_message.py  |  53 +++++++++++
 backend-demo/api/__init__.py                  |   0
 .../api/__pycache__/__init__.cpython-37.pyc   | Bin 0 -> 146 bytes
 .../api/__pycache__/crud.cpython-37.pyc       | Bin 0 -> 1585 bytes
 .../api/__pycache__/database.cpython-37.pyc   | Bin 0 -> 443 bytes
 .../api/__pycache__/main.cpython-37.pyc       | Bin 0 -> 1754 bytes
 .../api/__pycache__/models.cpython-37.pyc     | Bin 0 -> 1032 bytes
 .../api/__pycache__/schemas.cpython-37.pyc    | Bin 0 -> 1575 bytes
 backend-demo/api/crud.py                      |  29 ++++++
 backend-demo/api/database.py                  |  10 +++
 backend-demo/api/main.py                      |  41 +++++++++
 backend-demo/api/models.py                    |  21 +++++
 backend-demo/api/schemas.py                   |  30 +++++++
 19 files changed, 377 insertions(+)
 create mode 100644 backend-demo/alembic.ini
 create mode 100644 backend-demo/alembic/README
 create mode 100644 backend-demo/alembic/__pycache__/env.cpython-37.pyc
 create mode 100644 backend-demo/alembic/env.py
 create mode 100644 backend-demo/alembic/script.py.mako
 create mode 100644 backend-demo/alembic/versions/__pycache__/ac95fea8c6b2_message.cpython-37.pyc
 create mode 100644 backend-demo/alembic/versions/ac95fea8c6b2_message.py
 create mode 100644 backend-demo/api/__init__.py
 create mode 100644 backend-demo/api/__pycache__/__init__.cpython-37.pyc
 create mode 100644 backend-demo/api/__pycache__/crud.cpython-37.pyc
 create mode 100644 backend-demo/api/__pycache__/database.cpython-37.pyc
 create mode 100644 backend-demo/api/__pycache__/main.cpython-37.pyc
 create mode 100644 backend-demo/api/__pycache__/models.cpython-37.pyc
 create mode 100644 backend-demo/api/__pycache__/schemas.cpython-37.pyc
 create mode 100644 backend-demo/api/crud.py
 create mode 100644 backend-demo/api/database.py
 create mode 100644 backend-demo/api/main.py
 create mode 100644 backend-demo/api/models.py
 create mode 100644 backend-demo/api/schemas.py

diff --git a/backend-demo/alembic.ini b/backend-demo/alembic.ini
new file mode 100644
index 0000000..4e90f53
--- /dev/null
+++ b/backend-demo/alembic.ini
@@ -0,0 +1,85 @@
+# A generic, single database configuration.
+
+[alembic]
+# path to migration scripts
+script_location = alembic
+
+# template used to generate migration files
+# file_template = %%(rev)s_%%(slug)s
+
+# timezone to use when rendering the date
+# within the migration file as well as the filename.
+# string value is passed to dateutil.tz.gettz()
+# leave blank for localtime
+# timezone =
+
+# max length of characters to apply to the
+# "slug" field
+# truncate_slug_length = 40
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+# set to 'true' to allow .pyc and .pyo files without
+# a source .py file to be detected as revisions in the
+# versions/ directory
+# sourceless = false
+
+# version location specification; this defaults
+# to alembic/versions.  When using multiple version
+# directories, initial revisions must be specified with --version-path
+# version_locations = %(here)s/bar %(here)s/bat alembic/versions
+
+# the output encoding used when revision files
+# are written from script.py.mako
+# output_encoding = utf-8
+
+sqlalchemy.url = postgresql://postgres:1@localhost:5432/antdemo
+
+
+[post_write_hooks]
+# post_write_hooks defines scripts or Python functions that are run
+# on newly generated revision scripts.  See the documentation for further
+# detail and examples
+
+# format using "black" - use the console_scripts runner, against the "black" entrypoint
+# hooks=black
+# black.type=console_scripts
+# black.entrypoint=black
+# black.options=-l 79
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/backend-demo/alembic/README b/backend-demo/alembic/README
new file mode 100644
index 0000000..98e4f9c
--- /dev/null
+++ b/backend-demo/alembic/README
@@ -0,0 +1 @@
+Generic single-database configuration.
\ No newline at end of file
diff --git a/backend-demo/alembic/__pycache__/env.cpython-37.pyc b/backend-demo/alembic/__pycache__/env.cpython-37.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..dfa7eb196dae652248271bdda08a1fffbd8ce49f
GIT binary patch
literal 1738
zcmZuyPjBor5Ra2=lFe>+w|#AST)^V;-b!f286os&0qIGodX$Gi5mInt@2(fe3AVR?
zp4wBnaH!se53uEi#3zclAr5>6PK=%1w){!t@pxuDGnx6>IoR4tJ$U~3Nq+lV;(5Q@
z#pa2Cc?LuO3WRzT39m#MLYDW1Uj|v=_yG7Y3mqSdP8ns9<2xeGVz7-wQg*X$nPw^S
zSiI=1`YeUF%eJnOeQ#Tzu^5!YYzQ{dVw8;-Vxz_OHR#xIv6DTb3GM#iWxF(`J(%}c
zwA#P*)X!}9+C!d<*q)tf=L>J#{}VFy#;8fBT(HARPWcR0J50{FWcgH8WnOHgVO>=M
zv@u8wJ2yvTzlq6-u8Em9G382<l7T)vAet4{v3(GXE{sDM`WX=8tq_Gzx$=y^2rfMn
zQlADi<cM}|pg6`~b#A@a1II<T;NUAL&F9{+_chFrbWA=4r^ic)OFmP?@JedTC4N#(
zrvlP`g3F4sNt$AKUe3A3?I$c1)7Z?JUE2Kd88>shSZad_{_^-UXHO)>vI2?3pSU8t
z3tOt?Y<@4n;E@yss|`63%<Y;p#W2zMj0rKp_}~KT?|5BvIdcJ9Pq<LbW$U(_F-$A@
z#9(&HB$kX($l~LJk6t{-rot!S?v!w0BdovVkO%?!SDW-F?3@)#!=8?@3(rb!T(lXt
z9<@;}d}U=WqAnD#4X&1^UYfPjR|EaMAflMF@?x@7;$@T8M3GXP3&EO>?I<0GO#rH9
zXo#9Ilb6g8N(^bX1UF0(k)Lo$b<?Mu2v(T9s*OGx$4%7My&1Y)giEV|ZmABypy}Rq
zMfL4^`-B1La-)dUq_B3)KrLl{-{dh;J+S=6Q@b$zHGpX42`|23@^n&PH2eRw&ew2w
z2W<5Sh}Tb1h<x|<gA}QKklo?bhlM@j4yPF~#xrQ;8?*>+0k0-pbf|w5TzQw?3Q+m_
zy(?tmMRJKumj*XJpfz5lYuv`G-Ws=Ii(5eOnxi2gxby1A|AVjm4|<<V=pMjL7fcex
zD*#vv6$=B4CV<HXsCV!sT2}>kI*SzvErDXTXnz3z4FY-(5CA7Ssui2^^QLQ0dm+FX
z2-AB%u31Opz6zj5Y6zt1+5W<jcRkx_+nRI9H!>AktK?`Y#0zlhE)biQ!-3kiek=mi
zjwO$PK*ZiUqCEiWO{=gyZ^<5T&ECJe?W*@asI>Lt#Xd@*)hRSObX2_!((xPFKvXjT
z+HBG$s%+z%^u7q~hq;vFHNslJ2Kqv0aiu5r!c+FrSFzn2x(hHOb<HGgl03I}X`W{(
zsrkfSMM5k1Wvq)eTGz)J+4I#Tn*-HW!J^XT+6=kgfH=2)9+bcp!_^`A08~rCKC~CD
eehWz7?;{v~f=1}E-w&fOjiMxZoa`qk@&5(|Cg7X^

literal 0
HcmV?d00001

diff --git a/backend-demo/alembic/env.py b/backend-demo/alembic/env.py
new file mode 100644
index 0000000..a7ea42b
--- /dev/null
+++ b/backend-demo/alembic/env.py
@@ -0,0 +1,83 @@
+from logging.config import fileConfig
+
+from sqlalchemy import engine_from_config
+from sqlalchemy import pool
+
+from alembic import context
+
+import sys                                             
+from os.path import abspath, dirname                   
+sys.path.append(dirname(dirname(abspath(__file__))))  # 将项目路径引入
+
+from api.models import Base
+
+# this is the Alembic Config object, which provides
+# access to the values within the .ini file in use.
+config = context.config
+
+# Interpret the config file for Python logging.
+# This line sets up loggers basically.
+fileConfig(config.config_file_name)
+
+# add your model's MetaData object here
+# for 'autogenerate' support
+# from myapp import mymodel
+# target_metadata = mymodel.Base.metadata
+target_metadata = Base.metadata
+
+# other values from the config, defined by the needs of env.py,
+# can be acquired:
+# my_important_option = config.get_main_option("my_important_option")
+# ... etc.
+
+
+def run_migrations_offline():
+    """Run migrations in 'offline' mode.
+
+    This configures the context with just a URL
+    and not an Engine, though an Engine is acceptable
+    here as well.  By skipping the Engine creation
+    we don't even need a DBAPI to be available.
+
+    Calls to context.execute() here emit the given string to the
+    script output.
+
+    """
+    url = config.get_main_option("sqlalchemy.url")
+    context.configure(
+        url=url,
+        target_metadata=target_metadata,
+        literal_binds=True,
+        dialect_opts={"paramstyle": "named"},
+    )
+
+    with context.begin_transaction():
+        context.run_migrations()
+
+
+def run_migrations_online():
+    """Run migrations in 'online' mode.
+
+    In this scenario we need to create an Engine
+    and associate a connection with the context.
+
+    """
+    connectable = engine_from_config(
+        config.get_section(config.config_ini_section),
+        prefix="sqlalchemy.",
+        poolclass=pool.NullPool,
+    )
+
+    with connectable.connect() as connection:
+        context.configure(
+            connection=connection, target_metadata=target_metadata
+        )
+
+        with context.begin_transaction():
+            context.run_migrations()
+
+
+if context.is_offline_mode():
+    run_migrations_offline()
+else:
+    run_migrations_online()
diff --git a/backend-demo/alembic/script.py.mako b/backend-demo/alembic/script.py.mako
new file mode 100644
index 0000000..2c01563
--- /dev/null
+++ b/backend-demo/alembic/script.py.mako
@@ -0,0 +1,24 @@
+"""${message}
+
+Revision ID: ${up_revision}
+Revises: ${down_revision | comma,n}
+Create Date: ${create_date}
+
+"""
+from alembic import op
+import sqlalchemy as sa
+${imports if imports else ""}
+
+# revision identifiers, used by Alembic.
+revision = ${repr(up_revision)}
+down_revision = ${repr(down_revision)}
+branch_labels = ${repr(branch_labels)}
+depends_on = ${repr(depends_on)}
+
+
+def upgrade():
+    ${upgrades if upgrades else "pass"}
+
+
+def downgrade():
+    ${downgrades if downgrades else "pass"}
diff --git a/backend-demo/alembic/versions/__pycache__/ac95fea8c6b2_message.cpython-37.pyc b/backend-demo/alembic/versions/__pycache__/ac95fea8c6b2_message.cpython-37.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..26c43e205c21c476001f56e5dbe94f43a4480ac6
GIT binary patch
literal 1497
zcmZ`(&2QT_6c;I3l4Uz-6JOS0n}O|OU<sTwD>9=Ph9)Rh49JGH*ugNs(t51JRv#`Y
zr%kcruDR}PANMcux?T1!?6gPPi4~-iz$fzeKKP^FdwkOABpyD0|53dBDe}C(?Be>N
zIQbrjevAUVkp~25Mshl$#LoT99|aJM8sLvY91)Hf8W8^OjhYZajNb(O=U%`0;n!Sf
z&8H$sei0W^%d+S_J013T`t3Kb1V2t6O%7KYp@+TXL<wGr-YJS<?{IK<us1l`8yxoz
z9u6NK508%akB<%>4F*Z_4>I%m#L#kfHfq<SrB-wOv~Y5QL;pZLukscINJU@!Kr6Zk
zmLB*JT=@=ZAksi2yg{NnNQ_9hu~|ewmt+|djQl#r=uK!YaR+Ej3C2mTf|{n~G!!-d
zwFcR=|A$TI&fZ&hkZnZl8P<#!32bBZdsDKsr(1E0Y7FipgEnl{8LV0FIL$3?Yt63H
zbT&TVVOG0mCYWoX43+Q{lMom4EaQ_*n1;x?%wCx7bFR+?uo>6-U8%q{rDi;><b^OD
z`JP#C=4{bDoEP%#T$toqA;AQ)61izqvcjk>5IR+IR$=v+xO`WjvxE<kjk1r%yXa``
zS>4V?b08YnE#2J3>$jAxaMMm*wXw=kr<$Aaq|D}dVWMY6C8l`5@Oh<VF*VVXvdjc8
zO!sFcbFMCb6qhGup)18@QQbt61&H^CyfWRVr4n*l+%o&qQ=j3!PW=1gbU2>Mis9dR
zqh~x7V-R^czL<+WH<|GCjVNHX;F-uLG96zCWjjcZ>jlr&9kxHaG|_xERUE{wt@*D|
zcr+wC>$goGkOyS5-XbeReSvnj+9p82wasrh!Qv)Z+!P2F0|oyIFM%5@_z2dhV+(Ay
zC8>FYsKpE8R+PAop+{srT&XqB)DCh`Z4_84iOUr$crMg^i{G=tcDeGcXrkx`tMn$I
z${BMhQJ*4iR)CAJNyKXQxz#rTOUwPfuW_ljP<XUU?3*~LzC@(oG7)1?ri__rotX88
z`fbLu^jzeZs*9E;R_iOzbZlt0Sxyu$(sPDQBQlM|#0+z-Sy{MTxP3lKF@BtvFwewy
Ucx8Lq9zGy#5(fcsuOEN%A0+dN<NyEw

literal 0
HcmV?d00001

diff --git a/backend-demo/alembic/versions/ac95fea8c6b2_message.py b/backend-demo/alembic/versions/ac95fea8c6b2_message.py
new file mode 100644
index 0000000..4661506
--- /dev/null
+++ b/backend-demo/alembic/versions/ac95fea8c6b2_message.py
@@ -0,0 +1,53 @@
+"""message
+
+Revision ID: ac95fea8c6b2
+Revises: 
+Create Date: 2021-04-08 13:38:44.841600
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = 'ac95fea8c6b2'
+down_revision = None
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.create_table('user',
+    sa.Column('id', sa.Integer(), nullable=False),
+    sa.Column('email', sa.String(), nullable=True),
+    sa.Column('hashed_password', sa.String(), nullable=True),
+    sa.Column('is_active', sa.Boolean(), nullable=True),
+    sa.PrimaryKeyConstraint('id')
+    )
+    op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True)
+    op.create_index(op.f('ix_user_id'), 'user', ['id'], unique=False)
+    op.create_table('item',
+    sa.Column('id', sa.Integer(), nullable=False),
+    sa.Column('title', sa.String(), nullable=True),
+    sa.Column('description', sa.String(), nullable=True),
+    sa.Column('owner_id', sa.Integer(), nullable=True),
+    sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ),
+    sa.PrimaryKeyConstraint('id')
+    )
+    op.create_index(op.f('ix_item_description'), 'item', ['description'], unique=False)
+    op.create_index(op.f('ix_item_id'), 'item', ['id'], unique=False)
+    op.create_index(op.f('ix_item_title'), 'item', ['title'], unique=False)
+    # ### end Alembic commands ###
+
+
+def downgrade():
+    # ### commands auto generated by Alembic - please adjust! ###
+    op.drop_index(op.f('ix_item_title'), table_name='item')
+    op.drop_index(op.f('ix_item_id'), table_name='item')
+    op.drop_index(op.f('ix_item_description'), table_name='item')
+    op.drop_table('item')
+    op.drop_index(op.f('ix_user_id'), table_name='user')
+    op.drop_index(op.f('ix_user_email'), table_name='user')
+    op.drop_table('user')
+    # ### end Alembic commands ###
diff --git a/backend-demo/api/__init__.py b/backend-demo/api/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend-demo/api/__pycache__/__init__.cpython-37.pyc b/backend-demo/api/__pycache__/__init__.cpython-37.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ab31f1210930dd43be2cf4e56615f7c6509a0be1
GIT binary patch
literal 146
zcmZ?b<>g`kf_%H&1Q7igM8E(ekl_Ht#VkM~g&~+hlhJP_LlH<ALHx3Iv5HC0EQv4A
zFUl@1NK8(RNlDGkk0~on)de$>5|gu2^HLz3#DdJ2`1s5`p!#^dg34PQHo5sJr8%i~
KAVWU`F#`bnm?N?P

literal 0
HcmV?d00001

diff --git a/backend-demo/api/__pycache__/crud.cpython-37.pyc b/backend-demo/api/__pycache__/crud.cpython-37.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7bf5364a7c1c3238ec3ccb30d6ee59d946d7f647
GIT binary patch
literal 1585
zcmb_cJ8x4l6!z;rnl?>ZQd%D3A*L*y0U?Cw1Vb0Za)lJq*r7MQw+Z&GP^7T2!pg$X
zj{GIBO#KT?e8<Uc(@scW$@cN_IriuC9iNxYW`m)<-kH3Aq8R%{i@HS+UZA^g7&Ozo
zU?tBuXIf}kh*D;<RGH$;a1&|uL+D7yUsM)jo#+JXRIgy2>TR-s&FT-S*L3qsl&wIc
zMh(3VtCnuVYE|znS#9V|Sgi%CCOmJzv!l0Qx2|u#VclEjz{I-TCvS{%`S|#p(^~kr
z9BWfJpSZ!HDf<p8p$~j=;*9O(dLYOTjb=QU?V-DS7-sC0ReU7Q_^G6Mq-OkxR?!*5
zg?zso`{?6|u~SQaUcJSMJ|5;pg<0g<N5kB@sw-@aeVbtT$!rtT@8<5i{v>}luqS$V
zGWF>PQ}qZC9pnNcZd;HSzCHr-y!a=qHpO}20=q&I(QMl^tYN4@1_eP3IPE$lzfq&>
z9ZY)x2tB2SxdHiHW9Z88POsciKJihJmw7di*q}x;^$^{=5SF7m<A|!7U5)KEygnWe
zht5>C17|P$MX|Wg-hd)#=a2*VfP1CJ?dYvF-fhTrSQW+mBB~U8>H*_Z2s1uHl5exm
zg80Wu&V&|U#b*ihY9`N+`jC(hVU8CXiR_U3S?hRQSp%Tc!@fH-8kbUZv(|jsoAjOg
zG`6~%__UUn_#s=oQ)?d&#wB9u6KjUnxWlfRC)5s|)*1GXOz+x}KG6pupRR{wbpS+_
z340VMN8B^s;BE92-BRZN7uLLbRhiOm;k-FJHzEBUXE_`lIo6y#xODZ3cEe}7fJvlL
zZ3@IahETB)AA-J9aRvfsBIn=5Hy&t{7qqDbZP5~KVn0jApN>Oi=*Dw4?&CnObUvu;
zZHV>`4V1z)4-ZK}Um~Nh2YA<i&{Eigfg|ohS~9*<hyD?C`^XY1Ty_(~6}jct-N&L|
zP!*f*j%{hFPWZ)_rssQ^A6KDlc<Cx@>1SF>ci*7Wv=^uk1ltGDW(^`}p{2W*lI8Lf
brO!}5PYUy#P&xWnYrHMmvMoDuGwsMfHV{KP

literal 0
HcmV?d00001

diff --git a/backend-demo/api/__pycache__/database.cpython-37.pyc b/backend-demo/api/__pycache__/database.cpython-37.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5a6f997028acf84787c1f63d22bea80ee15fa18e
GIT binary patch
literal 443
zcmYjO%T59@6m4gQXMpI+#P|bjhWOeLV;B^}0!<WMI3d9@y~4zH3hiLTEo(o-U+T`d
z@)ulr2SViD^yJ)U?n&F#YMG!Ne5t1yCFDB`HkL!u!e_b&oNy|Lq#>mlcZ4I|&|Ps?
zcrq8}kb7YsDCCzkEI^K<LxJZ%+^`7ll7uB*=#zT!2e*+rwbe)iOJNAfI8Fc)I~*dx
zG)v=G7>=09auox{#43^O1@yZ=RVE#4Ff-9?G&Vu=uq9N)!~~1Y)05+)21`;7QeD+u
zTW0fAMM}yzwWW+di@BLtZxko|re3yX+{HvR0lCl)9v1HZCwzEI{jH<4iw(B6)u6Q0
zUN*JYzwHIRi*D!oad;Wr2koHW89v<gY<?}zR{Copz3fu!wNc(D(N&yX!SAj=VAFWO
fS;{i${&ZoBXHxOGfOGs9Vk!s~itcz$k=C3)vq*lN

literal 0
HcmV?d00001

diff --git a/backend-demo/api/__pycache__/main.cpython-37.pyc b/backend-demo/api/__pycache__/main.cpython-37.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..106858383b2602a8c8e541fb6e8198e26241cf41
GIT binary patch
literal 1754
zcmb_dOK)366rP#;^fON4IM23*Mb*1W{s2PIQbeUHRBCx>RLNZ5nS|@!7c<uqG0ui`
zYl#KyDgjIW((Dkc`~_B=Gqwpz5i7=;b7#)$p7VV(*AF|LCV}zu@8wrNd4&9doAvNv
z@c~TzfD$5zpc%<2r#a({;+|zr?s7NxxR?9fw>l>a@{otHc7>NU@+NQQE#Au8ylr)U
z*2%lPn{V(9N@Q!&n`}<D<c0_q6!zW~;oBk<jn{<t<@RJpG-Z1-5UmB1uIyjYGYb1%
z8H)BBCOV?~+Pk8B4?Oq9hD6(7pP)BX*IPw6@&JCJ*nH!NEwODQ4sAsLt=#Ch1DOY6
z*ZLjV8uq~N&VOXx7YEk+t~h*7qN8iXFJi`hn(EqwpH_8R7I79iCiqBJvJlz?AIG}>
z@Yxg7`Q*imXOF*4q;-m@37$)>!T%Z{ClKN$YATGMmqKO`r<1Q`9&5<nUO7H3lQ=WJ
zEY8zHLIQW379znCaA1}YCMxt0L`|;9C7Ctn<a0270W{9ZFZ5Sfe+g?SpPoicwZYSP
zNmgo1$;9|KqBcSIXXp53T%|AbI4zE=3*(>5dL+i^uJJX5`jpWoQ`@kf`6p8?m5wOy
zDygfo&~juOL2%Hddf=8Y420ppbYVV%sgFU_WJ2e3P9{vy1-oQ(riQg6m~e!<VBb@)
zyJ#2Qf<fi}Db(r#JZYBx0FBRf9^-l#XG+FmsN^~DAr-v5rR%t!>QMry8ehm7+@Nc$
zM)g5No@&5@Z(@5`=xBT~vWe9W`rySdtpw86(%K`(#@P%e5M!&fh%LnyM(u$>T$V_&
z9(8G(TJ0=YB6V^tz>cizS7~LuEX~t;2@-;X0|A12x&#SpwIH!MnNtACxpd|ZK*5m2
zL$InN5U{rGG}i6t$i1=J{M@rt-748M5joq8K7d3T-%g-%bqAia-m2>FEJUfe1ZacR
zdRg_qQhOg_*VL|w735W@-mTdUxpSn{14`9h@K1k4Lc5;<7sH~ghiB!q5YX2EY-$$-
zFkpKO*F9R2K#S3~7`2ZpJjFMhn8x1?-UGvmnJx{H8Pv=Mw|hKEYnkihf8)cZdy5a_
zS^e#DmwZ?gqIko{JFq*A?is(nsM6wGA@B-o<iQzyYT(W)<WAwyvB>bQ{w9kv{G41I
zmnv5QSPZcsD*Q`OEf72uaUG9ifF19+>Y&bj2zwLeaBa}ao2!cuXPI&0s<M}#yH%-c
z?jmEniSm)XF7~_|2Us#rTGaL+S){r91b|i8$UnNabm8qW;d}7um}c^QI0!@|t3BGM
ZZPuqf)?+>kgDyStx9HIA(gVLs{{o1mgkJyv

literal 0
HcmV?d00001

diff --git a/backend-demo/api/__pycache__/models.cpython-37.pyc b/backend-demo/api/__pycache__/models.cpython-37.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..15ea834c2207e27ecb7319e975ceabe9b7bfc411
GIT binary patch
literal 1032
zcmah|%Wl*#6t(kCCNnK>B+3WOX1)NSsz{&;R3Q~46jd_r*mSJgNybhn!*<yAL--|c
zS@joKac|NV7L{=1<9lrD`uJSuU^*=bwBl*|`6?jf7m}M6L9h?qeF6|fP)!W2s0pgT
zgjHywDl%~uQ(OylV$v#wI24i2OkU-bydfeM$qynDnQz0Y0G^5rcsB5f$dAdqxPt6t
z9{cp5X|&|EPhK^;H?=QbH&&{%`mNmf^sw&anY2DR?yRcM=G2!~YTl`)b}Q9hQA|R8
zbikclV#b>mL$D9sy#tV>qJmU`pj9X%8H)hE@G`6tVCldjU>UM4t#TL(n2j%kY9dRK
zpniy_qjDd8bRa3}0YA(GKWVKp+-}%8D2`Pv<Tsd2`dY1fX{T^LC`^U?!h7BM=&+N<
z$IV48EzHkO`SP5#P20n*q&u3=e8yPKjbzLhjG0FC8sn0&b<g$n%9OE=pK3W$8^R$L
zuj~UQY=S{74OiOlthifA!CLOzMPr49auc5`$M~{SU!_fl%?d=hpTzC~8{qfO;)J*A
z1kRV*E!vI67lWBy36Rl<&fv>vNr#oY&3_MD|7(5()(+N#<rt&=F%%%)((RoEu)C1f
zN2nS6x2WgZ=3)4rVr3R!1WLBg0Or)*hXLHR4{+`w!Xt#o2)FjxCrC&CI@M|ECqlZV
zRV}_ai@%1?hSD)ekJj0H2)DM4(Q}+bku&K3_*H^ivtU>`nwP$CYt8j?CCz3i%cuhn
x8EQG|nEl<fXsj9Ql?e{zhc|C?+&$bA&$~;A&t8m==q3IvE(6%3Wl#pY*&keP>f8VT

literal 0
HcmV?d00001

diff --git a/backend-demo/api/__pycache__/schemas.cpython-37.pyc b/backend-demo/api/__pycache__/schemas.cpython-37.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..84b8ba8c79e532c669b82b3d737ef55709407927
GIT binary patch
literal 1575
zcmbVMO>fjN5VaF0o6YXFL3|_5NRd!~!3tIF0U^P1fL@|XA*@ZYl#j(usmcK%l|RE@
z@(pp~FL2^b!nWP*RtO^5^0Oy1Z)TpoH<`o?-{<emn~BHR4+6_iV6lr~zhII~@{(0N
z<CT|roUm8=mB<8VPnq<k_{yYE;>^zi;6R4Jp$dRQ;DL;QqYe*%hcX6^JNpQDB*(yG
z6~TT8Jds<#TOE$&_A8bqKf!{fo)gctZC&(o(dwoy%9Oj|USZXXMyhg7Hp|b)Vi&_c
z!lYQnCCj`6rhFwNpMrlDs8IR$8L&?nQG|=0wW=bAE@*XIs+k)}W#>kB@zQ~d^1Lo8
zmFF(b^Qw`@C9TJK{^7VN|D5=?HEui4i@I*wf{5BYHv{l6#OfO}6I#>sE7>_L7W&Z6
z->a&yJBw2jV4I3>X^MHoyPcKXI8JuSWQ=K<o8-vAb1`MGZ&cB$S?Zez7G_8jwfZ7(
zzuKa`ZA@flApwSPgjd3ym+~=Amf<c+*_G6rUJ}9?iYo)q37sH1&ePIG%}K3HuH}mF
z^Hh>cz9rB6t|9)X5&gfj-_-B)(d$_%))InOQ&At(rqo>6|43;rv%7`$EDhKF__}Tl
zmD7;HCTE5ijxcP9i5i1i(cVfv=e4?nldHArvvSdaRi>92+OEr48mtjA6sae4E|yC@
zBkwmbx~yVu2>%C<n0WMJanYi%_M|cLpUK=qaH`sp4B^dWrZ~A;6cykOLO@3>3)MhH
z%R7meK*#_uK>$GwWe7nHoFK}Ea(6>*^J3oWkGQQGGO;T`|Eer<biK07O@!E}14{KF
z#;T&?^#<Oi|4C6!yltA&&@YCrSSOn6W%d9jcQGv8$zEoOXJ~hpj$GKDE_8j=J0h{6
xZ>O@TTRk`QV04c~cZK^fBn(3h={#)8$3@fr9eY9*vvgMyKK2rk_(_Q2{|2ZO4+j7M

literal 0
HcmV?d00001

diff --git a/backend-demo/api/crud.py b/backend-demo/api/crud.py
new file mode 100644
index 0000000..33cd363
--- /dev/null
+++ b/backend-demo/api/crud.py
@@ -0,0 +1,29 @@
+from sqlalchemy.orm import Session
+from . import models, schemas
+
+def get_user(db: Session, user_id: int):
+    return db.query(models.User).filter(models.User.id == user_id).first()
+
+def get_user_by_email(db: Session, email: str):
+    return db.query(models.User).filter(models.User.email == email).first()
+
+def get_users(db: Session, skip: int = 0, limit: int = 100):
+    return db.query(models.User).offset(skip).limit(limit).all()
+
+def create_user(db: Session, user: schemas.UserCreate):
+    fake_hashed_password = user.password + "notreallyhashed"
+    db_user = models.User(email=user.email, hashed_password=fake_hashed_password)
+    db.add(db_user)
+    db.commit()
+    db.refresh(db_user)
+    return db_user
+
+def get_items(db:Session, skip: int = 0, limit: int = 100):
+    return db.query(models.Item).offset(skip).limit(limit).all()
+
+def create_user_items(db: Session, item: schemas.ItemCreate, user_id: int):
+    db_item = models.Item(**item.dict(), owner_id=user_id)
+    db.add(db_item)
+    db.commit()
+    db.refresh(db_item)
+    return db_item
\ No newline at end of file
diff --git a/backend-demo/api/database.py b/backend-demo/api/database.py
new file mode 100644
index 0000000..fe16e15
--- /dev/null
+++ b/backend-demo/api/database.py
@@ -0,0 +1,10 @@
+from sqlalchemy import create_engine
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import sessionmaker
+
+SQLALCHEMY_DATABASE_URL = "postgresql://postgres:1@localhost:5432/antdemo"
+
+engine = create_engine(SQLALCHEMY_DATABASE_URL)
+SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+Base = declarative_base()
\ No newline at end of file
diff --git a/backend-demo/api/main.py b/backend-demo/api/main.py
new file mode 100644
index 0000000..02bfd13
--- /dev/null
+++ b/backend-demo/api/main.py
@@ -0,0 +1,41 @@
+from typing import List, Optional
+from fastapi import Depends, FastAPI, HTTPException
+from sqlalchemy.orm import Session
+
+from . import crud, models, schemas
+from .database import SessionLocal, engine
+
+models.Base.metadata.create_all(bind=engine)
+
+app = FastAPI()
+
+def get_db():
+    db = SessionLocal()
+    try:
+        yield db
+    finally:
+        db.close()
+
+@app.post("/users", response_model=schemas.User)
+def create_user(user: schemas.UserCreate, db:Session = Depends(get_db)):
+    db_user = crud.get_user_by_email(db, email=user.email)
+    if db_user:
+        raise HTTPException(status_code=400, detail="Email alread register")
+    return crud.create_user(db=db, user=user)
+
+@app.get("/users/", response_model=List[schemas.User])
+def read_users(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
+    users = crud.get_users(db, skip=skip, limit=limit)
+    return users
+
+@app.get("/users/{user_id}", response_model=schemas.User)
+def read_user(user_id: int, db: Session = Depends(get_db)):
+    db_user = crud.get_user(db, user_id=user_id)
+    if db_user is None:
+        raise HTTPException(status_code=404, detail="User not found")
+    return db_user
+
+@app.post("/users/{user_id}/items", response_model=schemas.Item)
+def read_items(skip: int = 0, limit: int = 100, db:Session = Depends(get_db)):
+    items = crud.get_items(db, skip=skip, limit=limit)
+    return items
diff --git a/backend-demo/api/models.py b/backend-demo/api/models.py
new file mode 100644
index 0000000..696fda6
--- /dev/null
+++ b/backend-demo/api/models.py
@@ -0,0 +1,21 @@
+from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
+from sqlalchemy.orm import relationship
+
+from .database import Base
+
+class User(Base):
+    __tablename__ = "user"
+    id = Column(Integer, primary_key=True, index=True)
+    email = Column(String, unique=True, index=True)
+    hashed_password = Column(String)
+    is_active = Column(Boolean, default=True)
+    items = relationship("Item", back_populates="owner")
+
+class Item(Base):
+    __tablename__ = "item"
+    id = Column(Integer, primary_key=True, index=True)
+    title = Column(String, index=True)
+    description = Column(String, index=True)
+    owner_id = Column(Integer, ForeignKey("user.id"))
+
+    owner = relationship("User", back_populates="items")
\ No newline at end of file
diff --git a/backend-demo/api/schemas.py b/backend-demo/api/schemas.py
new file mode 100644
index 0000000..f07db4e
--- /dev/null
+++ b/backend-demo/api/schemas.py
@@ -0,0 +1,30 @@
+from typing import List, Optional
+from pydantic import BaseModel
+
+class ItemBase(BaseModel):
+    title: str
+    description: Optional[str] = None
+
+class ItemCreate(ItemBase):
+    pass
+
+class Item(ItemBase):
+    id: int
+    owner_id: int
+    class Config:
+        orm_mode = True
+
+class UserBase(BaseModel):
+    email: str
+
+class UserCreate(UserBase):
+    password: str
+
+class User(UserBase):
+    id: int
+    is_active: bool
+    items: List[Item] = []
+
+    class Config:
+        orm_mode = True
+
-- 
2.21.0