├── .documentation
├── .gitignore
├── docfx.json
├── index.md
└── toc.yml
├── .env.sample
├── .github
├── icon.png
├── supabase-csharp.png
└── workflows
│ ├── build-and-test.yml
│ ├── build-documentation.yaml
│ └── release.yml
├── .gitignore
├── .gitmodules
├── CHANGELOG.md
├── Examples
├── BlazorWebAssemblySupabaseTemplate
│ ├── .firebaserc
│ ├── .gitignore
│ ├── .vscode
│ │ ├── launch.json
│ │ └── tasks.json
│ ├── App.razor
│ ├── BlazorWebAssemblySupabaseTemplate.csproj
│ ├── Dtos
│ │ ├── BaseModelApp.cs
│ │ ├── Todo.cs
│ │ └── TodoPrivate.cs
│ ├── Pages
│ │ ├── Auth
│ │ │ ├── Login.razor
│ │ │ ├── Login.razor.cs
│ │ │ ├── LoginPage.razor
│ │ │ └── LoginPage.razor.cs
│ │ ├── Crud
│ │ │ ├── CrudPage.razor
│ │ │ └── CrudPage.razor.cs
│ │ ├── CrudPrivate
│ │ │ ├── CrudPagePrivate.razor
│ │ │ └── CrudPagePrivate.razor.cs
│ │ ├── FileUploadFolder
│ │ │ ├── FileUpload.razor
│ │ │ └── FileUpload.razor.cs
│ │ └── Index.razor
│ ├── Program.cs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── Providers
│ │ ├── CustomAuthStateProvider.cs
│ │ └── CustomSupabaseSessionHandler.cs
│ ├── README.MD
│ ├── Services
│ │ ├── AuthService.cs
│ │ ├── DatabaseService.cs
│ │ └── StorageService.cs
│ ├── Shared
│ │ ├── MainLayout.razor
│ │ ├── MainLayout.razor.cs
│ │ └── MainLayout.razor.css
│ ├── Supabase
│ │ └── DatabasePolicies.sql
│ ├── _Imports.razor
│ ├── firebase.json
│ └── wwwroot
│ │ ├── css
│ │ └── app.css
│ │ ├── favicon.png
│ │ ├── icon-192.png
│ │ ├── icon-512.png
│ │ ├── index.html
│ │ ├── manifest.json
│ │ ├── sample-data
│ │ └── weather.json
│ │ ├── service-worker.js
│ │ └── service-worker.published.js
├── SupabaseExample
│ ├── Assets
│ │ └── supabase-csharp.png
│ ├── Models
│ │ ├── Channel.cs
│ │ └── Movie.cs
│ ├── Program.cs
│ ├── SupabaseExample.csproj
│ └── db
│ │ ├── 00-schema.sql
│ │ └── 01-dummy-data.sql
└── Xamarin.Forms
│ ├── SupabaseExample.sln
│ ├── SupabaseExampleXA.Android
│ ├── Assets
│ │ └── AboutAssets.txt
│ ├── MainActivity.cs
│ ├── Properties
│ │ ├── AndroidManifest.xml
│ │ └── AssemblyInfo.cs
│ ├── Resources
│ │ ├── AboutResources.txt
│ │ ├── Resource.designer.cs
│ │ ├── layout
│ │ │ ├── Tabbar.xml
│ │ │ └── Toolbar.xml
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── icon.xml
│ │ │ └── icon_round.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── icon.png
│ │ │ └── launcher_foreground.png
│ │ ├── mipmap-mdpi
│ │ │ ├── icon.png
│ │ │ └── launcher_foreground.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── icon.png
│ │ │ └── launcher_foreground.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── icon.png
│ │ │ └── launcher_foreground.png
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── icon.png
│ │ │ └── launcher_foreground.png
│ │ └── values
│ │ │ ├── colors.xml
│ │ │ └── styles.xml
│ └── SupabaseExampleXA.Android.csproj
│ ├── SupabaseExampleXA.iOS
│ ├── AppDelegate.cs
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon1024.png
│ │ │ ├── Icon120.png
│ │ │ ├── Icon152.png
│ │ │ ├── Icon167.png
│ │ │ ├── Icon180.png
│ │ │ ├── Icon20.png
│ │ │ ├── Icon29.png
│ │ │ ├── Icon40.png
│ │ │ ├── Icon58.png
│ │ │ ├── Icon60.png
│ │ │ ├── Icon76.png
│ │ │ ├── Icon80.png
│ │ │ └── Icon87.png
│ ├── Entitlements.plist
│ ├── Info.plist
│ ├── Main.cs
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── Resources
│ │ ├── Default-568h@2x.png
│ │ ├── Default-Portrait.png
│ │ ├── Default-Portrait@2x.png
│ │ ├── Default.png
│ │ ├── Default@2x.png
│ │ └── LaunchScreen.storyboard
│ └── SupabaseExampleXA.iOS.csproj
│ └── SupabaseExampleXA
│ ├── App.xaml
│ ├── App.xaml.cs
│ ├── AssemblyInfo.cs
│ ├── ChannelListPage.xaml
│ ├── ChannelListPage.xaml.cs
│ ├── LoadingPage.xaml
│ ├── LoadingPage.xaml.cs
│ ├── MessageListPage.xaml
│ ├── MessageListPage.xaml.cs
│ ├── Models
│ ├── Channel.cs
│ ├── Message.cs
│ └── User.cs
│ └── SupabaseExampleXA.csproj
├── LICENSE
├── README.md
├── Supabase.sln
├── Supabase
├── Client.cs
├── DefaultSupabaseSessionHandler.cs
├── Extensions
│ └── DictionaryExtension.cs
├── Interfaces
│ ├── ISupabaseClient.cs
│ ├── ISupabaseFunctions.cs
│ └── ISupabaseTable.cs
├── StatelessClient.cs
├── Supabase.csproj
├── SupabaseModel.cs
├── SupabaseOptions.cs
└── SupabaseTable.cs
├── SupabaseTests
├── Assets
│ └── supabase-csharp.png
├── Client.cs
├── Models
│ ├── Channel.cs
│ ├── Stub.cs
│ └── User.cs
├── StatelessClient.cs
├── Stubs
│ ├── FakeAuthClient.cs
│ ├── FakeFunctionsClient.cs
│ ├── FakeRealtimeClient.cs
│ ├── FakeRestClient.cs
│ └── FakeStorageClient.cs
├── SupabaseTests.csproj
└── db
│ ├── 00-schema.sql
│ ├── 01-auth-schema.sql
│ ├── 02-rest-schema.sql
│ └── 03-dummy-data.sql
└── docker-compose.yml
/.documentation/.gitignore:
--------------------------------------------------------------------------------
1 | ###############
2 | # folder #
3 | ###############
4 | /**/DROP/
5 | /**/TEMP/
6 | /**/packages/
7 | /**/bin/
8 | /**/obj/
9 | /_site
10 | /api
11 |
--------------------------------------------------------------------------------
/.documentation/docfx.json:
--------------------------------------------------------------------------------
1 | {
2 | "metadata": [
3 | {
4 | "src": [
5 | {
6 | "src": "../Supabase",
7 | "files": [
8 | "**/*.csproj"
9 | ]
10 | },
11 | {
12 | "src": "../modules/gotrue-csharp/Gotrue",
13 | "files": [
14 | "**/*.csproj"
15 | ]
16 | },
17 | {
18 | "src": "../modules/realtime-csharp/Realtime",
19 | "files": [
20 | "**/*.csproj"
21 | ]
22 | },
23 | {
24 | "src": "../modules/postgrest-csharp/Postgrest",
25 | "files": [
26 | "**/*.csproj"
27 | ]
28 | },
29 | {
30 | "src": "../modules/storage-csharp/Storage",
31 | "files": [
32 | "**/*.csproj"
33 | ]
34 | },
35 | {
36 | "src": "../modules/functions-csharp/Functions",
37 | "files": [
38 | "**/*.csproj"
39 | ]
40 | },
41 | {
42 | "src": "../modules/core-csharp/Core",
43 | "files": [
44 | "**/*.csproj"
45 | ]
46 | }
47 | ],
48 | "dest": "api"
49 | }
50 | ],
51 | "build": {
52 | "content": [
53 | {
54 | "files": [
55 | "**/*.{md,yml}"
56 | ],
57 | "exclude": [
58 | "_site/**"
59 | ]
60 | }
61 | ],
62 | "resource": [
63 | {
64 | "files": [
65 | "images/**"
66 | ]
67 | }
68 | ],
69 | "output": "_site",
70 | "template": [
71 | "default",
72 | "modern"
73 | ],
74 | "globalMetadata": {
75 | "_appName": "supabase-csharp",
76 | "_appTitle": "supabase-csharp",
77 | "_enableSearch": true
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/.documentation/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | _layout: landing
3 | ---
4 |
5 | # supabase-csharp
6 |
--------------------------------------------------------------------------------
/.documentation/toc.yml:
--------------------------------------------------------------------------------
1 | - name: API
2 | href: api/
3 |
--------------------------------------------------------------------------------
/.env.sample:
--------------------------------------------------------------------------------
1 | AWS_ACCESS_KEY=
2 | AWS_SECRET_ACCESS_KEY=
3 | AWS_BUCKET=
4 | AWS_REGION=us-east-1
--------------------------------------------------------------------------------
/.github/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/.github/icon.png
--------------------------------------------------------------------------------
/.github/supabase-csharp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/.github/supabase-csharp.png
--------------------------------------------------------------------------------
/.github/workflows/build-and-test.yml:
--------------------------------------------------------------------------------
1 | name: Build and Test
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches: [master]
8 |
9 | jobs:
10 | build-and-test:
11 | runs-on: ubuntu-latest
12 |
13 | env:
14 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
15 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
16 | AWS_BUCKET: ${{ secrets.AWS_BUCKET }}
17 | AWS_REGION: ${{ secrets.AWS_REGION }}
18 |
19 | steps:
20 | - uses: actions/checkout@v3
21 |
22 | - name: Setup .NET
23 | uses: actions/setup-dotnet@v3
24 | with:
25 | dotnet-version: 8.x
26 |
27 | - name: Restore dependencies
28 | run: dotnet restore
29 |
30 | - name: Build
31 | run: dotnet build --configuration Release --no-restore
32 |
33 | - name: Initialize Testing Stack
34 | run: docker compose up -d
35 |
36 | - name: Test
37 | run: dotnet test --no-restore
38 |
--------------------------------------------------------------------------------
/.github/workflows/build-documentation.yaml:
--------------------------------------------------------------------------------
1 | name: Build and Deploy Documentation
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - release/* # Default release branch
8 |
9 | jobs:
10 | docs:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 | with:
15 | submodules: "true"
16 | persist-credentials: false
17 |
18 | - name: Setup .NET
19 | uses: actions/setup-dotnet@v3
20 | with:
21 | dotnet-version: 8.x
22 |
23 | - name: Install docfx
24 | run: dotnet tool update -g docfx
25 |
26 | - name: Build documentation
27 | run: docfx .documentation/docfx.json
28 |
29 | - name: Deploy 🚀
30 | uses: JamesIves/github-pages-deploy-action@v4
31 | with:
32 | folder: .documentation/_site
33 | token: ${{ secrets.GITHUB_TOKEN }}
34 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Publish NuGet Package
2 |
3 | on:
4 | push:
5 | branches:
6 | - release/* # Default release branch
7 |
8 | jobs:
9 | publish:
10 | name: build, pack & publish
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 |
15 | - name: Setup .NET
16 | uses: actions/setup-dotnet@v3
17 | with:
18 | dotnet-version: 8.x
19 |
20 | - name: Wait for tests to succeed
21 | uses: lewagon/wait-on-check-action@v1.3.1
22 | with:
23 | ref: ${{ github.ref }}
24 | check-name: build-and-test
25 | repo-token: ${{ secrets.GITHUB_TOKEN }}
26 | wait-interval: 10
27 |
28 | - name: Restore dependencies
29 | run: dotnet restore
30 |
31 | - name: Build
32 | run: dotnet build --configuration Release --no-restore
33 |
34 | - name: Generate package
35 | run: dotnet pack ./Supabase/Supabase.csproj --configuration Release
36 |
37 | - name: Publish on version change
38 | run: dotnet nuget push "**/*.nupkg" --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}
39 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "modules/gotrue-csharp"]
2 | path = modules/gotrue-csharp
3 | url = https://github.com/supabase-community/gotrue-csharp
4 | [submodule "modules/postgrest-csharp"]
5 | path = modules/postgrest-csharp
6 | url = https://github.com/supabase-community/postgrest-csharp
7 | [submodule "modules/realtime-csharp"]
8 | path = modules/realtime-csharp
9 | url = https://github.com/supabase-community/realtime-csharp
10 | [submodule "modules/storage-csharp"]
11 | path = modules/storage-csharp
12 | url = https://github.com/supabase-community/storage-csharp
13 | [submodule "modules/functions-csharp"]
14 | path = modules/functions-csharp
15 | url = https://github.com/supabase-community/functions-csharp
16 | [submodule "modules/core-csharp"]
17 | path = modules/core-csharp
18 | url = git@github.com:supabase-community/core-csharp.git
19 | [submodule "Documentation"]
20 | path = Documentation
21 | url = git@github.com:supabase-community/supabase-csharp.wiki.git
22 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "blazorwasmsupabasetemplate"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 | *.VC.VC.opendb
85 |
86 | # Visual Studio profiler
87 | *.psess
88 | *.vsp
89 | *.vspx
90 | *.sap
91 |
92 | # TFS 2012 Local Workspace
93 | $tf/
94 |
95 | # Guidance Automation Toolkit
96 | *.gpState
97 |
98 | # ReSharper is a .NET coding add-in
99 | _ReSharper*/
100 | *.[Rr]e[Ss]harper
101 | *.DotSettings.user
102 |
103 | # JustCode is a .NET coding add-in
104 | .JustCode
105 |
106 | # TeamCity is a build add-in
107 | _TeamCity*
108 |
109 | # DotCover is a Code Coverage Tool
110 | *.dotCover
111 |
112 | # NCrunch
113 | _NCrunch_*
114 | .*crunch*.local.xml
115 | nCrunchTemp_*
116 |
117 | # MightyMoose
118 | *.mm.*
119 | AutoTest.Net/
120 |
121 | # Web workbench (sass)
122 | .sass-cache/
123 |
124 | # Installshield output folder
125 | [Ee]xpress/
126 |
127 | # DocProject is a documentation generator add-in
128 | DocProject/buildhelp/
129 | DocProject/Help/*.HxT
130 | DocProject/Help/*.HxC
131 | DocProject/Help/*.hhc
132 | DocProject/Help/*.hhk
133 | DocProject/Help/*.hhp
134 | DocProject/Help/Html2
135 | DocProject/Help/html
136 |
137 | # Click-Once directory
138 | publish/
139 |
140 | # Publish Web Output
141 | *.[Pp]ublish.xml
142 | *.azurePubxml
143 | # TODO: Comment the next line if you want to checkin your web deploy settings
144 | # but database connection strings (with potential passwords) will be unencrypted
145 | *.pubxml
146 | *.publishproj
147 |
148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
149 | # checkin your Azure Web App publish settings, but sensitive information contained
150 | # in these scripts will be unencrypted
151 | PublishScripts/
152 |
153 | # NuGet Packages
154 | *.nupkg
155 | # The packages folder can be ignored because of Package Restore
156 | **/packages/*
157 | # except build/, which is used as an MSBuild target.
158 | !**/packages/build/
159 | # Uncomment if necessary however generally it will be regenerated when needed
160 | #!**/packages/repositories.config
161 | # NuGet v3's project.json files produces more ignoreable files
162 | *.nuget.props
163 | *.nuget.targets
164 |
165 | # Microsoft Azure Build Output
166 | csx/
167 | *.build.csdef
168 |
169 | # Microsoft Azure Emulator
170 | ecf/
171 | rcf/
172 |
173 | # Windows Store app package directories and files
174 | AppPackages/
175 | BundleArtifacts/
176 | Package.StoreAssociation.xml
177 | _pkginfo.txt
178 |
179 | # Visual Studio cache files
180 | # files ending in .cache can be ignored
181 | *.[Cc]ache
182 | # but keep track of directories ending in .cache
183 | !*.[Cc]ache/
184 |
185 | # Others
186 | ClientBin/
187 | ~$*
188 | *~
189 | *.dbmdl
190 | *.dbproj.schemaview
191 | *.pfx
192 | *.publishsettings
193 | node_modules/
194 | orleans.codegen.cs
195 |
196 | # Since there are multiple workflows, uncomment next line to ignore bower_components
197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
198 | #bower_components/
199 |
200 | # RIA/Silverlight projects
201 | Generated_Code/
202 |
203 | # Backup & report files from converting an old project file
204 | # to a newer Visual Studio version. Backup files are not needed,
205 | # because we have git ;-)
206 | _UpgradeReport_Files/
207 | Backup*/
208 | UpgradeLog*.XML
209 | UpgradeLog*.htm
210 |
211 | # SQL Server files
212 | *.mdf
213 | *.ldf
214 |
215 | # Business Intelligence projects
216 | *.rdl.data
217 | *.bim.layout
218 | *.bim_*.settings
219 |
220 | # Microsoft Fakes
221 | FakesAssemblies/
222 |
223 | # GhostDoc plugin setting file
224 | *.GhostDoc.xml
225 |
226 | # Node.js Tools for Visual Studio
227 | .ntvs_analysis.dat
228 |
229 | # Visual Studio 6 build log
230 | *.plg
231 |
232 | # Visual Studio 6 workspace options file
233 | *.opt
234 |
235 | # Visual Studio LightSwitch build output
236 | **/*.HTMLClient/GeneratedArtifacts
237 | **/*.DesktopClient/GeneratedArtifacts
238 | **/*.DesktopClient/ModelManifest.xml
239 | **/*.Server/GeneratedArtifacts
240 | **/*.Server/ModelManifest.xml
241 | _Pvt_Extensions
242 |
243 | # Paket dependency manager
244 | .paket/paket.exe
245 | paket-files/
246 |
247 | # FAKE - F# Make
248 | .fake/
249 |
250 | # JetBrains Rider
251 | .idea/
252 | *.sln.iml
253 |
254 | # MaterialeShop
255 | src/MaterialeShop.Web/Logs/*
256 | src/MaterialeShop.Web.Host/Logs/*
257 | src/MaterialeShop.Web.Public/Logs/*
258 | src/MaterialeShop.Web.Public.Host/Logs/*
259 | src/MaterialeShop.AuthServer/Logs/*
260 | src/MaterialeShop.HttpApi.Host/Logs/*
261 | src/MaterialeShop.HttpApi.Host/Logs/*
262 | src/MaterialeShop.DbMigrator/Logs/*
263 | src/MaterialeShop.Blazor.Server/Logs/*
264 | src/MaterialeShop.Blazor.Server.Tiered/Logs/*
265 |
266 | # Use abp install-libs to restore.
267 | **/wwwroot/libs/*
268 |
269 | # IdentityServer temp files
270 | tempkey.rsa
271 | tempkey.jwk
272 |
273 | !**/packages/*Theme*
274 |
275 | .history
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Launch and Debug Standalone Blazor WebAssembly App",
6 | "type": "blazorwasm",
7 | "request": "launch",
8 | "cwd": "${workspaceFolder}"
9 | }
10 | ]
11 | }
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/BlazorWebAssemblySupabaseTemplate.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/BlazorWebAssemblySupabaseTemplate.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "--project",
36 | "${workspaceFolder}/BlazorWebAssemblySupabaseTemplate.csproj"
37 | ],
38 | "problemMatcher": "$msCompile"
39 | }
40 | ]
41 | }
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/App.razor:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Not found
13 |
14 | Sorry, there's nothing at this address.
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/BlazorWebAssemblySupabaseTemplate.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 | service-worker-assets.js
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Dtos/BaseModelApp.cs:
--------------------------------------------------------------------------------
1 | using Postgrest.Attributes;
2 | using Postgrest.Models;
3 |
4 | public class BaseModelApp : BaseModel
5 | {
6 | [PrimaryKey("id", false)] // Key is Autogenerated
7 | public int Id { get; set; }
8 |
9 | // There is a rule in the definition of this table to set to false as default value to this field.
10 | // So, It's necessary to set default value in the class creation bacause the insert method would send this value as null.
11 | // This way, the sent row wouldn't be set to false automaticaly by Postgre default value
12 | // because the field was sent with null value.
13 | // To fix this, the null fields should not be serialized in the request.
14 | [Column("SoftDeleted")]
15 | public bool? SoftDeleted { get; set; } = false;
16 |
17 | [Column("SoftDeletedAt")]
18 | public DateTime? SoftDeletedAt { get; set; }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Dtos/Todo.cs:
--------------------------------------------------------------------------------
1 | using Postgrest.Attributes;
2 |
3 | namespace BlazorWebAssemblySupabaseTemplate.Dtos;
4 |
5 | [Table("Todo")]
6 | public class Todo : BaseModelApp
7 | {
8 | [Column("Title")]
9 | public string? Title { get; set; }
10 | }
11 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Dtos/TodoPrivate.cs:
--------------------------------------------------------------------------------
1 | using Postgrest.Attributes;
2 |
3 | namespace BlazorWebAssemblySupabaseTemplate.Dtos;
4 |
5 | [Table("TodoPrivate")]
6 | public class TodoPrivate : BaseModelApp
7 | {
8 | [Column("title")]
9 | public string? Title { get; set; }
10 |
11 | [Column("user_id")]
12 | public string UserId { get; set; }
13 | }
14 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Pages/Auth/Login.razor:
--------------------------------------------------------------------------------
1 | @inject AuthService AuthService
2 | @inject NavigationManager NavigationManager
3 | @inject ISnackbar Snackbar
4 |
5 |
6 |
7 |
8 | Welcome
9 |
10 |
11 |
12 |
13 | Login
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Pages/Auth/Login.razor.cs:
--------------------------------------------------------------------------------
1 | namespace BlazorWebAssemblySupabaseTemplate.Pages.Auth;
2 |
3 | public partial class Login
4 | {
5 | protected string Email {get; set;} = "cliente1@gmail.com";
6 | protected string Password {get; set;} = "senhasdadasdaasd";
7 |
8 | public async Task OnClickLogin()
9 | {
10 | await AuthService.Login(Email, Password);
11 | Snackbar.Add("Login successful");
12 | NavigationManager.NavigateTo($"/");
13 | }
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Pages/Auth/LoginPage.razor:
--------------------------------------------------------------------------------
1 | @page "/login"
2 |
3 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Pages/Auth/LoginPage.razor.cs:
--------------------------------------------------------------------------------
1 | namespace BlazorWebAssemblySupabaseTemplate.Pages.Auth;
2 |
3 | public partial class LoginPage
4 | {
5 |
6 | }
7 |
8 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Pages/Crud/CrudPage.razor:
--------------------------------------------------------------------------------
1 | @page "/crud"
2 | @using Dtos
3 |
4 | @inject DatabaseService DatabaseService
5 | @inject NavigationManager NavigationManager
6 | @inject ISnackbar Snackbar
7 |
8 |
9 |
10 |
Todos
11 |
12 |
13 |
14 |
15 |
16 | New item
17 |
18 |
19 |
20 |
22 |
23 |
24 |
26 | @if (_processingNewItem)
27 | {
28 |
29 | Processing
30 | }
31 | else
32 | {
33 | Save
34 | }
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | @if (TodoListFiltered == null)
44 | {
45 |
46 |
47 |
48 | Title
49 | Action
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | }
60 | else if (TodoListFiltered.Count == 0)
61 | {
62 |
63 |
64 |
65 | Title
66 | Action
67 |
68 |
69 |
70 |
71 | There is no items in this table.
72 |
73 |
74 |
75 | }
76 | else
77 | {
78 |
80 |
81 |
84 |
85 |
86 |
87 |
88 | Title
89 |
90 |
91 |
92 | Action
93 |
94 |
95 |
96 | @context?.Title
97 |
98 | @* *@
102 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | }
111 |
112 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Pages/Crud/CrudPage.razor.cs:
--------------------------------------------------------------------------------
1 | using BlazorWebAssemblySupabaseTemplate.Dtos;
2 | using MudBlazor;
3 |
4 | namespace BlazorWebAssemblySupabaseTemplate.Pages.Crud;
5 |
6 | public partial class CrudPage
7 | {
8 | protected override async Task OnInitializedAsync()
9 | {
10 | await GetTable();
11 | }
12 |
13 | // ---------------- SELECT TABLE
14 | private IReadOnlyList? TodoList { get; set; }
15 | private IReadOnlyList? TodoListFiltered { get; set; }
16 | private MudTable? _table;
17 | protected async Task GetTable()
18 | {
19 | // await Task.Delay(10000);
20 | IReadOnlyList todos = await DatabaseService.From();
21 | TodoList = todos;
22 | TodoListFiltered = todos;
23 | await InvokeAsync(StateHasChanged);
24 | }
25 |
26 | // ---------------- SEARCH
27 | private void OnValueChangedSearch(string text)
28 | {
29 | TodoListFiltered = TodoList?.Where(row => row.Title.Contains(text)).ToList();
30 | }
31 |
32 | // ---------------- DELETE
33 | private async Task OnClickDelete(Todo item)
34 | {
35 | await DatabaseService.SoftDelete(item);
36 |
37 | await GetTable();
38 | }
39 |
40 | // ---------------- CREATE NEW
41 |
42 | protected Todo Model = new();
43 | private bool _success = false;
44 | string[] _errors = { };
45 | MudForm? _form;
46 | private bool _processingNewItem = false;
47 | private async Task OnClickSave()
48 | {
49 | _processingNewItem = true;
50 | await DatabaseService.Insert(Model);
51 | Model = new();
52 | await GetTable();
53 | _success = false;
54 | _processingNewItem = false;
55 | }
56 | }
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Pages/CrudPrivate/CrudPagePrivate.razor:
--------------------------------------------------------------------------------
1 | @page "/crud-private"
2 | @using Dtos
3 | @using Blazored.LocalStorage
4 |
5 | @inject DatabaseService DatabaseService
6 | @inject NavigationManager NavigationManager
7 | @inject ISnackbar Snackbar
8 | @inject ILocalStorageService LocalStorage
9 |
10 |
11 |
12 |
Todos private by RLS
13 |
14 |
The data in this page is filtered automatically by Row Level Security policies.
15 |
This way, you need to be logged in to see the content of this table and to insert an item.
16 |
Also, each user logged in can only SELECT (get from database) and UPDATE its own rows.
17 |
See the code file DatabasePolicies.sql on GitHub to get to know the policies.
18 |
19 |
20 |
21 |
22 |
23 |
24 | New item
25 |
26 |
27 |
28 |
30 |
31 |
32 |
34 | @if (_processingNewItem)
35 | {
36 |
37 | Processing
38 | }
39 | else
40 | {
41 | Save
42 | }
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | @if (TodoListFiltered == null)
52 | {
53 |
54 |
55 |
56 | Title
57 | Action
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | }
72 | else if (TodoListFiltered.Count == 0)
73 | {
74 |
75 |
76 |
77 | Title
78 | Action
79 |
80 |
81 |
82 |
83 | There is no items in this table.
84 |
85 |
86 |
87 | }
88 | else
89 | {
90 |
92 |
93 |
96 |
97 |
98 |
99 |
100 | Title
101 |
102 |
103 |
104 |
105 | User_id
106 |
107 |
108 |
109 | Action
110 |
111 |
112 |
113 | @context?.Title
114 | @context?.UserId
115 |
116 | @* *@
120 |
122 |
123 |
124 |
125 |
126 |
127 |
128 | }
129 |
130 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Pages/CrudPrivate/CrudPagePrivate.razor.cs:
--------------------------------------------------------------------------------
1 | using BlazorWebAssemblySupabaseTemplate.Dtos;
2 | using BlazorWebAssemblySupabaseTemplate.Services;
3 | using Microsoft.AspNetCore.Components;
4 | using MudBlazor;
5 | using Supabase.Gotrue;
6 |
7 | namespace BlazorWebAssemblySupabaseTemplate.Pages.CrudPrivate;
8 |
9 | public partial class CrudPagePrivate
10 | {
11 | [Inject]
12 | protected AuthService AuthService { get; set; }
13 | [Inject]
14 | protected IDialogService DialogService { get; set; }
15 |
16 | protected User? UserLoggedIn { get; set; }
17 |
18 | protected override async Task OnInitializedAsync()
19 | {
20 | UserLoggedIn = await AuthService.GetUser();
21 | await GetTable();
22 | }
23 |
24 | // ---------------- SELECT TABLE
25 | private IReadOnlyList? TodoList { get; set; }
26 | private IReadOnlyList? TodoListFiltered { get; set; }
27 | private MudTable? _table;
28 | protected async Task GetTable()
29 | {
30 | // await Task.Delay(10000);
31 | IReadOnlyList todos = await DatabaseService.From();
32 | TodoList = todos;
33 | TodoListFiltered = todos;
34 | await InvokeAsync(StateHasChanged);
35 | }
36 |
37 | // ---------------- SEARCH
38 | private void OnValueChangedSearch(string text)
39 | {
40 | TodoListFiltered = TodoList?.Where(row => row.Title.Contains(text)).ToList();
41 | }
42 |
43 | // ---------------- DELETE
44 | private async Task OnClickDelete(TodoPrivate item)
45 | {
46 | if (UserLoggedIn is not null)
47 | {
48 | await DatabaseService.SoftDelete(item);
49 | await GetTable();
50 | }
51 | else
52 | {
53 | await DialogService.ShowMessageBox(
54 | "Warning",
55 | "You need to be logged In to create or change an item in this table."
56 | );
57 | }
58 | }
59 |
60 | // ---------------- CREATE NEW
61 |
62 | protected TodoPrivate Model = new();
63 | private bool _success = false;
64 | string[] _errors = { };
65 | MudForm? _form;
66 | private bool _processingNewItem = false;
67 | private async Task OnClickSave()
68 | {
69 | if (UserLoggedIn is not null && UserLoggedIn?.Id is not null)
70 | {
71 | Console.WriteLine("UserLoggedIn?.Id");
72 | Console.WriteLine(UserLoggedIn?.Id);
73 |
74 | Model.UserId = UserLoggedIn?.Id;
75 |
76 | _processingNewItem = true;
77 | try
78 | {
79 | await DatabaseService.Insert(Model);
80 | Model = new();
81 | await GetTable();
82 | }
83 | catch (Exception ex)
84 | {
85 | await DialogService.ShowMessageBox(
86 | "Warning",
87 | "This request was not completed because of some problem. Error message: "
88 | +ex.Message
89 | );
90 | }
91 | _success = false;
92 | _processingNewItem = false;
93 | }
94 | else
95 | {
96 | await DialogService.ShowMessageBox(
97 | "Warning",
98 | "You need to be logged In to create or change an item in this table."
99 | );
100 | }
101 | }
102 | }
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Pages/FileUploadFolder/FileUpload.razor:
--------------------------------------------------------------------------------
1 | @page "/uploadfile"
2 |
3 | @inject StorageService StorageService
4 | @inject ISnackbar Snackbar
5 | @inject IJSRuntime Js
6 | @inject IDialogService DialogService
7 |
8 |
9 |
10 |
File upload example
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
25 | Upload Files
26 |
27 |
28 |
29 | Max file size: @_maxFileSizeInMb MB
30 |
31 |
32 |
33 |
34 | List of files uploaded
35 |
36 |
37 | @if (FileObjects == null)
38 | {
39 |
40 |
41 |
42 | Title
43 | Action
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | }
54 | else if (FileObjects.Count <= 1)
55 | {
56 |
57 |
58 |
59 | Title
60 | Action
61 |
62 |
63 |
64 |
65 | There is no items in this table.
66 |
67 |
68 |
69 |
70 | } else
71 | {
72 |
73 |
74 |
75 | File name
76 | Created At
77 |
78 |
79 |
80 | @foreach (var row in FileObjects)
81 | {
82 | if (row.Name != ".emptyFolderPlaceholder")
83 | {
84 |
85 |
86 |
88 | @row.Name
89 |
90 | @row.CreatedAt
91 |
92 | }
93 | }
94 |
95 |
96 | }
97 |
98 |
99 |
100 |
101 |
102 |
103 | You need to be logged in to upload a file.
104 |
105 | Login
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Pages/FileUploadFolder/FileUpload.razor.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Components.Forms;
2 |
3 | namespace BlazorWebAssemblySupabaseTemplate.Pages.FileUploadFolder;
4 |
5 | public partial class FileUpload
6 | {
7 | protected override async Task OnInitializedAsync()
8 | {
9 | await GetFilesFromBucket();
10 | }
11 |
12 | public List? FileObjects;
13 | private async Task GetFilesFromBucket()
14 | {
15 | FileObjects = await StorageService.GetFilesFromBucket("userfiles");
16 | }
17 |
18 | static long _maxFileSizeInMb = 15;
19 | long _maxFileSize = 1024 * 1024 * _maxFileSizeInMb;
20 | private async Task UploadFilesAsync(IBrowserFile file)
21 | {
22 | Console.WriteLine("file.Name");
23 | Console.WriteLine(file.Name);
24 |
25 | try
26 | {
27 | var streamData = file.OpenReadStream(_maxFileSize);
28 |
29 | var filename = await StorageService.UploadFile("userfiles", streamData, file.Name);
30 |
31 | Snackbar.Add( "File uploaded: "+filename.Split("/").Last() );
32 |
33 | await GetFilesFromBucket();
34 | await InvokeAsync(StateHasChanged);
35 | }
36 | catch (IOException ex)
37 | {
38 | Snackbar.Add( "Error: Max file size exceeded." );
39 | }
40 | }
41 |
42 | private async Task DownloadClick(Supabase.Storage.FileObject row)
43 | {
44 | var result = await DialogService.ShowMessageBox(
45 | "Warning",
46 | "The download feature is disabled because of security risks, but it could be tested with your own risk by downloading the source code and running it. "
47 | );
48 |
49 | // byte[] downloadedBytes = await StorageService.DownloadFile("userfiles", row.Name);
50 |
51 | // await JS.InvokeVoidAsync("downloadFileFromStream", row.Name, downloadedBytes);
52 | }
53 | }
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Pages/Index.razor:
--------------------------------------------------------------------------------
1 | @page "/"
2 |
3 | Index
4 |
5 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Program.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Components.Web;
2 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
3 | using BlazorWebAssemblySupabaseTemplate;
4 | using Microsoft.AspNetCore.Components.Authorization;
5 | using BlazorWebAssemblySupabaseTemplate.Providers;
6 | using BlazorWebAssemblySupabaseTemplate.Services;
7 | using Blazored.LocalStorage;
8 | using MudBlazor.Services;
9 |
10 | var builder = WebAssemblyHostBuilder.CreateDefault(args);
11 | builder.RootComponents.Add("#app");
12 | builder.RootComponents.Add("head::after");
13 |
14 | builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
15 |
16 | builder.Services.AddMudServices();
17 | builder.Services.AddBlazoredLocalStorage();
18 |
19 |
20 | // ---------- BLAZOR AUTH
21 | builder.Services.AddScoped(
22 | provider => new CustomAuthStateProvider(
23 | provider.GetRequiredService(),
24 | provider.GetRequiredService(),
25 | provider.GetRequiredService>()
26 | )
27 | )
28 | ;
29 | builder.Services.AddAuthorizationCore();
30 |
31 | // ---------- SUPABASE
32 | var url = "https://pylnesfgmytjegzzculn.supabase.co";
33 | var key = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InB5bG5lc2ZnbXl0amVnenpjdWxuIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NjgyOTMwMzcsImV4cCI6MTk4Mzg2OTAzN30.kI29Q_qYWDH5SD6oi5NTwHG6Pxy1e1AUfR8s_ga45lE";
34 |
35 | builder.Services.AddScoped(
36 | provider => new Supabase.Client(
37 | url,
38 | key,
39 | new Supabase.SupabaseOptions
40 | {
41 | AutoRefreshToken = true,
42 | AutoConnectRealtime = true,
43 | SessionHandler = new CustomSupabaseSessionHandler(
44 | provider.GetRequiredService(),
45 | provider.GetRequiredService>()
46 | )
47 | }
48 | )
49 | );
50 |
51 | builder.Services.AddScoped();
52 | builder.Services.AddScoped();
53 | builder.Services.AddScoped();
54 |
55 | await builder.Build().RunAsync();
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:35974",
7 | "sslPort": 44392
8 | }
9 | },
10 | "profiles": {
11 | "http": {
12 | "commandName": "Project",
13 | "dotnetRunMessages": true,
14 | "launchBrowser": true,
15 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
16 | "applicationUrl": "http://localhost:5013",
17 | "environmentVariables": {
18 | "ASPNETCORE_ENVIRONMENT": "Development"
19 | }
20 | },
21 | "https": {
22 | "commandName": "Project",
23 | "dotnetRunMessages": true,
24 | "launchBrowser": true,
25 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
26 | "applicationUrl": "https://localhost:7152;http://localhost:5013",
27 | "environmentVariables": {
28 | "ASPNETCORE_ENVIRONMENT": "Development"
29 | }
30 | },
31 | "IIS Express": {
32 | "commandName": "IISExpress",
33 | "launchBrowser": true,
34 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
35 | "environmentVariables": {
36 | "ASPNETCORE_ENVIRONMENT": "Development"
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Providers/CustomAuthStateProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Security.Claims;
2 | using System.Text.Json;
3 | using Blazored.LocalStorage;
4 | using Microsoft.AspNetCore.Components.Authorization;
5 |
6 | // Credits https://github.com/patrickgod/BlazorAuthenticationTutorial
7 |
8 | namespace BlazorWebAssemblySupabaseTemplate.Providers;
9 |
10 | public class CustomAuthStateProvider : AuthenticationStateProvider
11 | {
12 | private readonly ILocalStorageService _localStorage;
13 | private readonly Supabase.Client _client;
14 |
15 | private readonly ILogger _logger;
16 |
17 | public CustomAuthStateProvider(
18 | ILocalStorageService localStorage,
19 | Supabase.Client client,
20 | ILogger logger
21 | )
22 | {
23 | logger.LogInformation("------------------- CONSTRUCTOR -------------------");
24 |
25 | _localStorage = localStorage;
26 | _client = client;
27 | _logger = logger;
28 | }
29 |
30 | public override async Task GetAuthenticationStateAsync()
31 | {
32 | _logger.LogInformation("------------------- GetAuthenticationStateAsync -------------------");
33 |
34 | // Sets client auth and connects to realtime (if enabled)
35 | await _client.InitializeAsync();
36 |
37 | var identity = new ClaimsIdentity();
38 | // _http.DefaultRequestHeaders.Authorization = null;
39 |
40 | if (!string.IsNullOrEmpty(_client.Auth.CurrentSession?.AccessToken))
41 | {
42 | identity = new ClaimsIdentity(ParseClaimsFromJwt(_client.Auth.CurrentSession.AccessToken), "jwt");
43 | // _http.DefaultRequestHeaders.Authorization =
44 | // new AuthenticationHeaderValue("Bearer", token.Replace("\"", ""));
45 | }
46 |
47 | var user = new ClaimsPrincipal(identity);
48 | var state = new AuthenticationState(user);
49 |
50 | NotifyAuthenticationStateChanged(Task.FromResult(state));
51 |
52 | return state;
53 | }
54 |
55 | public static IEnumerable ParseClaimsFromJwt(string jwt)
56 | {
57 | var payload = jwt.Split('.')[1];
58 | var jsonBytes = ParseBase64WithoutPadding(payload);
59 | var keyValuePairs = JsonSerializer.Deserialize>(jsonBytes);
60 | return keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()));
61 | }
62 |
63 | private static byte[] ParseBase64WithoutPadding(string base64)
64 | {
65 | switch (base64.Length % 4)
66 | {
67 | case 2: base64 += "=="; break;
68 | case 3: base64 += "="; break;
69 | }
70 | return Convert.FromBase64String(base64);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Providers/CustomSupabaseSessionHandler.cs:
--------------------------------------------------------------------------------
1 | using Blazored.LocalStorage;
2 | using Supabase.Gotrue;
3 | using Supabase.Gotrue.Interfaces;
4 |
5 | namespace BlazorWebAssemblySupabaseTemplate.Providers;
6 |
7 | public class CustomSupabaseSessionHandler : IGotrueSessionPersistence
8 | {
9 | private readonly ILocalStorageService _localStorage;
10 | private readonly ILogger _logger;
11 | private const string SessionKey = "SUPABASE_SESSION";
12 |
13 | public CustomSupabaseSessionHandler(
14 | ILocalStorageService localStorage,
15 | ILogger logger
16 | )
17 | {
18 | logger.LogInformation("------------------- CONSTRUCTOR -------------------");
19 | _localStorage = localStorage;
20 | _logger = logger;
21 | }
22 |
23 | public async void DestroySession()
24 | {
25 | _logger.LogInformation("------------------- SessionDestroyer -------------------");
26 | await _localStorage.RemoveItemAsync(SessionKey);
27 | }
28 |
29 | public async void SaveSession(Session session)
30 | {
31 | _logger.LogInformation("------------------- SessionPersistor -------------------");
32 | await _localStorage.SetItemAsync(SessionKey, session);
33 | }
34 |
35 | public Session? LoadSession()
36 | {
37 | _logger.LogInformation("------------------- SessionRetriever -------------------");
38 |
39 | var session = _localStorage.GetItemAsync(SessionKey).Result;
40 | return session?.ExpiresAt() <= DateTime.Now ? null : session;
41 | }
42 | }
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/README.MD:
--------------------------------------------------------------------------------
1 | #TODO
2 | - CustomSupabaseSessionHandler not working.
3 | - how to test:
4 | - Logout
5 | - Login
6 | - Access Crud table private RLS page
7 | - Insert some row
8 | - you will see the row displayed below
9 | - press F5 on the browser
10 | - the rows will disappear
11 | - Maybe i created CustomSupabaseSessionHandler wrong. But it seem like that the token is not being sent with the headers...
12 |
13 | - create policy to insert that check if the user_id is the same of the user logged in trying to insert.
14 |
15 | # Credits
16 | https://github.com/supabase-community/supabase-csharp
17 | https://github.com/patrickgod/BlazorAuthenticationTutorial
18 | https://github.com/d11-jwaring/SupabaseRealtimeBlazorWASM/tree/master
19 |
20 |
21 | # How to deploy
22 | dotnet publish -c Release -o release
23 | firebase deploy
24 |
25 | # Error message
26 | Failed to find a valid digest in the 'integrity' attribute for resource 'https://blazorwasmsupabasetemplate.web.app/_framework/blazor.boot.json' with computed SHA-256 integrity 'XdcujrjLMAFyEwhjckKrX5naw+S/ieI/g8U7BkEVUc8='. The resource has been blocked.
27 | Unknown error occurred while trying to verify integrity.
28 | service-worker.js:22 Uncaught (in promise) TypeError: Failed to fetch
29 | at service-worker.js:22:54
30 | at async onInstall (service-worker.js:22:5)
31 |
32 | -----> This is because of old files in cache in the browser. Clear cache by clicking in the clear button (just ctrl + f5 doesn't work) and after press ctrl + f5. This will solve.
33 |
34 |
35 |
36 | # JWT ERROR
37 | - WHEN CLICK IN LOGOUT BUTTON OR TRY TO GET DATA FROM DATABASE, BUT THE JWT IS ALREADY EXPIRED:
38 | - this problem only happens when login, close the app, later, after token expired, the app is open again.
39 | - I set the JWT expiry limit to 180 just to test this.
40 |
41 | blazor.webassembly.js:1 crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
42 | Unhandled exception rendering component: {"code":401,"msg":"invalid JWT: unable to parse or verify signature, token is expired by 22m22s"}
43 | Supabase.Gotrue.RequestException: {"code":401,"msg":"invalid JWT: unable to parse or verify signature, token is expired by 22m22s"}
44 | at Supabase.Gotrue.Helpers.MakeRequest(HttpMethod method, String url, Object data, Dictionary`2 headers)
45 | at Supabase.Gotrue.Client.SignOut()
46 | at BlazorWebAssemblySupabaseTemplate.Services.AuthService.Logout()
47 | at BlazorWebAssemblySupabaseTemplate.Shared.MainLayout.OnClickLogout()
48 | at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
49 | at MudBlazor.MudChip.OnClickHandler(MouseEventArgs ev)
50 | at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
51 | at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task , ComponentState )
52 |
53 |
54 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Services/AuthService.cs:
--------------------------------------------------------------------------------
1 | using Blazored.LocalStorage;
2 | using Microsoft.AspNetCore.Components.Authorization;
3 | using Supabase.Gotrue;
4 |
5 | namespace BlazorWebAssemblySupabaseTemplate.Services;
6 |
7 | public class AuthService
8 | {
9 | private readonly Supabase.Client _client;
10 | private readonly AuthenticationStateProvider _customAuthStateProvider;
11 | private readonly ILocalStorageService _localStorage;
12 | private readonly ILogger _logger;
13 |
14 | public AuthService(
15 | Supabase.Client client,
16 | AuthenticationStateProvider customAuthStateProvider,
17 | ILocalStorageService localStorage,
18 | ILogger logger
19 | )
20 | {
21 | logger.LogInformation("------------------- CONSTRUCTOR -------------------");
22 |
23 | _client = client;
24 | _customAuthStateProvider = customAuthStateProvider;
25 | _localStorage = localStorage;
26 | _logger = logger;
27 | }
28 |
29 | public async Task Login(string email, string password)
30 | {
31 | _logger.LogInformation("METHOD: Login");
32 |
33 | var session = await _client.Auth.SignIn(email, password);
34 |
35 | _logger.LogInformation("------------------- User logged in -------------------");
36 | // logger.LogInformation($"instance.Auth.CurrentUser.Id {client?.Auth?.CurrentUser?.Id}");
37 | _logger.LogInformation($"client.Auth.CurrentUser.Email {_client?.Auth?.CurrentUser?.Email}");
38 |
39 | await _customAuthStateProvider.GetAuthenticationStateAsync();
40 | }
41 |
42 | public async Task Logout()
43 | {
44 | await _client.Auth.SignOut();
45 | await _localStorage.RemoveItemAsync("token");
46 | await _customAuthStateProvider.GetAuthenticationStateAsync();
47 | }
48 |
49 | public async Task GetUser()
50 | {
51 | var session = await _client.Auth.RetrieveSessionAsync();
52 | return session?.User;
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Services/DatabaseService.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using Blazored.LocalStorage;
3 | using Microsoft.AspNetCore.Components.Authorization;
4 | using MudBlazor;
5 | using Supabase.Functions;
6 |
7 | namespace BlazorWebAssemblySupabaseTemplate.Services;
8 |
9 | public class DatabaseService
10 | {
11 | private readonly Supabase.Client _client;
12 | private readonly AuthenticationStateProvider _customAuthStateProvider;
13 | private readonly ILocalStorageService _localStorage;
14 | private readonly ILogger _logger;
15 | private readonly IDialogService _dialogService;
16 |
17 | public DatabaseService(
18 | Supabase.Client client,
19 | AuthenticationStateProvider customAuthStateProvider,
20 | ILocalStorageService localStorage,
21 | ILogger logger,
22 | IDialogService dialogService)
23 | {
24 | logger.LogInformation("------------------- CONSTRUCTOR -------------------");
25 |
26 | _client = client;
27 | _customAuthStateProvider = customAuthStateProvider;
28 | _localStorage = localStorage;
29 | _logger = logger;
30 | _dialogService = dialogService;
31 | }
32 |
33 | public async Task> From() where TModel : BaseModelApp, new()
34 | {
35 | var modeledResponse = await _client
36 | .From()
37 | .Where(x => x.SoftDeleted == false)
38 | .Get();
39 | return modeledResponse.Models;
40 | }
41 |
42 | public async Task> Delete(TModel item) where TModel : BaseModelApp, new()
43 | {
44 | var modeledResponse = await _client
45 | .From()
46 | .Delete(item);
47 | return modeledResponse.Models;
48 | }
49 |
50 | public async Task?> Insert(TModel item) where TModel : BaseModelApp, new()
51 | {
52 | Postgrest.Responses.ModeledResponse modeledResponse;
53 | try
54 | {
55 | modeledResponse = await _client
56 | .From()
57 | .Insert(item);
58 |
59 | return modeledResponse.Models;
60 | }
61 | catch (Client.RequestException ex)
62 | {
63 | if(ex.Response?.StatusCode == HttpStatusCode.Forbidden)
64 | await _dialogService.ShowMessageBox(
65 | "Warning",
66 | "This database request was forbidden."
67 | );
68 | else
69 | await _dialogService.ShowMessageBox(
70 | "Warning",
71 | "This request was not completed because of some problem with the http request. \n "
72 | +ex.Response?.RequestMessage
73 | );
74 | }
75 |
76 | return null;
77 | }
78 |
79 | public async Task> SoftDelete(TModel item) where TModel : BaseModelApp, new()
80 | {
81 | var modeledResponse = await _client.Postgrest
82 | .Table()
83 | .Set(x => x.SoftDeleted, true)
84 | .Set(x => x.SoftDeletedAt, DateTime.Now)
85 | .Where(x => x.Id == item.Id)
86 | // .Filter(x => x.Id, Operator.Equals, item.Id)
87 | .Update();
88 | return modeledResponse.Models;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Services/StorageService.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using MudBlazor;
3 | using Supabase.Storage.Interfaces;
4 |
5 | namespace BlazorWebAssemblySupabaseTemplate.Services;
6 |
7 | public class StorageService
8 | {
9 | private readonly Supabase.Client _client;
10 | private readonly ILogger _logger;
11 | private readonly IDialogService _dialogService;
12 | private readonly IStorageClient _storage;
13 |
14 | public StorageService(
15 | Supabase.Client client,
16 | ILogger logger,
17 | IDialogService dialogService
18 | )
19 | {
20 | logger.LogInformation("------------------- CONSTRUCTOR -------------------");
21 | _client = client;
22 | _logger = logger;
23 | _dialogService = dialogService;
24 |
25 | _storage = client.Storage;
26 | }
27 |
28 | public async Task UploadFile(String bucketName, Stream streamData, String fileName)
29 | {
30 | var bucket = _storage.From(bucketName);
31 |
32 | // TODO: verify if there is a better way to do it
33 | // Maybe this isn't a good way to do it
34 | var bytesData = await StreamToBytesAsync(streamData);
35 |
36 | var fileExtension = fileName.Split(".").Last();
37 |
38 | var saveName = "File_" + DateTime.Now;
39 |
40 | saveName = saveName.Replace("/", "_").Replace(" ", "_").Replace(":", "_");
41 | saveName = saveName + "." + fileExtension;
42 |
43 | // Console.WriteLine("saveName");
44 | // Console.WriteLine(saveName);
45 |
46 | return await bucket.Upload(bytesData, saveName);
47 | }
48 |
49 | public async Task StreamToBytesAsync(Stream streamData)
50 | {
51 | byte[] bytes;
52 |
53 | using var memoryStream = new MemoryStream();
54 | await streamData.CopyToAsync(memoryStream);
55 | bytes = memoryStream.ToArray();
56 |
57 | return bytes;
58 | }
59 |
60 | public async Task?> GetFilesFromBucket(string bucketName)
61 | {
62 | return await _storage.From(bucketName).List();
63 | }
64 |
65 | public Task DownloadFile(string bucketName, string fileName)
66 | {
67 | var bucket = _storage.From(bucketName);
68 | return bucket.Download(fileName, (_, f) => Debug.WriteLine($"Download Progress: {f}%"));
69 | }
70 | }
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Shared/MainLayout.razor:
--------------------------------------------------------------------------------
1 | @inherits LayoutComponentBase
2 |
3 | @inject AuthService AuthService
4 | @inject NavigationManager NavigationManager
5 | @inject ISnackbar Snackbar
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
15 |
16 |
17 |
18 |
20 |
21 |
22 |
23 |
24 | Logout
25 |
26 |
27 | Login
28 |
29 |
30 |
31 |
32 |
33 | Blazor WASM Supabase Demo
34 |
35 |
36 | @* ********************************** MENU ********************************** *@
37 |
38 |
39 |
40 | Crud table
41 |
42 |
43 | Crud table private RLS
44 |
45 |
46 | Upload File
47 |
48 |
49 |
50 |
51 | @* ********************************** MENU ********************************** *@
52 |
53 |
54 |
55 | @Body
56 |
57 |
58 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Shared/MainLayout.razor.cs:
--------------------------------------------------------------------------------
1 | namespace BlazorWebAssemblySupabaseTemplate.Shared;
2 |
3 | public partial class MainLayout
4 | {
5 | bool _drawerOpen = true;
6 |
7 | void DrawerToggle()
8 | {
9 | _drawerOpen = !_drawerOpen;
10 | }
11 |
12 | private async Task OnClickLogout()
13 | {
14 | await AuthService.Logout();
15 | Snackbar.Add("Logout successful");
16 | NavigationManager.NavigateTo($"/");
17 | }
18 | }
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Shared/MainLayout.razor.css:
--------------------------------------------------------------------------------
1 | .page {
2 | position: relative;
3 | display: flex;
4 | flex-direction: column;
5 | }
6 |
7 | main {
8 | flex: 1;
9 | }
10 |
11 | .sidebar {
12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
13 | }
14 |
15 | .top-row {
16 | background-color: #f7f7f7;
17 | border-bottom: 1px solid #d6d5d5;
18 | justify-content: flex-end;
19 | height: 3.5rem;
20 | display: flex;
21 | align-items: center;
22 | }
23 |
24 | .top-row ::deep a, .top-row ::deep .btn-link {
25 | white-space: nowrap;
26 | margin-left: 1.5rem;
27 | text-decoration: none;
28 | }
29 |
30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
31 | text-decoration: underline;
32 | }
33 |
34 | .top-row ::deep a:first-child {
35 | overflow: hidden;
36 | text-overflow: ellipsis;
37 | }
38 |
39 | @media (max-width: 640.98px) {
40 | .top-row:not(.auth) {
41 | display: none;
42 | }
43 |
44 | .top-row.auth {
45 | justify-content: space-between;
46 | }
47 |
48 | .top-row ::deep a, .top-row ::deep .btn-link {
49 | margin-left: 0;
50 | }
51 | }
52 |
53 | @media (min-width: 641px) {
54 | .page {
55 | flex-direction: row;
56 | }
57 |
58 | .sidebar {
59 | width: 250px;
60 | height: 100vh;
61 | position: sticky;
62 | top: 0;
63 | }
64 |
65 | .top-row {
66 | position: sticky;
67 | top: 0;
68 | z-index: 1;
69 | }
70 |
71 | .top-row.auth ::deep a:first-child {
72 | flex: 1;
73 | text-align: right;
74 | width: 0;
75 | }
76 |
77 | .top-row, article {
78 | padding-left: 2rem !important;
79 | padding-right: 1.5rem !important;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/Supabase/DatabasePolicies.sql:
--------------------------------------------------------------------------------
1 | -- CREATE POLICY "policy_name"
2 | -- ON public.Lista FOR
3 | -- DELETE USING ( auth.uid() = user_id );
4 |
5 | -- CREATE POLICY "policy_name"
6 | -- ON public.Lista FOR
7 | -- SELECT USING ( auth.uid() = user_id );
8 |
9 | -- #################### TABELA LISTA
10 |
11 | -- CREATE POLICY "Enable read access for all users" ON "public"."Lista"
12 | -- AS PERMISSIVE FOR SELECT
13 | -- TO authenticated
14 | -- USING (true)
15 |
16 |
17 | CREATE POLICY "Users can SELECT if own row"
18 | ON "public"."TodoPrivate" AS PERMISSIVE FOR SELECT
19 | TO authenticated
20 | USING ( auth.uid() = user_id );
21 |
22 | CREATE POLICY "Users can UPDATE if own row"
23 | ON "public"."TodoPrivate" AS PERMISSIVE FOR UPDATE
24 | TO authenticated
25 | USING ( auth.uid() = user_id );
26 |
27 | CREATE POLICY "Users can DELETE if own row"
28 | ON "public"."TodoPrivate" AS PERMISSIVE FOR DELETE
29 | TO authenticated
30 | USING ( auth.uid() = user_id );
31 |
32 | CREATE POLICY "Users can INSERT only if own uuid"
33 | ON "public"."TodoPrivate"
34 | AS PERMISSIVE
35 | FOR INSERT
36 | TO authenticated
37 | WITH CHECK (auth.uid() = user_id);
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/_Imports.razor:
--------------------------------------------------------------------------------
1 | @using System.Net.Http
2 | @using System.Net.Http.Json
3 | @using Microsoft.AspNetCore.Components.Forms
4 | @using Microsoft.AspNetCore.Components.Routing
5 | @using Microsoft.AspNetCore.Components.Web
6 | @using Microsoft.AspNetCore.Components.Web.Virtualization
7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http
8 | @using Microsoft.JSInterop
9 | @using BlazorWebAssemblySupabaseTemplate
10 | @using BlazorWebAssemblySupabaseTemplate.Shared
11 | @using Microsoft.AspNetCore.Components.Authorization
12 | @using Microsoft.AspNetCore.Authorization
13 | @using MudBlazor
14 |
15 | @using BlazorWebAssemblySupabaseTemplate.Services
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "public": "release/wwwroot",
4 | "ignore": [
5 | "firebase.json",
6 | "**/.*",
7 | "**/node_modules/**"
8 | ],
9 | "rewrites": [
10 | {
11 | "source": "**",
12 | "destination": "/index.html"
13 | }
14 | ]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/css/app.css:
--------------------------------------------------------------------------------
1 | /* @import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); */
2 |
3 | html, body {
4 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
5 | }
6 |
7 | h1:focus {
8 | outline: none;
9 | }
10 |
11 | a, .btn-link {
12 | color: #0071c1;
13 | }
14 |
15 | .btn-primary {
16 | color: #fff;
17 | background-color: #1b6ec2;
18 | border-color: #1861ac;
19 | }
20 |
21 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
22 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
23 | }
24 |
25 | .content {
26 | padding-top: 1.1rem;
27 | }
28 |
29 | .valid.modified:not([type=checkbox]) {
30 | outline: 1px solid #26b050;
31 | }
32 |
33 | .invalid {
34 | outline: 1px solid red;
35 | }
36 |
37 | .validation-message {
38 | color: red;
39 | }
40 |
41 | #blazor-error-ui {
42 | background: lightyellow;
43 | bottom: 0;
44 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
45 | display: none;
46 | left: 0;
47 | padding: 0.6rem 1.25rem 0.7rem 1.25rem;
48 | position: fixed;
49 | width: 100%;
50 | z-index: 1000;
51 | }
52 |
53 | #blazor-error-ui .dismiss {
54 | cursor: pointer;
55 | position: absolute;
56 | right: 0.75rem;
57 | top: 0.5rem;
58 | }
59 |
60 | .blazor-error-boundary {
61 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
62 | padding: 1rem 1rem 1rem 3.7rem;
63 | color: white;
64 | }
65 |
66 | .blazor-error-boundary::after {
67 | content: "An error has occurred."
68 | }
69 |
70 | .loading-progress {
71 | position: relative;
72 | display: block;
73 | width: 8rem;
74 | height: 8rem;
75 | margin: 20vh auto 1rem auto;
76 | }
77 |
78 | .loading-progress circle {
79 | fill: none;
80 | stroke: #e0e0e0;
81 | stroke-width: 0.6rem;
82 | transform-origin: 50% 50%;
83 | transform: rotate(-90deg);
84 | }
85 |
86 | .loading-progress circle:last-child {
87 | stroke: #1b6ec2;
88 | stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
89 | transition: stroke-dasharray 0.05s ease-in-out;
90 | }
91 |
92 | .loading-progress-text {
93 | position: absolute;
94 | text-align: center;
95 | font-weight: bold;
96 | inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
97 | }
98 |
99 | .loading-progress-text:after {
100 | content: var(--blazor-load-percentage-text, "Loading");
101 | }
102 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/favicon.png
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/icon-192.png
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/icon-512.png
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | BlazorWebAssemblySupabaseTemplate
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | An unhandled error has occurred.
32 |
Reload
33 |
🗙
34 |
35 |
36 |
37 |
38 |
39 |
40 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "BlazorWebAssemblySupabaseTemplate",
3 | "short_name": "BlazorWebAssemblySupabaseTemplate",
4 | "start_url": "./",
5 | "display": "standalone",
6 | "background_color": "#ffffff",
7 | "theme_color": "#03173d",
8 | "prefer_related_applications": false,
9 | "icons": [
10 | {
11 | "src": "icon-512.png",
12 | "type": "image/png",
13 | "sizes": "512x512"
14 | },
15 | {
16 | "src": "icon-192.png",
17 | "type": "image/png",
18 | "sizes": "192x192"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/sample-data/weather.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "date": "2022-01-06",
4 | "temperatureC": 1,
5 | "summary": "Freezing"
6 | },
7 | {
8 | "date": "2022-01-07",
9 | "temperatureC": 14,
10 | "summary": "Bracing"
11 | },
12 | {
13 | "date": "2022-01-08",
14 | "temperatureC": -13,
15 | "summary": "Freezing"
16 | },
17 | {
18 | "date": "2022-01-09",
19 | "temperatureC": -16,
20 | "summary": "Balmy"
21 | },
22 | {
23 | "date": "2022-01-10",
24 | "temperatureC": -2,
25 | "summary": "Chilly"
26 | }
27 | ]
28 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/service-worker.js:
--------------------------------------------------------------------------------
1 | // In development, always fetch from the network and do not enable offline support.
2 | // This is because caching would make development more difficult (changes would not
3 | // be reflected on the first load after each change).
4 | self.addEventListener('fetch', () => { });
5 |
--------------------------------------------------------------------------------
/Examples/BlazorWebAssemblySupabaseTemplate/wwwroot/service-worker.published.js:
--------------------------------------------------------------------------------
1 | // Caution! Be sure you understand the caveats before publishing an application with
2 | // offline support. See https://aka.ms/blazor-offline-considerations
3 |
4 | self.importScripts('./service-worker-assets.js');
5 | self.addEventListener('install', event => event.waitUntil(onInstall(event)));
6 | self.addEventListener('activate', event => event.waitUntil(onActivate(event)));
7 | self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
8 |
9 | const cacheNamePrefix = 'offline-cache-';
10 | const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
11 | const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/ ];
12 | const offlineAssetsExclude = [ /^service-worker\.js$/ ];
13 |
14 | async function onInstall(event) {
15 | console.info('Service worker: Install');
16 |
17 | // Fetch and cache all matching items from the assets manifest
18 | const assetsRequests = self.assetsManifest.assets
19 | .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
20 | .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
21 | .map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' }));
22 | await caches.open(cacheName).then(cache => cache.addAll(assetsRequests));
23 | }
24 |
25 | async function onActivate(event) {
26 | console.info('Service worker: Activate');
27 |
28 | // Delete unused caches
29 | const cacheKeys = await caches.keys();
30 | await Promise.all(cacheKeys
31 | .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName)
32 | .map(key => caches.delete(key)));
33 | }
34 |
35 | async function onFetch(event) {
36 | let cachedResponse = null;
37 | if (event.request.method === 'GET') {
38 | // For all navigation requests, try to serve index.html from cache
39 | // If you need some URLs to be server-rendered, edit the following check to exclude those URLs
40 | const shouldServeIndexHtml = event.request.mode === 'navigate';
41 |
42 | const request = shouldServeIndexHtml ? 'index.html' : event.request;
43 | const cache = await caches.open(cacheName);
44 | cachedResponse = await cache.match(request);
45 | }
46 |
47 | return cachedResponse || fetch(event.request);
48 | }
49 |
--------------------------------------------------------------------------------
/Examples/SupabaseExample/Assets/supabase-csharp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/SupabaseExample/Assets/supabase-csharp.png
--------------------------------------------------------------------------------
/Examples/SupabaseExample/Models/Channel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Supabase.Postgrest.Attributes;
3 | using Supabase.Postgrest.Models;
4 |
5 | namespace SupabaseExample.Models
6 | {
7 | [Table("channels")]
8 | public class Channel : BaseModel
9 | {
10 | [PrimaryKey("id", false)] // Key is Autogenerated
11 | public int Id { get; set; }
12 |
13 | [Column("inserted_at")]
14 | public DateTime InsertedAt { get; set; }
15 |
16 | [Column("slug")]
17 | public string Slug { get; set; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Examples/SupabaseExample/Models/Movie.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Supabase.Postgrest.Attributes;
4 | using Supabase.Postgrest.Models;
5 |
6 | namespace SupabaseExample.Models
7 | {
8 | [Table("movie")]
9 | public class Movie : BaseModel
10 | {
11 | [PrimaryKey("id", false)]
12 | public int Id { get; set; }
13 |
14 | [Column("name")]
15 | public string Name { get; set; }
16 |
17 | [Reference(typeof(Person))]
18 | public List Persons { get; set; }
19 |
20 |
21 | [Column("created_at")]
22 | public DateTime CreatedAt { get; set; }
23 | }
24 |
25 | [Table("person")]
26 | public class Person : BaseModel
27 | {
28 | [PrimaryKey("id", false)]
29 | public int Id { get; set; }
30 |
31 | [Column("first_name")]
32 | public string FirstName { get; set; }
33 |
34 | [Column("last_name")]
35 | public string LastName { get; set; }
36 |
37 | [Reference(typeof(Profile))]
38 | public Profile Profile { get; set; }
39 |
40 | [Column("created_at")]
41 | public DateTime CreatedAt { get; set; }
42 | }
43 |
44 | [Table("profile")]
45 | public class Profile : BaseModel
46 | {
47 | [Column("email")]
48 | public string Email { get; set; }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Examples/SupabaseExample/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Reflection;
5 | using System.Threading.Tasks;
6 | using Supabase.Gotrue;
7 | using Supabase.Gotrue.Interfaces;
8 | using static Supabase.Realtime.PostgresChanges.PostgresChangesOptions;
9 | using Constants = Supabase.Realtime.Constants;
10 |
11 | namespace SupabaseExample
12 | {
13 |
14 | class Program
15 | {
16 | static async Task Main(string[] args)
17 | {
18 | // Be sure to set this in your Debug Options.
19 | var url = Environment.GetEnvironmentVariable("SUPABASE_URL");
20 | var key = Environment.GetEnvironmentVariable("SUPABASE_KEY");
21 |
22 | var supabase = new Supabase.Client(url, key, new Supabase.SupabaseOptions { AutoConnectRealtime = true });
23 | await supabase.InitializeAsync();
24 |
25 | var reference = supabase.From();
26 |
27 | await reference.On(ListenType.All,
28 | (sender, response) =>
29 | {
30 | switch (response.Event)
31 | {
32 | case Constants.EventType.Insert:
33 | break;
34 | case Constants.EventType.Update:
35 | break;
36 | case Constants.EventType.Delete:
37 | break;
38 | }
39 | Debug.WriteLine($"[{response.Event}]:{response.Topic}:{response.Payload.Data}");
40 | });
41 |
42 | var channels = await reference.Get();
43 |
44 | //await reference.Insert(new Models.Channel { Slug = GenerateName(10), InsertedAt = DateTime.Now });
45 |
46 | #region Storage
47 |
48 | var storage = supabase.Storage;
49 |
50 | var exists = await storage.GetBucket("testing") != null;
51 | if (!exists)
52 | await storage.CreateBucket("testing", new Supabase.Storage.BucketUpsertOptions { Public = true });
53 |
54 | var buckets = await storage.ListBuckets();
55 |
56 | foreach (var b in buckets)
57 | Debug.WriteLine($"[{b.Id}] {b.Name}");
58 |
59 | var bucket = storage.From("testing");
60 | var basePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase).Replace("file:", "")
61 | .Replace("C:\\", "");
62 | var imagePath = Path.Combine(basePath, "Assets", "supabase-csharp.png");
63 |
64 | Debug.WriteLine(await bucket.Upload(imagePath, "supabase-csharp.png",
65 | new Supabase.Storage.FileOptions { Upsert = true },
66 | (sender, args) => Debug.WriteLine($"Upload Progress: {args}%")));
67 | Debug.WriteLine(bucket.GetPublicUrl("supabase-csharp.png"));
68 | Debug.WriteLine(await bucket.CreateSignedUrl("supabase-csharp.png", 3600));
69 |
70 | var bucketItems = await bucket.List();
71 |
72 | foreach (var item in bucketItems)
73 | Debug.WriteLine($"[{item.Id}] {item.Name} - {item.CreatedAt}");
74 |
75 | Debug.WriteLine(await bucket.Download("supabase-csharp.png", Path.Combine(basePath, "testing-download.png"),
76 | (sender, args) => Debug.WriteLine($"Download Progress: {args}%")));
77 |
78 | await storage.EmptyBucket("testing");
79 | await storage.DeleteBucket("testing");
80 |
81 |
82 | #endregion
83 | }
84 |
85 | // From: https://stackoverflow.com/a/49922533/3629438
86 | static string GenerateName(int len)
87 | {
88 | Random r = new Random();
89 | string[] consonants =
90 | {
91 | "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "l", "n", "p", "q", "r", "s", "sh", "zh", "t", "v",
92 | "w", "x"
93 | };
94 | string[] vowels = { "a", "e", "i", "o", "u", "ae", "y" };
95 | string Name = "";
96 | Name += consonants[r.Next(consonants.Length)].ToUpper();
97 | Name += vowels[r.Next(vowels.Length)];
98 | int
99 | b = 2; //b tells how many times a new letter has been added. It's 2 right now because the first two letters are already in the name.
100 | while (b < len)
101 | {
102 | Name += consonants[r.Next(consonants.Length)];
103 | b++;
104 | Name += vowels[r.Next(vowels.Length)];
105 | b++;
106 | }
107 |
108 | return Name;
109 | }
110 | }
111 | }
--------------------------------------------------------------------------------
/Examples/SupabaseExample/SupabaseExample.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net7.0
6 | 0.7.2
7 | 9
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | PreserveNewest
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Examples/SupabaseExample/db/00-schema.sql:
--------------------------------------------------------------------------------
1 | ALTER SYSTEM SET wal_level='logical';
2 | ALTER SYSTEM SET max_wal_senders='10';
3 | ALTER SYSTEM SET max_replication_slots='10';
4 |
5 | -- Create the Replication publication
6 | CREATE PUBLICATION supabase_realtime FOR ALL TABLES;
7 |
8 | -- Create a second schema
9 | CREATE SCHEMA personal;
10 |
11 | -- USERS
12 | CREATE TYPE public.user_status AS ENUM ('ONLINE', 'OFFLINE');
13 | CREATE TABLE public.users (
14 | username text primary key,
15 | inserted_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
16 | updated_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
17 | data jsonb DEFAULT null,
18 | age_range int4range DEFAULT null,
19 | status user_status DEFAULT 'ONLINE'::public.user_status,
20 | catchphrase tsvector DEFAULT null
21 | );
22 | ALTER TABLE public.users REPLICA IDENTITY FULL; -- Send "previous data" to supabase
23 | COMMENT ON COLUMN public.users.data IS 'For unstructured data and prototyping.';
24 |
25 | -- CHANNELS
26 | CREATE TABLE public.channels (
27 | id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
28 | inserted_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
29 | updated_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
30 | data jsonb DEFAULT null,
31 | slug text
32 | );
33 | ALTER TABLE public.users REPLICA IDENTITY FULL; -- Send "previous data" to supabase
34 | COMMENT ON COLUMN public.channels.data IS 'For unstructured data and prototyping.';
35 |
36 | -- MESSAGES
37 | CREATE TABLE public.messages (
38 | id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
39 | inserted_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
40 | updated_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
41 | data jsonb DEFAULT null,
42 | message text,
43 | username text REFERENCES users NOT NULL,
44 | channel_id bigint REFERENCES channels NOT NULL
45 | );
46 | ALTER TABLE public.messages REPLICA IDENTITY FULL; -- Send "previous data" to supabase
47 | COMMENT ON COLUMN public.messages.data IS 'For unstructured data and prototyping.';
48 |
49 | -- STORED FUNCTION
50 | CREATE FUNCTION public.get_status(name_param text)
51 | RETURNS user_status AS $$
52 | SELECT status from users WHERE username=name_param;
53 | $$ LANGUAGE SQL IMMUTABLE;
54 |
55 | -- SECOND SCHEMA USERS
56 | CREATE TYPE personal.user_status AS ENUM ('ONLINE', 'OFFLINE');
57 | CREATE TABLE personal.users(
58 | username text primary key,
59 | inserted_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
60 | updated_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
61 | data jsonb DEFAULT null,
62 | age_range int4range DEFAULT null,
63 | status user_status DEFAULT 'ONLINE'::public.user_status
64 | );
65 |
66 | -- SECOND SCHEMA STORED FUNCTION
67 | CREATE FUNCTION personal.get_status(name_param text)
68 | RETURNS user_status AS $$
69 | SELECT status from users WHERE username=name_param;
70 | $$ LANGUAGE SQL IMMUTABLE;
71 |
72 |
73 | CREATE SCHEMA IF NOT EXISTS auth AUTHORIZATION postgres;
74 | -- auth.users definition
75 | CREATE TABLE auth.users (
76 | instance_id uuid NULL,
77 | id uuid NOT NULL,
78 | aud varchar(255) NULL,
79 | "role" varchar(255) NULL,
80 | email varchar(255) NULL,
81 | encrypted_password varchar(255) NULL,
82 | confirmed_at timestamptz NULL,
83 | invited_at timestamptz NULL,
84 | confirmation_token varchar(255) NULL,
85 | confirmation_sent_at timestamptz NULL,
86 | recovery_token varchar(255) NULL,
87 | recovery_sent_at timestamptz NULL,
88 | email_change_token varchar(255) NULL,
89 | email_change varchar(255) NULL,
90 | email_change_sent_at timestamptz NULL,
91 | last_sign_in_at timestamptz NULL,
92 | raw_app_meta_data jsonb NULL,
93 | raw_user_meta_data jsonb NULL,
94 | is_super_admin bool NULL,
95 | created_at timestamptz NULL,
96 | updated_at timestamptz NULL,
97 | CONSTRAINT users_pkey PRIMARY KEY (id)
98 | );
99 | CREATE INDEX users_instance_id_email_idx ON auth.users USING btree (instance_id, email);
100 | CREATE INDEX users_instance_id_idx ON auth.users USING btree (instance_id);
101 | -- auth.refresh_tokens definition
102 | CREATE TABLE auth.refresh_tokens (
103 | instance_id uuid NULL,
104 | id bigserial NOT NULL,
105 | "token" varchar(255) NULL,
106 | user_id varchar(255) NULL,
107 | revoked bool NULL,
108 | created_at timestamptz NULL,
109 | updated_at timestamptz NULL,
110 | CONSTRAINT refresh_tokens_pkey PRIMARY KEY (id)
111 | );
112 | CREATE INDEX refresh_tokens_instance_id_idx ON auth.refresh_tokens USING btree (instance_id);
113 | CREATE INDEX refresh_tokens_instance_id_user_id_idx ON auth.refresh_tokens USING btree (instance_id, user_id);
114 | CREATE INDEX refresh_tokens_token_idx ON auth.refresh_tokens USING btree (token);
115 | -- auth.instances definition
116 | CREATE TABLE auth.instances (
117 | id uuid NOT NULL,
118 | uuid uuid NULL,
119 | raw_base_config text NULL,
120 | created_at timestamptz NULL,
121 | updated_at timestamptz NULL,
122 | CONSTRAINT instances_pkey PRIMARY KEY (id)
123 | );
124 | -- auth.audit_log_entries definition
125 | CREATE TABLE auth.audit_log_entries (
126 | instance_id uuid NULL,
127 | id uuid NOT NULL,
128 | payload json NULL,
129 | created_at timestamptz NULL,
130 | CONSTRAINT audit_log_entries_pkey PRIMARY KEY (id)
131 | );
132 | CREATE INDEX audit_logs_instance_id_idx ON auth.audit_log_entries USING btree (instance_id);
133 | -- auth.schema_migrations definition
134 | CREATE TABLE auth.schema_migrations (
135 | "version" varchar(255) NOT NULL,
136 | CONSTRAINT schema_migrations_pkey PRIMARY KEY ("version")
137 | );
138 | INSERT INTO auth.schema_migrations (version)
139 | VALUES ('20171026211738'),
140 | ('20171026211808'),
141 | ('20171026211834'),
142 | ('20180103212743'),
143 | ('20180108183307'),
144 | ('20180119214651'),
145 | ('20180125194653');
146 | -- Gets the User ID from the request cookie
147 | create or replace function auth.uid() returns uuid as $$
148 | select nullif(current_setting('request.jwt.claim.sub', true), '')::uuid;
149 | $$ language sql stable;
150 | -- Gets the User ID from the request cookie
151 | create or replace function auth.role() returns text as $$
152 | select nullif(current_setting('request.jwt.claim.role', true), '')::text;
153 | $$ language sql stable;
154 | GRANT ALL PRIVILEGES ON SCHEMA auth TO postgres;
155 | GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA auth TO postgres;
156 | GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA auth TO postgres;
157 | ALTER USER postgres SET search_path = "auth";
158 |
--------------------------------------------------------------------------------
/Examples/SupabaseExample/db/01-dummy-data.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO
2 | public.users (username, status, age_range, catchphrase)
3 | VALUES
4 | ('supabot', 'ONLINE', '[1,2)'::int4range, 'fat cat'::tsvector),
5 | ('kiwicopple', 'OFFLINE', '[25,35)'::int4range, 'cat bat'::tsvector),
6 | ('awailas', 'ONLINE', '[25,35)'::int4range, 'bat rat'::tsvector),
7 | ('dragarcia', 'ONLINE', '[20,30)'::int4range, 'rat fat'::tsvector);
8 |
9 | INSERT INTO
10 | public.channels (slug)
11 | VALUES
12 | ('public'),
13 | ('random');
14 |
15 | INSERT INTO
16 | public.messages (message, channel_id, username)
17 | VALUES
18 | ('Hello World 👋', 1, 'supabot'),
19 | ('Perfection is attained, not when there is nothing more to add, but when there is nothing left to take away.', 2, 'supabot');
20 |
21 | INSERT INTO
22 | personal.users (username, status, age_range)
23 | VALUES
24 | ('supabot', 'ONLINE', '[1,2)'::int4range),
25 | ('kiwicopple', 'OFFLINE', '[25,35)'::int4range),
26 | ('awailas', 'ONLINE', '[25,35)'::int4range),
27 | ('dragarcia', 'ONLINE', '[20,30)'::int4range),
28 | ('leroyjenkins', 'ONLINE', '[20,40)'::int4range);
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExample.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F94175FC-DE2B-4DBC-9C79-2A97B8489412}"
5 | ProjectSection(SolutionItems) = preProject
6 | README.md = README.md
7 | EndProjectSection
8 | EndProject
9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SupabaseExampleXA", "SupabaseExampleXA\SupabaseExampleXA.csproj", "{0F986F22-CF68-40E8-B193-45880B7C4D49}"
10 | EndProject
11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SupabaseExampleXA.Android", "SupabaseExampleXA.Android\SupabaseExampleXA.Android.csproj", "{4C26C775-92CC-4EE4-A57B-51093C5DF5BF}"
12 | EndProject
13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SupabaseExampleXA.iOS", "SupabaseExampleXA.iOS\SupabaseExampleXA.iOS.csproj", "{A99C9838-F680-4388-838F-60DC72CDD257}"
14 | EndProject
15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Supabase", "..\..\Supabase\Supabase.csproj", "{313E0FFC-E478-4365-99FA-70B8BBC92A5D}"
16 | EndProject
17 | Global
18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
19 | Debug|Any CPU = Debug|Any CPU
20 | Release|Any CPU = Release|Any CPU
21 | Debug|iPhoneSimulator = Debug|iPhoneSimulator
22 | Release|iPhoneSimulator = Release|iPhoneSimulator
23 | Debug|iPhone = Debug|iPhone
24 | Release|iPhone = Release|iPhone
25 | EndGlobalSection
26 | GlobalSection(MonoDevelopProperties) = preSolution
27 | version = 0.1.0-prerelease
28 | EndGlobalSection
29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
30 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Release|Any CPU.Build.0 = Release|Any CPU
34 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
35 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
36 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
37 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
38 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Debug|iPhone.ActiveCfg = Debug|Any CPU
39 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Debug|iPhone.Build.0 = Debug|Any CPU
40 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Release|iPhone.ActiveCfg = Release|Any CPU
41 | {0F986F22-CF68-40E8-B193-45880B7C4D49}.Release|iPhone.Build.0 = Release|Any CPU
42 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
44 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
45 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Release|Any CPU.Build.0 = Release|Any CPU
46 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
47 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
48 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
49 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
50 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Debug|iPhone.ActiveCfg = Debug|Any CPU
51 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Debug|iPhone.Build.0 = Debug|Any CPU
52 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Release|iPhone.ActiveCfg = Release|Any CPU
53 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}.Release|iPhone.Build.0 = Release|Any CPU
54 | {A99C9838-F680-4388-838F-60DC72CDD257}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator
55 | {A99C9838-F680-4388-838F-60DC72CDD257}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator
56 | {A99C9838-F680-4388-838F-60DC72CDD257}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator
57 | {A99C9838-F680-4388-838F-60DC72CDD257}.Release|Any CPU.Build.0 = Release|iPhoneSimulator
58 | {A99C9838-F680-4388-838F-60DC72CDD257}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
59 | {A99C9838-F680-4388-838F-60DC72CDD257}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
60 | {A99C9838-F680-4388-838F-60DC72CDD257}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
61 | {A99C9838-F680-4388-838F-60DC72CDD257}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
62 | {A99C9838-F680-4388-838F-60DC72CDD257}.Debug|iPhone.ActiveCfg = Debug|iPhone
63 | {A99C9838-F680-4388-838F-60DC72CDD257}.Debug|iPhone.Build.0 = Debug|iPhone
64 | {A99C9838-F680-4388-838F-60DC72CDD257}.Release|iPhone.ActiveCfg = Release|iPhone
65 | {A99C9838-F680-4388-838F-60DC72CDD257}.Release|iPhone.Build.0 = Release|iPhone
66 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
67 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
68 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
69 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Release|Any CPU.Build.0 = Release|Any CPU
70 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
71 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
72 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
73 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
74 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Debug|iPhone.ActiveCfg = Debug|Any CPU
75 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Debug|iPhone.Build.0 = Debug|Any CPU
76 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Release|iPhone.ActiveCfg = Release|Any CPU
77 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}.Release|iPhone.Build.0 = Release|Any CPU
78 | EndGlobalSection
79 | EndGlobal
80 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Assets/AboutAssets.txt:
--------------------------------------------------------------------------------
1 | Any raw assets you want to be deployed with your application can be placed in
2 | this directory (and child directories) and given a Build Action of "AndroidAsset".
3 |
4 | These files will be deployed with your package and will be accessible using Android's
5 | AssetManager, like this:
6 |
7 | public class ReadAsset : Activity
8 | {
9 | protected override void OnCreate (Bundle bundle)
10 | {
11 | base.OnCreate (bundle);
12 |
13 | InputStream input = Assets.Open ("my_asset.txt");
14 | }
15 | }
16 |
17 | Additionally, some Android functions will automatically load asset files:
18 |
19 | Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf");
20 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/MainActivity.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using Android.App;
4 | using Android.Content.PM;
5 | using Android.Runtime;
6 | using Android.Views;
7 | using Android.Widget;
8 | using Android.OS;
9 |
10 | namespace SupabaseExampleXA.Droid
11 | {
12 | [Activity(Label = "SupabaseExampleXA", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
13 | public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
14 | {
15 | protected override void OnCreate(Bundle savedInstanceState)
16 | {
17 | TabLayoutResource = Resource.Layout.Tabbar;
18 | ToolbarResource = Resource.Layout.Toolbar;
19 |
20 | base.OnCreate(savedInstanceState);
21 |
22 | Xamarin.Essentials.Platform.Init(this, savedInstanceState);
23 | global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
24 | LoadApplication(new App());
25 | }
26 | public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
27 | {
28 | Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
29 |
30 | base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Properties/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 | using Android.App;
5 |
6 | // General Information about an assembly is controlled through the following
7 | // set of attributes. Change these attribute values to modify the information
8 | // associated with an assembly.
9 | [assembly: AssemblyTitle("SupabaseExampleXA.Android")]
10 | [assembly: AssemblyDescription("")]
11 | [assembly: AssemblyConfiguration("")]
12 | [assembly: AssemblyCompany("")]
13 | [assembly: AssemblyProduct("SupabaseExampleXA.Android")]
14 | [assembly: AssemblyCopyright("Copyright © 2014")]
15 | [assembly: AssemblyTrademark("")]
16 | [assembly: AssemblyCulture("")]
17 | [assembly: ComVisible(false)]
18 |
19 | // Version information for an assembly consists of the following four values:
20 | //
21 | // Major Version
22 | // Minor Version
23 | // Build Number
24 | // Revision
25 | [assembly: AssemblyVersion("1.0.0.0")]
26 | [assembly: AssemblyFileVersion("1.0.0.0")]
27 |
28 | // Add some common permissions, these can be removed if not needed
29 | [assembly: UsesPermission(Android.Manifest.Permission.Internet)]
30 | [assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]
31 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/AboutResources.txt:
--------------------------------------------------------------------------------
1 | Images, layout descriptions, binary blobs and string dictionaries can be included
2 | in your application as resource files. Various Android APIs are designed to
3 | operate on the resource IDs instead of dealing with images, strings or binary blobs
4 | directly.
5 |
6 | For example, a sample Android app that contains a user interface layout (main.xml),
7 | an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
8 | would keep its resources in the "Resources" directory of the application:
9 |
10 | Resources/
11 | drawable-hdpi/
12 | icon.png
13 |
14 | drawable-ldpi/
15 | icon.png
16 |
17 | drawable-mdpi/
18 | icon.png
19 |
20 | layout/
21 | main.xml
22 |
23 | values/
24 | strings.xml
25 |
26 | In order to get the build system to recognize Android resources, set the build action to
27 | "AndroidResource". The native Android APIs do not operate directly with filenames, but
28 | instead operate on resource IDs. When you compile an Android application that uses resources,
29 | the build system will package the resources for distribution and generate a class called
30 | "Resource" that contains the tokens for each one of the resources included. For example,
31 | for the above Resources layout, this is what the Resource class would expose:
32 |
33 | public class Resource {
34 | public class drawable {
35 | public const int icon = 0x123;
36 | }
37 |
38 | public class layout {
39 | public const int main = 0x456;
40 | }
41 |
42 | public class strings {
43 | public const int first_string = 0xabc;
44 | public const int second_string = 0xbcd;
45 | }
46 | }
47 |
48 | You would then use R.drawable.icon to reference the drawable/icon.png file, or Resource.layout.main
49 | to reference the layout/main.xml file, or Resource.strings.first_string to reference the first
50 | string in the dictionary file values/strings.xml.
51 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/layout/Tabbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/layout/Toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-anydpi-v26/icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-anydpi-v26/icon_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-hdpi/icon.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-hdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-hdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-mdpi/icon.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-mdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-mdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xhdpi/icon.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xhdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xhdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xxhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xxhdpi/icon.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xxhdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xxhdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xxxhdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xxxhdpi/icon.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xxxhdpi/launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/mipmap-xxxhdpi/launcher_foreground.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 | #3F51B5
5 | #303F9F
6 | #FF4081
7 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/Resources/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.Android/SupabaseExampleXA.Android.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | {4C26C775-92CC-4EE4-A57B-51093C5DF5BF}
7 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
8 | {c9e5eea5-ca05-42a1-839b-61506e0a37df}
9 | Library
10 | SupabaseExampleXA.Droid
11 | SupabaseExampleXA.Android
12 | True
13 | True
14 | Resources\Resource.designer.cs
15 | Resource
16 | Properties\AndroidManifest.xml
17 | Resources
18 | Assets
19 | v10.0
20 | true
21 | true
22 | Xamarin.Android.Net.AndroidClientHandler
23 |
24 |
25 | 0.1.0-prerelease
26 |
27 |
28 | true
29 | portable
30 | false
31 | bin\Debug
32 | DEBUG;
33 | prompt
34 | 4
35 | None
36 |
37 |
38 | true
39 | portable
40 | true
41 | bin\Release
42 | prompt
43 | 4
44 | true
45 | false
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | {0F986F22-CF68-40E8-B193-45880B7C4D49}
94 | SupabaseExampleXA
95 |
96 |
97 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}
98 | Supabase
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/AppDelegate.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | using Foundation;
6 | using UIKit;
7 |
8 | namespace SupabaseExampleXA.iOS
9 | {
10 | // The UIApplicationDelegate for the application. This class is responsible for launching the
11 | // User Interface of the application, as well as listening (and optionally responding) to
12 | // application events from iOS.
13 | [Register("AppDelegate")]
14 | public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
15 | {
16 | //
17 | // This method is invoked when the application has loaded and is ready to run. In this
18 | // method you should instantiate the window, load the UI into it and then make the window
19 | // visible.
20 | //
21 | // You have 17 seconds to return from this method, or iOS will terminate your application.
22 | //
23 | public override bool FinishedLaunching(UIApplication app, NSDictionary options)
24 | {
25 | global::Xamarin.Forms.Forms.Init();
26 | LoadApplication(new App());
27 |
28 | return base.FinishedLaunching(app, options);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "scale": "2x",
5 | "size": "20x20",
6 | "idiom": "iphone",
7 | "filename": "Icon40.png"
8 | },
9 | {
10 | "scale": "3x",
11 | "size": "20x20",
12 | "idiom": "iphone",
13 | "filename": "Icon60.png"
14 | },
15 | {
16 | "scale": "2x",
17 | "size": "29x29",
18 | "idiom": "iphone",
19 | "filename": "Icon58.png"
20 | },
21 | {
22 | "scale": "3x",
23 | "size": "29x29",
24 | "idiom": "iphone",
25 | "filename": "Icon87.png"
26 | },
27 | {
28 | "scale": "2x",
29 | "size": "40x40",
30 | "idiom": "iphone",
31 | "filename": "Icon80.png"
32 | },
33 | {
34 | "scale": "3x",
35 | "size": "40x40",
36 | "idiom": "iphone",
37 | "filename": "Icon120.png"
38 | },
39 | {
40 | "scale": "2x",
41 | "size": "60x60",
42 | "idiom": "iphone",
43 | "filename": "Icon120.png"
44 | },
45 | {
46 | "scale": "3x",
47 | "size": "60x60",
48 | "idiom": "iphone",
49 | "filename": "Icon180.png"
50 | },
51 | {
52 | "scale": "1x",
53 | "size": "20x20",
54 | "idiom": "ipad",
55 | "filename": "Icon20.png"
56 | },
57 | {
58 | "scale": "2x",
59 | "size": "20x20",
60 | "idiom": "ipad",
61 | "filename": "Icon40.png"
62 | },
63 | {
64 | "scale": "1x",
65 | "size": "29x29",
66 | "idiom": "ipad",
67 | "filename": "Icon29.png"
68 | },
69 | {
70 | "scale": "2x",
71 | "size": "29x29",
72 | "idiom": "ipad",
73 | "filename": "Icon58.png"
74 | },
75 | {
76 | "scale": "1x",
77 | "size": "40x40",
78 | "idiom": "ipad",
79 | "filename": "Icon40.png"
80 | },
81 | {
82 | "scale": "2x",
83 | "size": "40x40",
84 | "idiom": "ipad",
85 | "filename": "Icon80.png"
86 | },
87 | {
88 | "scale": "1x",
89 | "size": "76x76",
90 | "idiom": "ipad",
91 | "filename": "Icon76.png"
92 | },
93 | {
94 | "scale": "2x",
95 | "size": "76x76",
96 | "idiom": "ipad",
97 | "filename": "Icon152.png"
98 | },
99 | {
100 | "scale": "2x",
101 | "size": "83.5x83.5",
102 | "idiom": "ipad",
103 | "filename": "Icon167.png"
104 | },
105 | {
106 | "scale": "1x",
107 | "size": "1024x1024",
108 | "idiom": "ios-marketing",
109 | "filename": "Icon1024.png"
110 | }
111 | ],
112 | "properties": {},
113 | "info": {
114 | "version": 1,
115 | "author": "xcode"
116 | }
117 | }
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon1024.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon120.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon152.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon167.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon180.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon20.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon29.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon40.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon58.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon60.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon76.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon80.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Assets.xcassets/AppIcon.appiconset/Icon87.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Entitlements.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIDeviceFamily
6 |
7 | 1
8 | 2
9 |
10 | UISupportedInterfaceOrientations
11 |
12 | UIInterfaceOrientationPortrait
13 | UIInterfaceOrientationLandscapeLeft
14 | UIInterfaceOrientationLandscapeRight
15 |
16 | UISupportedInterfaceOrientations~ipad
17 |
18 | UIInterfaceOrientationPortrait
19 | UIInterfaceOrientationPortraitUpsideDown
20 | UIInterfaceOrientationLandscapeLeft
21 | UIInterfaceOrientationLandscapeRight
22 |
23 | MinimumOSVersion
24 | 8.0
25 | CFBundleDisplayName
26 | SupabaseExampleXA
27 | CFBundleIdentifier
28 | io.supabase.SupabaseExample
29 | CFBundleVersion
30 | 1.0
31 | UILaunchStoryboardName
32 | LaunchScreen
33 | CFBundleName
34 | SupabaseExampleXA
35 | XSAppIconAssets
36 | Assets.xcassets/AppIcon.appiconset
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Main.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | using Foundation;
6 | using UIKit;
7 |
8 | namespace SupabaseExampleXA.iOS
9 | {
10 | public class Application
11 | {
12 | // This is the main entry point of the application.
13 | static void Main(string[] args)
14 | {
15 | // if you want to use a different Application Delegate class from "AppDelegate"
16 | // you can specify it here.
17 | UIApplication.Main(args, null, "AppDelegate");
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("SupabaseExampleXA.iOS")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("SupabaseExampleXA.iOS")]
13 | [assembly: AssemblyCopyright("Copyright © 2014")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("72bdc44f-c588-44f3-b6df-9aace7daafdd")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default-568h@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default-568h@2x.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default-Portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default-Portrait.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default-Portrait@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default-Portrait@2x.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/Default@2x.png
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/Resources/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA.iOS/SupabaseExampleXA.iOS.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | iPhoneSimulator
6 | 8.0.30703
7 | 2.0
8 | {A99C9838-F680-4388-838F-60DC72CDD257}
9 | {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
10 | {6143fdea-f3c2-4a09-aafa-6e230626515e}
11 | Exe
12 | SupabaseExampleXA.iOS
13 | Resources
14 | SupabaseExampleXA.iOS
15 | true
16 | NSUrlSessionHandler
17 | automatic
18 | 0.1.0-prerelease
19 |
20 |
21 | true
22 | full
23 | false
24 | bin\iPhoneSimulator\Debug
25 | DEBUG
26 | prompt
27 | 4
28 | x86_64
29 | None
30 | true
31 | HttpClientHandler
32 | iPhone Developer
33 |
34 |
35 | none
36 | true
37 | bin\iPhoneSimulator\Release
38 | prompt
39 | 4
40 | None
41 | x86_64
42 |
43 |
44 | true
45 | full
46 | false
47 | bin\iPhone\Debug
48 | DEBUG
49 | prompt
50 | 4
51 | ARM64
52 | iPhone Developer
53 | true
54 | Entitlements.plist
55 | None
56 | -all
57 |
58 |
59 | none
60 | true
61 | bin\iPhone\Release
62 | prompt
63 | 4
64 | ARM64
65 | iPhone Developer
66 | Entitlements.plist
67 | SdkOnly
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | false
80 |
81 |
82 | false
83 |
84 |
85 | false
86 |
87 |
88 | false
89 |
90 |
91 | false
92 |
93 |
94 | false
95 |
96 |
97 | false
98 |
99 |
100 | false
101 |
102 |
103 | false
104 |
105 |
106 | false
107 |
108 |
109 | false
110 |
111 |
112 | false
113 |
114 |
115 | false
116 |
117 |
118 | false
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 | {0F986F22-CF68-40E8-B193-45880B7C4D49}
136 | SupabaseExampleXA
137 |
138 |
139 | {313E0FFC-E478-4365-99FA-70B8BBC92A5D}
140 | Supabase
141 |
142 |
143 |
144 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA/App.xaml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Threading.Tasks;
5 | using Newtonsoft.Json;
6 | using Supabase;
7 | using Supabase.Gotrue;
8 | using Xamarin.Essentials;
9 | using Xamarin.Forms;
10 | using Xamarin.Forms.Xaml;
11 | using Client = Supabase.Client;
12 |
13 | namespace SupabaseExampleXA
14 | {
15 | public partial class App : Application
16 | {
17 | private string supabaseCacheFilename = ".supabase.cache";
18 |
19 | public App()
20 | {
21 | InitializeComponent();
22 |
23 | MainPage = new LoadingPage();
24 | }
25 |
26 | protected override void OnStart()
27 | {
28 | InitSupabase();
29 | }
30 |
31 | protected override void OnSleep()
32 | {
33 | }
34 |
35 | protected override void OnResume()
36 | {
37 | }
38 |
39 | async void InitSupabase()
40 | {
41 | var options = new SupabaseOptions
42 | {
43 | SessionPersistor = SessionPersistor,
44 | SessionRetriever = SessionRetriever,
45 | SessionDestroyer = SessionDestroyer
46 | };
47 |
48 | await Client.Initialize(Environment.GetEnvironmentVariable("supabaseUrl"), Environment.GetEnvironmentVariable("supabaseKey"), options);
49 |
50 | if (Client.Instance.Auth.CurrentSession == null)
51 | {
52 | await Client.Instance.Auth.SignIn(Environment.GetEnvironmentVariable("email"), Environment.GetEnvironmentVariable("password"));
53 | }
54 |
55 | await Client.Instance.Realtime.Connect();
56 |
57 | MainPage = new NavigationPage(new ChannelListPage());
58 | }
59 |
60 | internal Task SessionPersistor(Session session)
61 | {
62 | try
63 | {
64 | var cacheDir = FileSystem.CacheDirectory;
65 | var path = Path.Join(cacheDir, supabaseCacheFilename);
66 | var str = JsonConvert.SerializeObject(session);
67 |
68 | using (StreamWriter file = new StreamWriter(path))
69 | {
70 | file.Write(str);
71 | file.Dispose();
72 | return Task.FromResult(true);
73 |
74 | };
75 | }
76 | catch (Exception err)
77 | {
78 | Debug.WriteLine("Unable to write cache file.");
79 | throw err;
80 | }
81 | }
82 |
83 | internal Task SessionRetriever()
84 | {
85 | var tsc = new TaskCompletionSource();
86 | try
87 | {
88 | var cacheDir = FileSystem.CacheDirectory;
89 | var path = Path.Join(cacheDir, supabaseCacheFilename);
90 |
91 | if (File.Exists(path))
92 | {
93 | using (StreamReader file = new StreamReader(path))
94 | {
95 | var str = file.ReadToEnd();
96 | if (!String.IsNullOrEmpty(str))
97 | tsc.SetResult(JsonConvert.DeserializeObject(str));
98 | else
99 | tsc.SetResult(null);
100 | file.Dispose();
101 | };
102 | }
103 | else
104 | {
105 | tsc.SetResult(null);
106 | }
107 | }
108 | catch
109 | {
110 | Debug.WriteLine("Unable to read cache file.");
111 | tsc.SetResult(null);
112 | }
113 | return tsc.Task;
114 |
115 | }
116 |
117 | internal Task SessionDestroyer()
118 | {
119 | try
120 | {
121 | var cacheDir = FileSystem.CacheDirectory;
122 | var path = Path.Join(cacheDir, supabaseCacheFilename);
123 | if (File.Exists(path))
124 | File.Delete(path);
125 | return Task.FromResult(true);
126 | }
127 | catch (Exception err)
128 | {
129 | Debug.WriteLine("Unable to delete cache file.");
130 | return Task.FromResult(false);
131 | }
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using Xamarin.Forms.Xaml;
2 |
3 | [assembly: XamlCompilation(XamlCompilationOptions.Compile)]
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA/ChannelListPage.xaml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA/ChannelListPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using SupabaseExampleXA.Models;
8 | using Xamarin.Forms;
9 |
10 | namespace SupabaseExampleXA
11 | {
12 | public partial class ChannelListPage : ContentPage
13 | {
14 | public ChannelListPage()
15 | {
16 | InitializeComponent();
17 |
18 | ChannelList.ItemSelected += ChannelList_ItemSelected;
19 | }
20 |
21 | private void ChannelList_ItemSelected(object sender, SelectedItemChangedEventArgs e)
22 | {
23 | if (ChannelList.SelectedItem == null) return;
24 |
25 | Navigation.PushAsync(new MessageListPage(ChannelList.SelectedItem as Channel));
26 | ChannelList.SelectedItem = null;
27 | }
28 |
29 | protected override void OnAppearing()
30 | {
31 | base.OnAppearing();
32 | Device.BeginInvokeOnMainThread(async () => await Refresh());
33 |
34 | Supabase.Client.Instance.Auth.StateChanged += async (object sender, Supabase.Gotrue.ClientStateChanged e) =>
35 | {
36 | if (e.State == Supabase.Gotrue.Client.AuthState.SignedIn)
37 | {
38 | await Refresh();
39 | }
40 | };
41 | }
42 |
43 | private async Task Refresh()
44 | {
45 | var channels = await Supabase.Client.Instance.From().Get();
46 | ChannelList.ItemsSource = channels?.Models;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA/LoadingPage.xaml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA/LoadingPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | using Xamarin.Forms;
5 |
6 | namespace SupabaseExampleXA
7 | {
8 | public partial class LoadingPage : ContentPage
9 | {
10 | public LoadingPage()
11 | {
12 | InitializeComponent();
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA/MessageListPage.xaml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA/MessageListPage.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Threading.Tasks;
5 | using Newtonsoft.Json;
6 | using Newtonsoft.Json.Linq;
7 | using Supabase.Realtime;
8 | using SupabaseExampleXA.Models;
9 | using Xamarin.Forms;
10 | using static Supabase.Client;
11 |
12 | namespace SupabaseExampleXA
13 | {
14 | public partial class MessageListPage : ContentPage
15 | {
16 | Models.Channel channel;
17 | Supabase.Realtime.Channel subscription;
18 | ObservableCollection messages { get; set; } = new ObservableCollection();
19 |
20 | public MessageListPage(Models.Channel channel)
21 | {
22 | InitializeComponent();
23 |
24 | this.channel = channel;
25 | Title = channel.Slug;
26 |
27 | MessageList.ItemsSource = messages;
28 | MessageEditor.Completed += MessageEditor_Completed;
29 |
30 | Init();
31 | }
32 |
33 | public async void Init()
34 | {
35 | var query = await Instance.From().Filter("channel_id", Postgrest.Constants.Operator.Equals, channel.Id).Get();
36 |
37 | foreach (var model in query.Models)
38 | messages.Add(model);
39 |
40 | subscription = await Instance.From().On(ChannelEventType.All, OnSubscriptionEvent);
41 | }
42 |
43 | private async void MessageEditor_Completed(object sender, EventArgs e)
44 | {
45 | await Instance.From().Insert(new Message
46 | {
47 | Text = MessageEditor.Text,
48 | UserId = Instance.Auth.CurrentUser.Id,
49 | ChannelId = channel.Id
50 | });
51 | MessageEditor.Text = null;
52 | }
53 |
54 | protected override void OnDisappearing()
55 | {
56 | base.OnDisappearing();
57 |
58 | if (subscription != null)
59 | subscription.Unsubscribe();
60 | }
61 |
62 |
63 |
64 | private void OnSubscriptionEvent(object sender, SocketResponseEventArgs args)
65 | {
66 | switch (args.Response.Event)
67 | {
68 | case Constants.EventType.Insert:
69 | var str = JsonConvert.SerializeObject(args.Response.Payload.Record);
70 | var message = args.Response.Model();
71 | messages.Add(message);
72 | break;
73 | }
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA/Models/Channel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Postgrest.Attributes;
3 | using Postgrest.Models;
4 |
5 | namespace SupabaseExampleXA.Models
6 | {
7 | [Table("channels")]
8 | public class Channel : BaseModel
9 | {
10 | [PrimaryKey("id", false)] // Key is Autogenerated
11 | public int Id { get; set; }
12 |
13 | [Column("inserted_at")]
14 | public DateTime InsertedAt { get; set; }
15 |
16 | [Column("slug")]
17 | public string Slug { get; set; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA/Models/Message.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Postgrest.Attributes;
3 | using Postgrest.Models;
4 |
5 | namespace SupabaseExampleXA.Models
6 | {
7 | [Table("messages")]
8 | public class Message : BaseModel
9 | {
10 | [PrimaryKey("id", false)]
11 | public int Id { get; set; }
12 |
13 | [Column("inserted_at")]
14 | public DateTime InsertedAt { get; set; } = new DateTime();
15 |
16 | [Column("message")]
17 | public string Text { get; set; }
18 |
19 | [Column("user_id")]
20 | public string UserId { get; set; }
21 |
22 | [Column("channel_id")]
23 | public int ChannelId { get; set; }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA/Models/User.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Postgrest.Attributes;
3 | using Postgrest.Models;
4 |
5 | namespace SupabaseExampleXA.Models
6 | {
7 | [Table("users")]
8 | public class User : BaseModel
9 | {
10 | [PrimaryKey("id")]
11 | public string Id { get; set; }
12 |
13 | [Column("username")]
14 | public string Username { get; set; }
15 |
16 | [Column("status")]
17 | public string Status { get; set; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Examples/Xamarin.Forms/SupabaseExampleXA/SupabaseExampleXA.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 | true
6 | 0.1.0-prerelease
7 |
8 |
9 |
10 | portable
11 | true
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Joseph Schultz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Documentation can be found [below](#getting-started), on
12 | the [Supabase Developer Documentation](https://supabase.com/docs/reference/csharp/introduction) and additionally in
13 | the [Generated API Docs](https://supabase-community.github.io/supabase-csharp/api/Supabase.Client.html).
14 |
15 | [**CHANGELOG is available in the repository root.
16 | **](https://github.com/supabase-community/supabase-csharp/blob/master/CHANGELOG.md)
17 |
18 | ## [NOTICE FOR v1.0.0]
19 |
20 | - The `supabase-csharp` Nuget package has been renamed to `Supabase` and a depreciation notice set to encourage
21 | adoption.
22 | - Almost all APIs stay the same when migrating from v0.16.x _except_ the change in namespace from `Postgrest`
23 | to `Supabase.Postgrest`. Some minor refactoring will be required in the codebase.
24 | - The assembly name has been changed from `supabase` to `Supabase`.
25 |
26 | ## Features
27 |
28 | - [x] Integration with [Supabase.Realtime](https://github.com/supabase-community/realtime-csharp)
29 | - Realtime listeners for database changes
30 | - [x] Integration with [Postgrest](https://github.com/supabase-community/postgrest-csharp)
31 | - Access your database using a REST API generated from your schema & database functions
32 | - [x] Integration with [Gotrue](https://github.com/supabase-community/gotrue-csharp)
33 | - User authentication, including OAuth, email/password, and native sign-in
34 | - [x] Integration with [Supabase Storage](https://github.com/supabase-community/storage-csharp)
35 | - Store files in S3 with additional managed metadata
36 | - [x] Integration with [Supabase Edge Functions](https://github.com/supabase-community/functions-csharp)
37 | - Run serverless functions on the edge
38 | - [x] [Nuget Release](https://www.nuget.org/packages/supabase-csharp)
39 |
40 | ## Quickstart
41 |
42 | 1. To get started, create a new project in the [Supabase Admin Panel](https://app.supabase.io).
43 | 2. Grab your Supabase URL and Supabase Public Key from the Admin Panel (Settings -> API Keys).
44 | 3. Initialize the client!
45 |
46 | _Reminder: `supabase-csharp` has some APIs that require the `service_key` rather than the `public_key` (for instance:
47 | the administration of users, bypassing database roles, etc.). If you are using
48 | the `service_key` **be sure it is not exposed client side.** Additionally, if you need to use both a service account and
49 | a public/user account, please do so using a separate client instance for each._
50 |
51 | ## Documentation
52 |
53 | - [Getting Started](https://github.com/supabase-community/supabase-csharp/wiki#getting-started)
54 | - [Unity](https://github.com/supabase-community/supabase-csharp/wiki/Unity)
55 | - [Desktop/Mobile Clients (e.g. Xamarin, MAUI, etc.)](https://github.com/supabase-community/supabase-csharp/wiki/Desktop-Clients)
56 | - [Server-Side Applications](https://github.com/supabase-community/supabase-csharp/wiki/Server-Side-Applications)
57 | - [Release Notes/Breaking Changes](https://github.com/supabase-community/supabase-csharp/wiki/Release-Notes)
58 | - [Using the Client](https://github.com/supabase-community/supabase-csharp/wiki#using-the-client)
59 | - [Examples](https://github.com/supabase-community/supabase-csharp/wiki/Examples)
60 |
61 | ### Specific Features
62 |
63 | - [Offline Support](https://github.com/supabase-community/supabase-csharp/wiki/Authorization-with-Gotrue#offline-support)
64 | - [Refresh Token Thread](https://github.com/supabase-community/supabase-csharp/wiki/Authorization-with-Gotrue#updated-refresh-token-handling)
65 | - [Native Sign in with Apple]([Documentation/NativeSignInWithApple.md](https://github.com/supabase-community/supabase-csharp/wiki/Authorization-with-Gotrue#native-sign-in-with-apple))
66 |
67 | ### Troubleshooting
68 |
69 | - [Troubleshooting](https://github.com/supabase-community/supabase-csharp/wiki/Troubleshooting)
70 | - [Discussion Forum](https://github.com/supabase-community/supabase-csharp/discussions)
71 |
72 | ## Package made possible through the efforts of:
73 |
74 |
75 |
76 |
77 |
78 | Join the ranks! See a problem? Help fix it!
79 |
80 | ## Contributing
81 |
82 | We are more than happy to have contributions! Please submit a PR.
83 |
--------------------------------------------------------------------------------
/Supabase.sln:
--------------------------------------------------------------------------------
1 | Microsoft Visual Studio Solution File, Format Version 12.00
2 | # Visual Studio Version 17
3 | VisualStudioVersion = 17.2.32519.379
4 | MinimumVisualStudioVersion = 10.0.40219.1
5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F94175FC-DE2B-4DBC-9C79-2A97B8489412}"
6 | ProjectSection(SolutionItems) = preProject
7 | .env = .env
8 | .env.sample = .env.sample
9 | .gitignore = .gitignore
10 | CHANGELOG.md = CHANGELOG.md
11 | docker-compose.yml = docker-compose.yml
12 | README.md = README.md
13 | EndProjectSection
14 | EndProject
15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Supabase", "Supabase\Supabase.csproj", "{FAE80407-C121-47A3-9304-D39FA828E9F1}"
16 | EndProject
17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SupabaseTests", "SupabaseTests\SupabaseTests.csproj", "{28EE4F80-74AA-46F6-B15E-27C30310401A}"
18 | EndProject
19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{43FFFE0C-91D2-43AB-913F-B3824702AE49}"
20 | EndProject
21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{5B805377-7615-441C-86B1-4BE203B0289B}"
22 | ProjectSection(SolutionItems) = preProject
23 | .github\workflows\build-documentation.yaml = .github\workflows\build-documentation.yaml
24 | .github\workflows\release.yml = .github\workflows\release.yml
25 | .github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml
26 | EndProjectSection
27 | EndProject
28 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{E407C761-AA9C-423C-AD1C-7EE687D3CAB9}"
29 | EndProject
30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SupabaseExample", "Examples\SupabaseExample\SupabaseExample.csproj", "{F73BCB1B-1EEE-41FA-B7A7-C655F391A26D}"
31 | EndProject
32 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{F7E6F0F9-19B4-403B-A5C8-36D43CDC6B5F}"
33 | EndProject
34 | Global
35 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
36 | Debug|Any CPU = Debug|Any CPU
37 | Release|Any CPU = Release|Any CPU
38 | EndGlobalSection
39 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
40 | {FAE80407-C121-47A3-9304-D39FA828E9F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {FAE80407-C121-47A3-9304-D39FA828E9F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {FAE80407-C121-47A3-9304-D39FA828E9F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {FAE80407-C121-47A3-9304-D39FA828E9F1}.Release|Any CPU.Build.0 = Release|Any CPU
44 | {28EE4F80-74AA-46F6-B15E-27C30310401A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45 | {28EE4F80-74AA-46F6-B15E-27C30310401A}.Debug|Any CPU.Build.0 = Debug|Any CPU
46 | {28EE4F80-74AA-46F6-B15E-27C30310401A}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 | {28EE4F80-74AA-46F6-B15E-27C30310401A}.Release|Any CPU.Build.0 = Release|Any CPU
48 | {F73BCB1B-1EEE-41FA-B7A7-C655F391A26D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
49 | {F73BCB1B-1EEE-41FA-B7A7-C655F391A26D}.Debug|Any CPU.Build.0 = Debug|Any CPU
50 | {F73BCB1B-1EEE-41FA-B7A7-C655F391A26D}.Release|Any CPU.ActiveCfg = Release|Any CPU
51 | {F73BCB1B-1EEE-41FA-B7A7-C655F391A26D}.Release|Any CPU.Build.0 = Release|Any CPU
52 | EndGlobalSection
53 | GlobalSection(SolutionProperties) = preSolution
54 | HideSolutionNode = FALSE
55 | EndGlobalSection
56 | GlobalSection(NestedProjects) = preSolution
57 | {43FFFE0C-91D2-43AB-913F-B3824702AE49} = {F94175FC-DE2B-4DBC-9C79-2A97B8489412}
58 | {5B805377-7615-441C-86B1-4BE203B0289B} = {43FFFE0C-91D2-43AB-913F-B3824702AE49}
59 | {F73BCB1B-1EEE-41FA-B7A7-C655F391A26D} = {F7E6F0F9-19B4-403B-A5C8-36D43CDC6B5F}
60 | EndGlobalSection
61 | GlobalSection(ExtensibilityGlobals) = postSolution
62 | SolutionGuid = {832DE89D-7252-4B03-9301-BB8D36B40992}
63 | EndGlobalSection
64 | GlobalSection(MonoDevelopProperties) = preSolution
65 | version = 0.3.4
66 | EndGlobalSection
67 | EndGlobal
68 |
--------------------------------------------------------------------------------
/Supabase/DefaultSupabaseSessionHandler.cs:
--------------------------------------------------------------------------------
1 | using Supabase.Gotrue;
2 | using Supabase.Gotrue.Interfaces;
3 |
4 | namespace Supabase
5 | {
6 | ///
7 | /// Represents the default session handler for Gotrue - it does nothing by default.
8 | ///
9 | public class DefaultSupabaseSessionHandler : IGotrueSessionPersistence
10 | {
11 | ///
12 | /// Default Session Save (does nothing by default)
13 | ///
14 | ///
15 | public void SaveSession(Session session)
16 | {
17 | }
18 |
19 | ///
20 | /// Default Session Destroyer (does nothing by default)
21 | ///
22 | public void DestroySession()
23 | {
24 | }
25 |
26 | ///
27 | /// Default Session Loader (does nothing by default)
28 | ///
29 | ///
30 | public Session? LoadSession()
31 | {
32 | return null;
33 | }
34 | }
35 | }
--------------------------------------------------------------------------------
/Supabase/Extensions/DictionaryExtension.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace Supabase.Extensions
5 | {
6 | internal static class DictionaryExtensions
7 | {
8 | // Works in C#3/VS2008:
9 | // Returns a new dictionary of this ... others merged leftward.
10 | // Keeps the type of 'this', which must be default-instantiable.
11 | // Example:
12 | // result = map.MergeLeft(other1, other2, ...)
13 | // From: https://stackoverflow.com/a/2679857/3629438
14 | public static T MergeLeft(this T me, params IDictionary[] others)
15 | where T : IDictionary, new()
16 | {
17 | T newMap = new T();
18 | foreach (IDictionary src in (new List> { me }).Concat(others))
19 | {
20 | foreach (KeyValuePair p in src)
21 | {
22 | newMap[p.Key] = p.Value;
23 | }
24 | }
25 | return newMap;
26 | }
27 |
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Supabase/Interfaces/ISupabaseClient.cs:
--------------------------------------------------------------------------------
1 | using Supabase.Postgrest.Interfaces;
2 | using Supabase.Postgrest.Models;
3 | using Supabase.Postgrest.Responses;
4 | using Supabase.Functions.Interfaces;
5 | using Supabase.Gotrue;
6 | using Supabase.Gotrue.Interfaces;
7 | using Supabase.Realtime.Interfaces;
8 | using Supabase.Storage;
9 | using Supabase.Storage.Interfaces;
10 | using System.Threading.Tasks;
11 |
12 | namespace Supabase.Interfaces
13 | {
14 | ///
15 | /// Contract for what a SupabaseClient should implement
16 | ///
17 | /// Model representing User
18 | /// Model representing Session
19 | /// Class that conforms to
20 | /// Class that conforms to
21 | /// Model representing
22 | /// Model representing
23 | public interface ISupabaseClient
24 | where TUser : User
25 | where TSession : Session
26 | where TSocket : IRealtimeSocket
27 | where TChannel : IRealtimeChannel
28 | where TBucket : Bucket
29 | where TFileObject : FileObject
30 | {
31 | ///
32 | /// The Gotrue Auth Instance
33 | ///
34 | IGotrueClient Auth { get; set; }
35 |
36 | ///
37 | /// Creates a Gotrue Admin Auth Client
38 | ///
39 | ///
40 | ///
41 | IGotrueAdminClient AdminAuth(string serviceKey);
42 |
43 | ///
44 | /// The Supabase Functions Client
45 | ///
46 | IFunctionsClient Functions { get; set; }
47 |
48 | ///
49 | /// The Postgrest Client
50 | ///
51 | IPostgrestClient Postgrest { get; set; }
52 |
53 | ///
54 | /// The Realtime Client
55 | ///
56 | IRealtimeClient Realtime { get; set; }
57 |
58 | ///
59 | /// The Storage Client
60 | ///
61 | IStorageClient Storage { get; set; }
62 |
63 | ///
64 | /// Used for interacting with a Postgrest Table + Model. Provides helpers
65 | /// to be able to add realtime listeners and queries.
66 | ///
67 | ///
68 | ///
69 | ISupabaseTable From() where TModel : BaseModel, new();
70 |
71 |
72 | ///
73 | /// Initializes a supabase client according to the provided .
74 | /// If option is enabled:
75 | /// - Will connect to realtime instance
76 | /// - Will restore session using a specified in
77 | ///
78 | ///
79 | Task> InitializeAsync();
80 |
81 | ///
82 | /// Perform a stored procedure call.
83 | ///
84 | /// The function name to call
85 | /// The parameters to pass to the function call
86 | ///
87 | Task Rpc(string procedureName, object? parameters);
88 |
89 | ///
90 | /// Perform a stored procedure call.
91 | ///
92 | /// The function name to call
93 | /// The parameters to pass to the function call
94 | /// A type used for hydrating the HTTP response content (hydration through JSON.NET)
95 | /// A hydrated model
96 | Task Rpc(string procedureName, object? parameters);
97 | }
98 | }
--------------------------------------------------------------------------------
/Supabase/Interfaces/ISupabaseFunctions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Net.Http;
3 | using System.Threading.Tasks;
4 | using Newtonsoft.Json;
5 |
6 | namespace Supabase.Interfaces
7 | {
8 | ///
9 | /// Contract representing a wrapper client.
10 | ///
11 | public interface ISupabaseFunctions
12 | {
13 | ///
14 | /// Invoke a supabase function
15 | ///
16 | ///
17 | ///
18 | /// String content from invoke
19 | Task Invoke(string functionName, Dictionary? body = null);
20 |
21 | ///
22 | /// Invoke a supabase function and deserialize data to a provided model.
23 | ///
24 | ///
25 | ///
26 | /// Model representing data that is compatible with
27 | /// The deserialized Model
28 | Task Invoke(string functionName, Dictionary? body = null) where T : class;
29 |
30 | ///
31 | /// Invoke a supabase function and return the for the developer to parse.
32 | ///
33 | ///
34 | ///
35 | /// The HTTP Content
36 | Task RawInvoke(string functionName, Dictionary? body = null);
37 | }
38 | }
--------------------------------------------------------------------------------
/Supabase/Interfaces/ISupabaseTable.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using Supabase.Realtime.Interfaces;
3 | using Supabase.Postgrest.Interfaces;
4 | using Supabase.Postgrest.Models;
5 | using static Supabase.Realtime.PostgresChanges.PostgresChangesOptions;
6 |
7 | namespace Supabase.Interfaces
8 | {
9 | ///
10 | /// Contract representing a supabase wrapped postgrest
11 | ///
12 | /// Model that inherits from that represents this Table
13 | /// Class that implements
14 | public interface ISupabaseTable : IPostgrestTable
15 | where TModel : BaseModel, new()
16 | where TChannel : IRealtimeChannel
17 | {
18 | ///
19 | /// Add a realtime listener to this table.
20 | ///
21 | ///
22 | ///
23 | ///
24 | Task On(ListenType listenType, IRealtimeChannel.PostgresChangesHandler handler);
25 | }
26 | }
--------------------------------------------------------------------------------
/Supabase/Supabase.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 9.0
5 | enable
6 | CS8600;CS8602;CS8603
7 | true
8 | Supabase
9 | Supabase
10 | Supabase
11 | Supabase
12 | Joseph Schultz <joseph@acupofjose.com>
13 | A C# implementation of the Supabase client
14 | MIT
15 | en
16 | true
17 | MIT
18 | Joseph Schultz <joseph@acupofjose.com>
19 | https://github.com/supabase-community/supabase-csharp
20 | A C# implementation of the Supabase client
21 | supabase
22 | 1.1.1
23 | 1.1.1
24 | true
25 | icon.png
26 | README.md
27 | https://github.com/supabase-community/supabase-csharp
28 | true
29 | snupkg
30 | true
31 | netstandard2.1
32 |
33 |
34 |
35 | 1.1.1
36 | $(VersionPrefix)
37 |
38 |
39 | true
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/Supabase/SupabaseModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Supabase.Postgrest.Models;
3 |
4 | namespace Supabase
5 | {
6 | ///
7 | /// Depreciated
8 | ///
9 | [Obsolete]
10 | public abstract class SupabaseModel : BaseModel
11 | {}
12 | }
13 |
--------------------------------------------------------------------------------
/Supabase/SupabaseOptions.cs:
--------------------------------------------------------------------------------
1 | using Supabase.Gotrue;
2 | using System.Collections.Generic;
3 | using Supabase.Gotrue.Interfaces;
4 |
5 | namespace Supabase
6 | {
7 | ///
8 | /// Options available for Supabase Client Configuration
9 | ///
10 | public class SupabaseOptions
11 | {
12 | ///
13 | /// Schema to be used in Postgres / Realtime
14 | ///
15 | public string Schema = "public";
16 |
17 | ///
18 | /// Should the Client automatically handle refreshing the User's Token?
19 | ///
20 | public bool AutoRefreshToken { get; set; } = true;
21 |
22 | ///
23 | /// Should the Client automatically connect to Realtime?
24 | ///
25 | public bool AutoConnectRealtime { get; set; }
26 |
27 | ///
28 | /// Functions passed to Gotrue that handle sessions.
29 | ///
30 | /// **By default these do nothing for persistence.**
31 | ///
32 | public IGotrueSessionPersistence SessionHandler { get; set; } = new DefaultSupabaseSessionHandler();
33 |
34 | ///
35 | /// Allows developer to specify options that will be passed to all child Supabase clients.
36 | ///
37 | public Dictionary Headers = new();
38 |
39 | ///
40 | /// Specifies Options passed to the StorageClient.
41 | ///
42 | public Storage.ClientOptions StorageClientOptions { get; set; } = new();
43 |
44 | ///
45 | /// The Supabase Auth Url Format
46 | ///
47 | public string AuthUrlFormat { get; set; } = "{0}/auth/v1";
48 |
49 | ///
50 | /// The Supabase Postgrest Url Format
51 | ///
52 | public string RestUrlFormat { get; set; } = "{0}/rest/v1";
53 |
54 | ///
55 | /// The Supabase Realtime Url Format
56 | ///
57 | public string RealtimeUrlFormat { get; set; } = "{0}/realtime/v1";
58 |
59 | ///
60 | /// The Supabase Storage Url Format
61 | ///
62 | public string StorageUrlFormat { get; set; } = "{0}/storage/v1";
63 |
64 | ///
65 | /// The Supabase Functions Url Format
66 | ///
67 | public string FunctionsUrlFormat { get; set; } = "{0}/functions/v1";
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Supabase/SupabaseTable.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using Supabase.Postgrest;
4 | using Supabase.Postgrest.Interfaces;
5 | using Supabase.Postgrest.Models;
6 | using Supabase.Interfaces;
7 | using Supabase.Realtime;
8 | using Supabase.Realtime.Interfaces;
9 | using static Supabase.Realtime.PostgresChanges.PostgresChangesOptions;
10 |
11 | namespace Supabase
12 | {
13 | ///
14 | /// A Supabase wrapper for a Postgrest Table.
15 | ///
16 | /// Model that implements
17 | public class SupabaseTable : Table, ISupabaseTable
18 | where TModel : BaseModel, new()
19 | {
20 | private RealtimeChannel? _channel;
21 | private readonly IPostgrestClient _postgrestClient;
22 | private readonly IRealtimeClient _realtimeClient;
23 | private readonly string _schema;
24 |
25 | ///
26 | /// A Supabase wrapper for a Postgrest table.
27 | ///
28 | ///
29 | ///
30 | ///
31 | public SupabaseTable(IPostgrestClient postgrestClient,
32 | IRealtimeClient realtimeClient, string schema = "public") : base(
33 | postgrestClient.BaseUrl, Postgrest.Client.SerializerSettings(postgrestClient.Options),
34 | postgrestClient.Options)
35 | {
36 | _postgrestClient = postgrestClient;
37 | _realtimeClient = realtimeClient;
38 | _schema = schema;
39 | GetHeaders = postgrestClient.GetHeaders;
40 | }
41 |
42 | ///
43 | public async Task On(ListenType listenType, IRealtimeChannel.PostgresChangesHandler handler)
44 | {
45 | if (_channel == null)
46 | {
47 | var parameters = new Dictionary();
48 |
49 | // In regard to: https://github.com/supabase/supabase-js/pull/270
50 | var headers = _postgrestClient?.GetHeaders?.Invoke();
51 | if (headers != null && headers.TryGetValue("Authorization", out var header))
52 | {
53 | parameters.Add("user_token", header.Split(' ')[1]);
54 | }
55 |
56 | _channel = _realtimeClient.Channel("realtime", _schema, TableName, parameters: parameters);
57 | }
58 |
59 | if (_realtimeClient.Socket == null || !_realtimeClient.Socket.IsConnected)
60 | await _realtimeClient.ConnectAsync();
61 |
62 | _channel.AddPostgresChangeHandler(listenType, handler);
63 |
64 | await _channel.Subscribe();
65 | return _channel;
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/SupabaseTests/Assets/supabase-csharp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/supabase-community/supabase-csharp/6e2eb32e5cf260c7e9c8ca9c9dcb355f6aa43203/SupabaseTests/Assets/supabase-csharp.png
--------------------------------------------------------------------------------
/SupabaseTests/Client.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading.Tasks;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using Supabase.Postgrest;
6 | using SupabaseTests.Stubs;
7 |
8 | namespace SupabaseTests
9 | {
10 | [TestClass]
11 | public class Client
12 | {
13 | private static readonly Random Random = new();
14 |
15 | private Supabase.Client _instance;
16 |
17 | private static string RandomString(int length)
18 | {
19 | const string chars = "abcdefghijklmnopqrstuvwxyz0123456789";
20 | return new string(Enumerable.Repeat(chars, length)
21 | .Select(s => s[Random.Next(s.Length)]).ToArray());
22 | }
23 |
24 | [TestInitialize]
25 | public async Task InitializeTest()
26 | {
27 | _instance = new Supabase.Client("http://localhost", null, new Supabase.SupabaseOptions
28 | {
29 | AuthUrlFormat = "{0}:9999",
30 | RealtimeUrlFormat = "ws://realtime-dev.localhost:4000/socket",
31 | RestUrlFormat = "{0}:3000",
32 | AutoConnectRealtime = false,
33 | });
34 | await _instance.InitializeAsync();
35 | }
36 |
37 | [TestMethod("Client: Initializes.")]
38 | public void ClientInitializes()
39 | {
40 | Assert.IsNotNull(_instance.Realtime);
41 | Assert.IsNotNull(_instance.Auth);
42 | }
43 |
44 | [TestMethod("SupabaseModel: Successfully Updates")]
45 | public async Task SupabaseModelUpdates()
46 | {
47 | var model = new Models.Channel { Slug = Guid.NewGuid().ToString() };
48 | var insertResult = await _instance.From().Insert(model);
49 | var newChannel = insertResult.Models.FirstOrDefault();
50 |
51 | var newSlug = $"Updated Slug @ {DateTime.Now.ToLocalTime()}";
52 | newChannel.Slug = newSlug;
53 |
54 | var updatedResult = await newChannel.Update();
55 |
56 | Assert.AreEqual(newSlug, updatedResult.Models.First().Slug);
57 | }
58 |
59 | [TestMethod("SupabaseModel: Successfully Deletes")]
60 | public async Task SupabaseModelDeletes()
61 | {
62 | var slug = Guid.NewGuid().ToString();
63 | var model = new Models.Channel { Slug = slug };
64 |
65 | var insertResult = await _instance.From().Insert(model);
66 | var newChannel = insertResult.Models.FirstOrDefault();
67 |
68 | await newChannel.Delete();
69 |
70 | var result = await _instance.From()
71 | .Filter("slug", Constants.Operator.Equals, slug).Get();
72 |
73 | Assert.AreEqual(0, result.Models.Count);
74 | }
75 |
76 | [TestMethod("Supports Dependency Injection for clients via property")]
77 | public void SupportsDIForClientsViaProperty()
78 | {
79 | _instance.Auth = new FakeAuthClient();
80 | _instance.Functions = new FakeFunctionsClient();
81 | _instance.Realtime = new FakeRealtimeClient();
82 | _instance.Postgrest = new FakeRestClient();
83 | _instance.Storage = new FakeStorageClient();
84 |
85 | Assert.ThrowsExceptionAsync(() => _instance.Auth.GetUser(""));
86 | Assert.ThrowsExceptionAsync(() => _instance.Functions.Invoke(""));
87 | Assert.ThrowsExceptionAsync(() => _instance.Realtime.ConnectAsync());
88 | Assert.ThrowsExceptionAsync(() => _instance.Postgrest.Rpc("", null));
89 | Assert.ThrowsExceptionAsync(() => _instance.Storage.ListBuckets());
90 | }
91 | }
92 | }
--------------------------------------------------------------------------------
/SupabaseTests/Models/Channel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Supabase.Postgrest.Attributes;
3 | using Supabase.Postgrest.Models;
4 |
5 | namespace SupabaseTests.Models
6 | {
7 | [Table("channels")]
8 | public class Channel : BaseModel
9 | {
10 | [PrimaryKey("id", false)] // Key is Autogenerated
11 | public int Id { get; set; }
12 |
13 | [Column("inserted_at")]
14 | public DateTime InsertedAt { get; set; }
15 |
16 | [Column("slug")]
17 | public string Slug { get; set; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/SupabaseTests/Models/Stub.cs:
--------------------------------------------------------------------------------
1 | using Supabase.Postgrest.Models;
2 |
3 | namespace SupabaseTests.Models
4 | {
5 | public class Stub : BaseModel
6 | {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/SupabaseTests/Models/User.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Supabase.Postgrest.Attributes;
3 | using Supabase.Postgrest.Models;
4 |
5 | namespace SupabaseTests.Models
6 | {
7 | [Table("users")]
8 | public class User : BaseModel
9 | {
10 | [JsonProperty("username")]
11 | public string Username { get; set; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/SupabaseTests/StatelessClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 | using Supabase.Gotrue;
6 | using SupabaseTests.Models;
7 | using static Supabase.Gotrue.Constants;
8 | using static Supabase.StatelessClient;
9 |
10 | namespace SupabaseTests
11 | {
12 | [TestClass]
13 | public class StatelessClient
14 | {
15 |
16 | private string supabaseUrl = "http://localhost";
17 | private Supabase.SupabaseOptions options = new()
18 | {
19 | AuthUrlFormat = "{0}:9999",
20 | RealtimeUrlFormat = "{0}:4000/socket",
21 | RestUrlFormat = "{0}:3000"
22 | };
23 |
24 | [TestMethod("Can access Stateless REST")]
25 | public async Task CanAccessStatelessRest()
26 | {
27 | var restOptions = GetRestOptions(null, options);
28 | var result1 = await new Supabase.Postgrest.Client(String.Format(options.RestUrlFormat, supabaseUrl), restOptions).Table().Get();
29 |
30 | var result2 = await From(supabaseUrl, null, options).Get();
31 |
32 | Assert.AreEqual(result1.Models.Count, result2.Models.Count);
33 | }
34 |
35 | [TestMethod("Can access Stateless GoTrue")]
36 | public void CanAccessStatelessGotrue()
37 | {
38 | var gotrueOptions = GetAuthOptions(supabaseUrl, null, options);
39 |
40 | var client = new Supabase.Gotrue.Client(gotrueOptions);
41 |
42 | var url = client.SignIn(Provider.Spotify);
43 |
44 | Assert.IsNotNull(url);
45 | }
46 |
47 | [TestMethod("User defined Headers will override internal headers")]
48 | public void CanOverrideInternalHeaders()
49 | {
50 | Supabase.SupabaseOptions options = new Supabase.SupabaseOptions
51 | {
52 | AuthUrlFormat = "{0}:9999",
53 | RealtimeUrlFormat = "{0}:4000/socket",
54 | RestUrlFormat = "{0}:3000",
55 | Headers = new Dictionary {
56 | { "Authorization", "Bearer 123" }
57 | }
58 | };
59 |
60 | var gotrueOptions = GetAuthOptions(supabaseUrl, "456", options);
61 |
62 | Assert.AreEqual("Bearer 123", gotrueOptions.Headers["Authorization"]);
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/SupabaseTests/Stubs/FakeFunctionsClient.cs:
--------------------------------------------------------------------------------
1 | using Supabase.Functions.Interfaces;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Net.Http;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace SupabaseTests.Stubs
9 | {
10 | internal class FakeFunctionsClient : IFunctionsClient
11 | {
12 | public Func> GetHeaders { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
13 |
14 | public Task Invoke(string url, string token = null, Supabase.Functions.Client.InvokeFunctionOptions options = null)
15 | {
16 | throw new NotImplementedException();
17 | }
18 |
19 | public Task Invoke(string url, string token = null, Supabase.Functions.Client.InvokeFunctionOptions options = null) where T : class
20 | {
21 | throw new NotImplementedException();
22 | }
23 |
24 | public Task RawInvoke(string url, string token = null, Supabase.Functions.Client.InvokeFunctionOptions options = null)
25 | {
26 | throw new NotImplementedException();
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/SupabaseTests/Stubs/FakeRealtimeClient.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Supabase.Realtime;
3 | using Supabase.Realtime.Interfaces;
4 | using Supabase.Realtime.Socket;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Collections.ObjectModel;
8 | using System.Net.WebSockets;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 | using Supabase.Realtime.Exceptions;
12 |
13 | namespace SupabaseTests.Stubs
14 | {
15 | internal class FakeRealtimeClient : IRealtimeClient
16 | {
17 | public void AddStateChangedHandler(IRealtimeClient.SocketStateEventHandler handler)
18 | {
19 | throw new NotImplementedException();
20 | }
21 |
22 | public void RemoveStateChangedHandler(IRealtimeClient.SocketStateEventHandler handler)
23 | {
24 | throw new NotImplementedException();
25 | }
26 |
27 | public void ClearStateChangedHandlers()
28 | {
29 | throw new NotImplementedException();
30 | }
31 |
32 | public void AddDebugHandler(IRealtimeDebugger.DebugEventHandler handler)
33 | {
34 | throw new NotImplementedException();
35 | }
36 |
37 | public void RemoveDebugHandler(IRealtimeDebugger.DebugEventHandler handler)
38 | {
39 | throw new NotImplementedException();
40 | }
41 |
42 | public void ClearDebugHandlers()
43 | {
44 | throw new NotImplementedException();
45 | }
46 |
47 | public RealtimeChannel Channel(string channelName)
48 | {
49 | throw new NotImplementedException();
50 | }
51 |
52 | public RealtimeChannel Channel(string database = "realtime", string schema = "public", string table = "*",
53 | string column = null, string value = null, Dictionary parameters = null)
54 | {
55 | throw new NotImplementedException();
56 | }
57 |
58 | public IRealtimeClient Connect(Action, RealtimeException> callback = null)
59 | {
60 | throw new NotImplementedException();
61 | }
62 |
63 | public Task> ConnectAsync()
64 | {
65 | throw new NotImplementedException();
66 | }
67 |
68 | public IRealtimeClient Disconnect(WebSocketCloseStatus code = WebSocketCloseStatus.NormalClosure, string reason = "Programmatic Disconnect")
69 | {
70 | throw new NotImplementedException();
71 | }
72 |
73 | public void Remove(RealtimeChannel channel)
74 | {
75 | throw new NotImplementedException();
76 | }
77 |
78 | public void SetAuth(string jwt)
79 | {
80 | throw new NotImplementedException();
81 | }
82 |
83 | public ClientOptions Options { get; }
84 | public JsonSerializerSettings SerializerSettings { get; }
85 | public IRealtimeSocket Socket { get; }
86 | public ReadOnlyDictionary Subscriptions { get; }
87 | public Func> GetHeaders { get; set; }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/SupabaseTests/Stubs/FakeRestClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Supabase.Postgrest;
5 | using Supabase.Postgrest.Interfaces;
6 | using Supabase.Postgrest.Models;
7 | using Supabase.Postgrest.Responses;
8 |
9 | namespace SupabaseTests.Stubs
10 | {
11 | internal class FakeRestClient : IPostgrestClient
12 | {
13 | public IPostgrestTableWithCache Table(IPostgrestCacheProvider cacheProvider) where T : BaseModel, new()
14 | {
15 | throw new NotImplementedException();
16 | }
17 |
18 | public string BaseUrl => throw new NotImplementedException();
19 |
20 | public ClientOptions Options => throw new NotImplementedException();
21 |
22 | public Func> GetHeaders { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
23 |
24 | public void AddRequestPreparedHandler(OnRequestPreparedEventHandler handler)
25 | {
26 | throw new NotImplementedException();
27 | }
28 |
29 | public void RemoveRequestPreparedHandler(OnRequestPreparedEventHandler handler)
30 | {
31 | throw new NotImplementedException();
32 | }
33 |
34 | public void ClearRequestPreparedHandlers()
35 | {
36 | throw new NotImplementedException();
37 | }
38 |
39 | public void AddDebugHandler(IPostgrestDebugger.DebugEventHandler handler)
40 | {
41 | throw new NotImplementedException();
42 | }
43 |
44 | public void RemoveDebugHandler(IPostgrestDebugger.DebugEventHandler handler)
45 | {
46 | throw new NotImplementedException();
47 | }
48 |
49 | public void ClearDebugHandlers()
50 | {
51 | throw new NotImplementedException();
52 | }
53 |
54 | public Task Rpc(string procedureName, object parameters)
55 | {
56 | throw new NotImplementedException();
57 | }
58 |
59 | public Task Rpc(string procedureName, object parameters = null)
60 | {
61 | throw new NotImplementedException();
62 | }
63 |
64 | public Task Rpc(string procedureName, Dictionary parameters)
65 | {
66 | throw new NotImplementedException();
67 | }
68 |
69 | public IPostgrestTable Table() where T : BaseModel, new()
70 | {
71 | throw new NotImplementedException();
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/SupabaseTests/Stubs/FakeStorageClient.cs:
--------------------------------------------------------------------------------
1 | using Supabase.Storage;
2 | using Supabase.Storage.Interfaces;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace SupabaseTests.Stubs
9 | {
10 | internal class FakeStorageClient : IStorageClient
11 | {
12 | public Dictionary Headers { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
13 | public Func> GetHeaders { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
14 |
15 | public ClientOptions Options => throw new NotImplementedException();
16 |
17 | public Task CreateBucket(string id, BucketUpsertOptions options = null)
18 | {
19 | throw new NotImplementedException();
20 | }
21 |
22 | public Task DeleteBucket(string id)
23 | {
24 | throw new NotImplementedException();
25 | }
26 |
27 | public Task EmptyBucket(string id)
28 | {
29 | throw new NotImplementedException();
30 | }
31 |
32 | public IStorageFileApi From(string id)
33 | {
34 | throw new NotImplementedException();
35 | }
36 |
37 | public Task GetBucket(string id)
38 | {
39 | throw new NotImplementedException();
40 | }
41 |
42 | public Task> ListBuckets()
43 | {
44 | throw new NotImplementedException();
45 | }
46 |
47 | public Task UpdateBucket(string id, BucketUpsertOptions options = null)
48 | {
49 | throw new NotImplementedException();
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/SupabaseTests/SupabaseTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | false
6 | 0.8.5
7 | Library
8 | net7.0
9 |
10 |
11 |
12 |
13 |
14 |
15 | runtime; build; native; contentfiles; analyzers; buildtransitive
16 | all
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | PreserveNewest
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/SupabaseTests/db/00-schema.sql:
--------------------------------------------------------------------------------
1 | -- Set up reatime
2 | create role anon nologin noinherit;
3 | create role authenticated nologin noinherit;
4 | create role service_role nologin noinherit bypassrls;
5 |
6 | grant usage on schema public to anon, authenticated, service_role;
7 |
8 | alter default privileges in schema public grant all on tables to anon, authenticated, service_role;
9 | alter default privileges in schema public grant all on functions to anon, authenticated, service_role;
10 | alter default privileges in schema public grant all on sequences to anon, authenticated, service_role;
11 |
12 | create schema if not exists _realtime;
13 | create schema if not exists realtime;
14 |
15 | create publication supabase_realtime with (publish = 'insert, update, delete');
16 |
17 | -- Supabase super admin
18 | create user supabase_admin;
19 | alter user supabase_admin with superuser createdb createrole replication bypassrls;
20 |
21 |
22 | -- Extension namespacing
23 | create schema extensions;
24 | create extension if not exists "uuid-ossp" with schema extensions;
25 | create extension if not exists pgcrypto with schema extensions;
26 | -- create extension if not exists pgjwt with schema extensions;
27 |
28 | create user authenticator noinherit;
29 | grant anon to authenticator;
30 | grant authenticated to authenticator;
31 | grant service_role to authenticator;
32 | grant supabase_admin to authenticator;
33 |
34 | grant usage on schema public to postgres, anon, authenticated, service_role;
35 | alter default privileges in schema public grant all on tables to postgres, anon, authenticated, service_role;
36 | alter default privileges in schema public grant all on functions to postgres, anon, authenticated, service_role;
37 | alter default privileges in schema public grant all on sequences to postgres, anon, authenticated, service_role;
38 |
39 | -- Set up namespacing
40 | alter user supabase_admin SET search_path TO public, extensions; -- don't include the "auth" schema
41 |
42 | -- These are required so that the users receive grants whenever "supabase_admin" creates tables/function
43 | alter default privileges for user supabase_admin in schema public grant all
44 | on sequences to postgres, anon, authenticated, service_role;
45 | alter default privileges for user supabase_admin in schema public grant all
46 | on tables to postgres, anon, authenticated, service_role;
47 | alter default privileges for user supabase_admin in schema public grant all
48 | on functions to postgres, anon, authenticated, service_role;
--------------------------------------------------------------------------------
/SupabaseTests/db/01-auth-schema.sql:
--------------------------------------------------------------------------------
1 | CREATE SCHEMA IF NOT EXISTS auth AUTHORIZATION postgres;
2 | -- auth.users definition
3 | CREATE TABLE auth.users (
4 | instance_id uuid NULL,
5 | id uuid NOT NULL,
6 | aud varchar(255) NULL,
7 | "role" varchar(255) NULL,
8 | email varchar(255) NULL,
9 | encrypted_password varchar(255) NULL,
10 | confirmed_at timestamptz NULL,
11 | invited_at timestamptz NULL,
12 | confirmation_token varchar(255) NULL,
13 | confirmation_sent_at timestamptz NULL,
14 | recovery_token varchar(255) NULL,
15 | recovery_sent_at timestamptz NULL,
16 | email_change_token varchar(255) NULL,
17 | email_change varchar(255) NULL,
18 | email_change_sent_at timestamptz NULL,
19 | last_sign_in_at timestamptz NULL,
20 | raw_app_meta_data jsonb NULL,
21 | raw_user_meta_data jsonb NULL,
22 | is_super_admin bool NULL,
23 | created_at timestamptz NULL,
24 | updated_at timestamptz NULL,
25 | CONSTRAINT users_pkey PRIMARY KEY (id)
26 | );
27 | CREATE INDEX users_instance_id_email_idx ON auth.users USING btree (instance_id, email);
28 | CREATE INDEX users_instance_id_idx ON auth.users USING btree (instance_id);
29 | -- auth.refresh_tokens definition
30 | CREATE TABLE auth.refresh_tokens (
31 | instance_id uuid NULL,
32 | id bigserial NOT NULL,
33 | "token" varchar(255) NULL,
34 | user_id varchar(255) NULL,
35 | revoked bool NULL,
36 | created_at timestamptz NULL,
37 | updated_at timestamptz NULL,
38 | CONSTRAINT refresh_tokens_pkey PRIMARY KEY (id)
39 | );
40 | CREATE INDEX refresh_tokens_instance_id_idx ON auth.refresh_tokens USING btree (instance_id);
41 | CREATE INDEX refresh_tokens_instance_id_user_id_idx ON auth.refresh_tokens USING btree (instance_id, user_id);
42 | CREATE INDEX refresh_tokens_token_idx ON auth.refresh_tokens USING btree (token);
43 | -- auth.instances definition
44 | CREATE TABLE auth.instances (
45 | id uuid NOT NULL,
46 | uuid uuid NULL,
47 | raw_base_config text NULL,
48 | created_at timestamptz NULL,
49 | updated_at timestamptz NULL,
50 | CONSTRAINT instances_pkey PRIMARY KEY (id)
51 | );
52 | -- auth.audit_log_entries definition
53 | CREATE TABLE auth.audit_log_entries (
54 | instance_id uuid NULL,
55 | id uuid NOT NULL,
56 | payload json NULL,
57 | created_at timestamptz NULL,
58 | CONSTRAINT audit_log_entries_pkey PRIMARY KEY (id)
59 | );
60 | CREATE INDEX audit_logs_instance_id_idx ON auth.audit_log_entries USING btree (instance_id);
61 | -- auth.schema_migrations definition
62 | CREATE TABLE auth.schema_migrations (
63 | "version" varchar(255) NOT NULL,
64 | CONSTRAINT schema_migrations_pkey PRIMARY KEY ("version")
65 | );
66 | INSERT INTO auth.schema_migrations (version)
67 | VALUES ('20171026211738'),
68 | ('20171026211808'),
69 | ('20171026211834'),
70 | ('20180103212743'),
71 | ('20180108183307'),
72 | ('20180119214651'),
73 | ('20180125194653');
74 | -- Gets the User ID from the request cookie
75 | create or replace function auth.uid() returns uuid as $$
76 | select nullif(current_setting('request.jwt.claim.sub', true), '')::uuid;
77 | $$ language sql stable;
78 | -- Gets the User ID from the request cookie
79 | create or replace function auth.role() returns text as $$
80 | select nullif(current_setting('request.jwt.claim.role', true), '')::text;
81 | $$ language sql stable;
82 | GRANT ALL PRIVILEGES ON SCHEMA auth TO postgres;
83 | GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA auth TO postgres;
84 | GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA auth TO postgres;
85 | ALTER USER postgres SET search_path = "auth";
--------------------------------------------------------------------------------
/SupabaseTests/db/02-rest-schema.sql:
--------------------------------------------------------------------------------
1 | -- CHANNELS
2 | CREATE TABLE public.channels (
3 | id int generated by default as identity,
4 | inserted_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
5 | updated_at timestamp without time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
6 | data jsonb DEFAULT null,
7 | slug text
8 | );
9 | ALTER TABLE public.channels REPLICA IDENTITY FULL;
10 | ALTER publication supabase_realtime add table public.channels;
--------------------------------------------------------------------------------
/SupabaseTests/db/03-dummy-data.sql:
--------------------------------------------------------------------------------
1 | -- insert users
2 | INSERT INTO "auth"."users" ("instance_id", "id", "aud", "role", "email", "encrypted_password", "confirmed_at", "invited_at", "confirmation_token", "confirmation_sent_at", "recovery_token", "recovery_sent_at", "email_change_token", "email_change", "email_change_sent_at", "last_sign_in_at", "raw_app_meta_data", "raw_user_meta_data", "is_super_admin", "created_at", "updated_at") VALUES
3 | ('00000000-0000-0000-0000-000000000000', '317eadce-631a-4429-a0bb-f19a7a517b4a', 'authenticated', 'authenticated', 'inian+user2@supabase.io', '', NULL, '2021-02-17 04:41:13.408828+00', '541rn7rTZPGeGCYsp0a38g', '2021-02-17 04:41:13.408828+00', '', NULL, '', '', NULL, NULL, '{"provider": "email"}', 'null', 'f', '2021-02-17 04:41:13.406912+00', '2021-02-17 04:41:13.406919+00'),
4 | ('00000000-0000-0000-0000-000000000000', '4d56e902-f0a0-4662-8448-a4d9e643c142', 'authenticated', 'authenticated', 'inian+user1@supabase.io', '', NULL, '2021-02-17 04:40:58.570482+00', 'U1HvzExEO3l7JzP-4tTxJA', '2021-02-17 04:40:58.570482+00', '', NULL, '', '', NULL, NULL, '{"provider": "email"}', 'null', 'f', '2021-02-17 04:40:58.568637+00', '2021-02-17 04:40:58.568642+00'),
5 | ('00000000-0000-0000-0000-000000000000', 'd8c7bce9-cfeb-497b-bd61-e66ce2cbdaa2', 'authenticated', 'authenticated', 'inian+admin@supabase.io', '', NULL, '2021-02-17 04:40:42.901743+00', '3EG99GjT_e3NC4eGEBXOjw', '2021-02-17 04:40:42.901743+00', '', NULL, '', '', NULL, NULL, '{"provider": "email"}', 'null', 'f', '2021-02-17 04:40:42.890632+00', '2021-02-17 04:40:42.890637+00');
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "2.3"
2 |
3 | services:
4 | db:
5 | image: supabase/postgres:14.1.0.105
6 | container_name: realtime-db
7 | ports:
8 | - "5432:5432"
9 | volumes:
10 | - ./SupabaseTests/db:/docker-entrypoint-initdb.d/
11 | command: postgres -c config_file=/etc/postgresql/postgresql.conf
12 | environment:
13 | POSTGRES_HOST: /var/run/postgresql
14 | POSTGRES_PASSWORD: postgres
15 |
16 | rest:
17 | image: postgrest/postgrest:latest
18 | restart: unless-stopped
19 | ports:
20 | - "3000:3000"
21 | environment:
22 | PGRST_DB_URI: postgres://postgres:postgres@db:5432/postgres
23 | PGRST_DB_SCHEMA: public,storage
24 | PGRST_DB_EXTRA_SEARCH_PATH: public,storage,extensions
25 | PGRST_DB_ANON_ROLE: postgres
26 | PGRST_JWT_SECRET: 'f023d3db-39dc-4ac9-87b2-b2be72e9162b'
27 | depends_on:
28 | - db
29 |
30 | gotrue:
31 | image: supabase/gotrue:v2.129.1
32 | restart: unless-stopped
33 | ports:
34 | - "9999:9999"
35 | environment:
36 | GOTRUE_MAILER_URLPATHS_CONFIRMATION: '/verify'
37 | GOTRUE_JWT_SECRET: 'f023d3db-39dc-4ac9-87b2-b2be72e9162b'
38 | GOTRUE_JWT_EXP: 3600
39 | GOTRUE_DB_DRIVER: postgres
40 | DB_NAMESPACE: auth
41 | GOTRUE_API_HOST: 0.0.0.0
42 | PORT: 9999
43 | GOTRUE_DISABLE_SIGNUP: 'false'
44 | API_EXTERNAL_URL: http://localhost:9999
45 | GOTRUE_SITE_URL: http://localhost:9999
46 | GOTRUE_URI_ALLOW_LIST: https://supabase.io/docs
47 | GOTRUE_MAILER_AUTOCONFIRM: 'true'
48 | GOTRUE_LOG_LEVEL: DEBUG
49 | GOTRUE_OPERATOR_TOKEN: super-secret-operator-token
50 | DATABASE_URL: 'postgres://postgres:postgres@db:5432/postgres?sslmode=disable'
51 | depends_on:
52 | - db
53 |
54 | realtime:
55 | depends_on:
56 | - db
57 | image: supabase/realtime:v2.13.0
58 | container_name: realtime-server
59 | ports:
60 | - "4000:4000"
61 | environment:
62 | PORT: 4000
63 | DB_HOST: host.docker.internal
64 | DB_PORT: 5432
65 | DB_USER: postgres
66 | DB_PASSWORD: postgres
67 | DB_NAME: postgres
68 | DB_ENC_KEY: supabaserealtime
69 | DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'
70 | API_JWT_SECRET: dc447559-996d-4761-a306-f47a5eab1623
71 | FLY_ALLOC_ID: fly123
72 | FLY_APP_NAME: realtime
73 | SECRET_KEY_BASE: UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq
74 | ERL_AFLAGS: -proto_dist inet_tcp
75 | ENABLE_TAILSCALE: "false"
76 | DNS_NODES: "''"
77 | command: sh -c "/app/bin/migrate && /app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)' && /app/bin/server"
78 |
79 |
--------------------------------------------------------------------------------