├── .gitattributes ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── Example ├── App.razor ├── Example.csproj ├── Models │ ├── EntryModel.cs │ └── TodoModel.cs ├── Options │ └── PocketBaseOptions.cs ├── Pages │ ├── Index.razor │ ├── Login │ │ ├── Components │ │ │ ├── NotLoggedIn.razor │ │ │ ├── NotLoggedIn.razor.cs │ │ │ ├── Profile.razor │ │ │ └── Profile.razor.cs │ │ ├── Login.razor │ │ └── Login.razor.cs │ └── SharedComponents │ │ ├── TodoList.razor │ │ ├── TodoList.razor.cs │ │ ├── TodoListEntries.razor │ │ └── TodoListEntries.razor.cs ├── PocketBaseAuthenticationStateProvider.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Shared │ ├── Loading.razor │ ├── Loading.razor.cs │ ├── MainLayout.razor │ ├── MainLayout.razor.cs │ ├── RedirectTo.razor │ └── RedirectTo.razor.cs ├── _Imports.razor └── wwwroot │ ├── appsettings.development.json │ ├── appsettings.json │ ├── css │ └── app.css │ ├── favicon.ico │ ├── icon-192.png │ └── index.html ├── LICENSE.txt ├── README.md ├── pocketbase-csharp-sdk.Tests ├── AuthStoreTests.cs ├── PocketBaseTests.cs ├── Usings.cs └── pocketbase-csharp-sdk.Tests.csproj ├── pocketbase-csharp-sdk.sln └── pocketbase-csharp-sdk ├── .editorconfig ├── AssemblyInfo.cs ├── AuthStore.cs ├── AuthStoreEvent.cs ├── Class Diagrams └── Services.cd ├── ClientException.cs ├── Errors └── ClientError.cs ├── Event ├── RequestEventArgs.cs └── ResponseEventArgs.cs ├── Extensions └── PocketBaseExtensions.cs ├── Helper ├── Convert │ └── SafeConvert.cs └── Extensions │ └── DictionaryExtensions.cs ├── Json └── DateTimeConverter.cs ├── Models ├── AdminModel.cs ├── ApiHealthModel.cs ├── Auth │ ├── AdminAuthModel.cs │ ├── AuthModel.cs │ ├── RecordAuthModel.cs │ └── UserAuthModel.cs ├── BackupModel.cs ├── BaseAuthModel.cs ├── BaseModel.cs ├── Collection │ ├── CollectionModel.cs │ ├── CollectionOptionsModel.cs │ └── SchemaFieldModel.cs ├── Enum │ └── ThumbFormat.cs ├── ExternalAuthModel.cs ├── Files │ ├── BaseFile.cs │ ├── FilepathFile.cs │ ├── IFile.cs │ └── StreamFile.cs ├── IBaseAuthModel.cs ├── IBaseModel.cs ├── Log │ ├── AuthMethodProvider.cs │ ├── AuthMethodsList.cs │ ├── LogRequestModel.cs │ └── LogRequestStatistic.cs ├── PagedCollectionModel.cs ├── ResultList.cs ├── SseMessage.cs └── UserModel.cs ├── PocketBase.cs ├── Services ├── AdminService.cs ├── BackupService.cs ├── Base │ ├── BaseAuthService.cs │ ├── BaseCrudService.cs │ ├── BaseService.cs │ └── BaseSubCrudService.cs ├── CollectionAuthService.cs ├── CollectionService.cs ├── HealthService.cs ├── LogService.cs ├── RealTimeService.cs ├── RecordService.cs ├── SettingsService.cs └── UserService.cs ├── Sse └── SseClient.cs ├── pocketbase-csharp-sdk - Backup.csproj └── pocketbase-csharp-sdk.csproj /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | deploy-to-github-pages: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3.3.0 11 | 12 | - name: Setup .NET Core 13 | uses: actions/setup-dotnet@v3.0.3 14 | with: 15 | dotnet-version: 6.0.x 16 | 17 | - name: Install WASM Build Tools 18 | run: dotnet workload install wasm-tools 19 | 20 | - name: Publish .NET Core Project 21 | run: dotnet publish Example/Example.csproj -c:Release -o dist/Web --nologo 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # rider settings 7 | .idea/ 8 | 9 | 10 | # User-specific files 11 | *.rsuser 12 | *.suo 13 | *.user 14 | *.userosscache 15 | *.sln.docstates 16 | appsettings.development.json 17 | 18 | # User-specific files (MonoDevelop/Xamarin Studio) 19 | *.userprefs 20 | 21 | # Mono auto generated files 22 | mono_crash.* 23 | 24 | # Build results 25 | [Dd]ebug/ 26 | [Dd]ebugPublic/ 27 | [Rr]elease/ 28 | [Rr]eleases/ 29 | x64/ 30 | x86/ 31 | [Ww][Ii][Nn]32/ 32 | [Aa][Rr][Mm]/ 33 | [Aa][Rr][Mm]64/ 34 | bld/ 35 | [Bb]in/ 36 | [Oo]bj/ 37 | [Oo]ut/ 38 | [Ll]og/ 39 | [Ll]ogs/ 40 | 41 | # Visual Studio 2015/2017 cache/options directory 42 | .vs/ 43 | # Uncomment if you have tasks that create the project's static files in wwwroot 44 | #wwwroot/ 45 | 46 | # Visual Studio 2017 auto generated files 47 | Generated\ Files/ 48 | 49 | # MSTest test Results 50 | [Tt]est[Rr]esult*/ 51 | [Bb]uild[Ll]og.* 52 | 53 | # NUnit 54 | *.VisualState.xml 55 | TestResult.xml 56 | nunit-*.xml 57 | 58 | # Build Results of an ATL Project 59 | [Dd]ebugPS/ 60 | [Rr]eleasePS/ 61 | dlldata.c 62 | 63 | # Benchmark Results 64 | BenchmarkDotNet.Artifacts/ 65 | 66 | # .NET Core 67 | project.lock.json 68 | project.fragment.lock.json 69 | artifacts/ 70 | 71 | # ASP.NET Scaffolding 72 | ScaffoldingReadMe.txt 73 | 74 | # StyleCop 75 | StyleCopReport.xml 76 | 77 | # Files built by Visual Studio 78 | *_i.c 79 | *_p.c 80 | *_h.h 81 | *.ilk 82 | *.meta 83 | *.obj 84 | *.iobj 85 | *.pch 86 | *.pdb 87 | *.ipdb 88 | *.pgc 89 | *.pgd 90 | *.rsp 91 | *.sbr 92 | *.tlb 93 | *.tli 94 | *.tlh 95 | *.tmp 96 | *.tmp_proj 97 | *_wpftmp.csproj 98 | *.log 99 | *.vspscc 100 | *.vssscc 101 | .builds 102 | *.pidb 103 | *.svclog 104 | *.scc 105 | 106 | # Chutzpah Test files 107 | _Chutzpah* 108 | 109 | # Visual C++ cache files 110 | ipch/ 111 | *.aps 112 | *.ncb 113 | *.opendb 114 | *.opensdf 115 | *.sdf 116 | *.cachefile 117 | *.VC.db 118 | *.VC.VC.opendb 119 | 120 | # Visual Studio profiler 121 | *.psess 122 | *.vsp 123 | *.vspx 124 | *.sap 125 | 126 | # Visual Studio Trace Files 127 | *.e2e 128 | 129 | # TFS 2012 Local Workspace 130 | $tf/ 131 | 132 | # Guidance Automation Toolkit 133 | *.gpState 134 | 135 | # ReSharper is a .NET coding add-in 136 | _ReSharper*/ 137 | *.[Rr]e[Ss]harper 138 | *.DotSettings.user 139 | 140 | # TeamCity is a build add-in 141 | _TeamCity* 142 | 143 | # DotCover is a Code Coverage Tool 144 | *.dotCover 145 | 146 | # AxoCover is a Code Coverage Tool 147 | .axoCover/* 148 | !.axoCover/settings.json 149 | 150 | # Coverlet is a free, cross platform Code Coverage Tool 151 | coverage*.json 152 | coverage*.xml 153 | coverage*.info 154 | 155 | # Visual Studio code coverage results 156 | *.coverage 157 | *.coveragexml 158 | 159 | # NCrunch 160 | _NCrunch_* 161 | .*crunch*.local.xml 162 | nCrunchTemp_* 163 | 164 | # MightyMoose 165 | *.mm.* 166 | AutoTest.Net/ 167 | 168 | # Web workbench (sass) 169 | .sass-cache/ 170 | 171 | # Installshield output folder 172 | [Ee]xpress/ 173 | 174 | # DocProject is a documentation generator add-in 175 | DocProject/buildhelp/ 176 | DocProject/Help/*.HxT 177 | DocProject/Help/*.HxC 178 | DocProject/Help/*.hhc 179 | DocProject/Help/*.hhk 180 | DocProject/Help/*.hhp 181 | DocProject/Help/Html2 182 | DocProject/Help/html 183 | 184 | # Click-Once directory 185 | publish/ 186 | 187 | # Publish Web Output 188 | *.[Pp]ublish.xml 189 | *.azurePubxml 190 | # Note: Comment the next line if you want to checkin your web deploy settings, 191 | # but database connection strings (with potential passwords) will be unencrypted 192 | *.pubxml 193 | *.publishproj 194 | 195 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 196 | # checkin your Azure Web App publish settings, but sensitive information contained 197 | # in these scripts will be unencrypted 198 | PublishScripts/ 199 | 200 | # NuGet Packages 201 | *.nupkg 202 | # NuGet Symbol Packages 203 | *.snupkg 204 | # The packages folder can be ignored because of Package Restore 205 | **/[Pp]ackages/* 206 | # except build/, which is used as an MSBuild target. 207 | !**/[Pp]ackages/build/ 208 | # Uncomment if necessary however generally it will be regenerated when needed 209 | #!**/[Pp]ackages/repositories.config 210 | # NuGet v3's project.json files produces more ignorable files 211 | *.nuget.props 212 | *.nuget.targets 213 | 214 | # Microsoft Azure Build Output 215 | csx/ 216 | *.build.csdef 217 | 218 | # Microsoft Azure Emulator 219 | ecf/ 220 | rcf/ 221 | 222 | # Windows Store app package directories and files 223 | AppPackages/ 224 | BundleArtifacts/ 225 | Package.StoreAssociation.xml 226 | _pkginfo.txt 227 | *.appx 228 | *.appxbundle 229 | *.appxupload 230 | 231 | # Visual Studio cache files 232 | # files ending in .cache can be ignored 233 | *.[Cc]ache 234 | # but keep track of directories ending in .cache 235 | !?*.[Cc]ache/ 236 | 237 | # Others 238 | ClientBin/ 239 | ~$* 240 | *~ 241 | *.dbmdl 242 | *.dbproj.schemaview 243 | *.jfm 244 | *.pfx 245 | *.publishsettings 246 | orleans.codegen.cs 247 | 248 | # Including strong name files can present a security risk 249 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 250 | #*.snk 251 | 252 | # Since there are multiple workflows, uncomment next line to ignore bower_components 253 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 254 | #bower_components/ 255 | 256 | # RIA/Silverlight projects 257 | Generated_Code/ 258 | 259 | # Backup & report files from converting an old project file 260 | # to a newer Visual Studio version. Backup files are not needed, 261 | # because we have git ;-) 262 | _UpgradeReport_Files/ 263 | Backup*/ 264 | UpgradeLog*.XML 265 | UpgradeLog*.htm 266 | ServiceFabricBackup/ 267 | *.rptproj.bak 268 | 269 | # SQL Server files 270 | *.mdf 271 | *.ldf 272 | *.ndf 273 | 274 | # Business Intelligence projects 275 | *.rdl.data 276 | *.bim.layout 277 | *.bim_*.settings 278 | *.rptproj.rsuser 279 | *- [Bb]ackup.rdl 280 | *- [Bb]ackup ([0-9]).rdl 281 | *- [Bb]ackup ([0-9][0-9]).rdl 282 | 283 | # Microsoft Fakes 284 | FakesAssemblies/ 285 | 286 | # GhostDoc plugin setting file 287 | *.GhostDoc.xml 288 | 289 | # Node.js Tools for Visual Studio 290 | .ntvs_analysis.dat 291 | node_modules/ 292 | 293 | # Visual Studio 6 build log 294 | *.plg 295 | 296 | # Visual Studio 6 workspace options file 297 | *.opt 298 | 299 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 300 | *.vbw 301 | 302 | # Visual Studio LightSwitch build output 303 | **/*.HTMLClient/GeneratedArtifacts 304 | **/*.DesktopClient/GeneratedArtifacts 305 | **/*.DesktopClient/ModelManifest.xml 306 | **/*.Server/GeneratedArtifacts 307 | **/*.Server/ModelManifest.xml 308 | _Pvt_Extensions 309 | 310 | # Paket dependency manager 311 | .paket/paket.exe 312 | paket-files/ 313 | 314 | # FAKE - F# Make 315 | .fake/ 316 | 317 | # CodeRush personal settings 318 | .cr/personal 319 | 320 | # Python Tools for Visual Studio (PTVS) 321 | __pycache__/ 322 | *.pyc 323 | 324 | # Cake - Uncomment if you are using it 325 | # tools/** 326 | # !tools/packages.config 327 | 328 | # Tabs Studio 329 | *.tss 330 | 331 | # Telerik's JustMock configuration file 332 | *.jmconfig 333 | 334 | # BizTalk build output 335 | *.btp.cs 336 | *.btm.cs 337 | *.odx.cs 338 | *.xsd.cs 339 | 340 | # OpenCover UI analysis results 341 | OpenCover/ 342 | 343 | # Azure Stream Analytics local run output 344 | ASALocalRun/ 345 | 346 | # MSBuild Binary and Structured Log 347 | *.binlog 348 | 349 | # NVidia Nsight GPU debugger configuration file 350 | *.nvuser 351 | 352 | # MFractors (Xamarin productivity tool) working folder 353 | .mfractor/ 354 | 355 | # Local History for Visual Studio 356 | .localhistory/ 357 | 358 | # BeatPulse healthcheck temp database 359 | healthchecksdb 360 | 361 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 362 | MigrationBackup/ 363 | 364 | # Ionide (cross platform F# VS Code tools) working folder 365 | .ionide/ 366 | 367 | # Fody - auto-generated XML schema 368 | FodyWeavers.xsd 369 | /pocketbase-csharp-sdk.Tests/RealTest.cs 370 | /pocketbase-csharp-sdk.Tests/RealTest.cs 371 | /pocketbase-csharp-sdk/pocketbase-csharp-sdk.csproj 372 | !/Example/Options/PocketBaseOptions.cs 373 | !/Example/wwwroot/appsettings.development.json 374 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | // Use IntelliSense to find out which attributes exist for C# debugging 6 | // Use hover for the description of the existing attributes 7 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/pocketbase-csharp-sdk.Tests/bin/Debug/net6.0/pocketbase-csharp-sdk.Tests.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/pocketbase-csharp-sdk.Tests", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /.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}/pocketbase-csharp-sdk.Tests/pocketbase-csharp-sdk.Tests.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}/pocketbase-csharp-sdk.Tests/pocketbase-csharp-sdk.Tests.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}/pocketbase-csharp-sdk.Tests/pocketbase-csharp-sdk.Tests.csproj" 37 | ], 38 | "problemMatcher": "$msCompile" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /Example/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | Not found 9 | 10 |

Sorry, there's nothing at this address.

11 |
12 |
13 |
14 |
-------------------------------------------------------------------------------- /Example/Example.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | fd23fba6-03b7-4047-8686-dc3d7ef8e733 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 | PreserveNewest 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Example/Models/EntryModel.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Models; 2 | 3 | namespace Example.Models 4 | { 5 | public class EntryModel : BaseModel 6 | { 7 | public string? Name { get; set; } 8 | public bool IsDone { get; set; } 9 | public string? Todo_Id { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Example/Models/TodoModel.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Models; 2 | 3 | namespace Example.Models 4 | { 5 | public class TodoModel : BaseModel 6 | { 7 | public string? Name { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Example/Options/PocketBaseOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Example.Options; 2 | 3 | public class PocketBaseOptions 4 | { 5 | public const string Position = "PocketBase"; 6 | 7 | public string BaseUrl { get; set; } = "https://sdk-todo-example.pockethost.io/"; 8 | } 9 | -------------------------------------------------------------------------------- /Example/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @using Example.Pages.SharedComponents 3 | 4 | ToDo-Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Example/Pages/Login/Components/NotLoggedIn.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Login 5 | Username: demo Password: demo1234 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Login 14 | 15 | -------------------------------------------------------------------------------- /Example/Pages/Login/Components/NotLoggedIn.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Authorization; 2 | using Microsoft.AspNetCore.Components; 3 | using MudBlazor; 4 | using pocketbase_csharp_sdk; 5 | 6 | namespace Example.Pages.Login.Components 7 | { 8 | public partial class NotLoggedIn 9 | { 10 | 11 | [Inject] 12 | public NavigationManager NavigationManager { get; set; } = null!; 13 | 14 | [Inject] 15 | public PocketBase PocketBase { get; set; } = null!; 16 | 17 | [Inject] 18 | public ISnackbar Snackbar { get; set; } = null!; 19 | 20 | [Inject] 21 | public AuthenticationStateProvider AuthenticationStateProvider { get; set; } = null!; 22 | 23 | public string? Username { get; set; } 24 | public string? Password { get; set; } 25 | 26 | protected async Task LoginAsync() 27 | { 28 | var valid = CheckInputs(); 29 | if (valid) 30 | { 31 | try 32 | { 33 | var result = await PocketBase.User.AuthenticateWithPasswordAsync(Username!, Password!); 34 | if (result.IsSuccess) 35 | { 36 | Snackbar.Add("Logged in!", Severity.Success); 37 | var claims = PocketBaseAuthenticationStateProvider.ParseClaimsFromJwt(result.Value.Token); 38 | ((PocketBaseAuthenticationStateProvider)AuthenticationStateProvider).MarkUserAsAuthenticated(claims); 39 | NavigationManager.NavigateTo("/"); 40 | } 41 | } 42 | catch (Exception ex) 43 | { 44 | Snackbar.Add("Login failed, please check your Username and Password", Severity.Error); 45 | } 46 | 47 | } 48 | } 49 | 50 | private bool CheckInputs() 51 | { 52 | var userEmpty = string.IsNullOrWhiteSpace(Username); 53 | var passwordEmpty = string.IsNullOrWhiteSpace(Password); 54 | 55 | if (userEmpty || passwordEmpty) 56 | { 57 | Snackbar.Add("The Username und Password fields are required.", Severity.Warning); 58 | return false; 59 | } 60 | return true; 61 | } 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Example/Pages/Login/Components/Profile.razor: -------------------------------------------------------------------------------- 1 | @if (_currentUser is null) 2 | { 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | } 19 | else 20 | { 21 | 22 | 23 | 24 | 25 | @_currentUser.Username?.Substring(0, 1).ToUpper() 26 | 27 | 28 | 29 | @_currentUser.Username 30 | @string.Format("Created: {0:dd.MM.yyyy}", _currentUser.Created) 31 | 32 | 33 | @if (!string.IsNullOrWhiteSpace(_avatarAsBase64)) 34 | { 35 | 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /Example/Pages/Login/Components/Profile.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using pocketbase_csharp_sdk; 3 | using pocketbase_csharp_sdk.Extensions; 4 | using pocketbase_csharp_sdk.Models; 5 | 6 | namespace Example.Pages.Login.Components 7 | { 8 | public partial class Profile 9 | { 10 | 11 | [Inject] 12 | public PocketBase PocketBase { get; set; } = null!; 13 | 14 | protected UserModel? _currentUser = null; 15 | protected string? _avatarAsBase64 = null; 16 | 17 | protected override async Task OnInitializedAsync() 18 | { 19 | var currentUserResult = await PocketBase.GetCurrentUserAsync(); 20 | if (currentUserResult.IsSuccess) 21 | { 22 | _currentUser = currentUserResult.Value; 23 | var result = currentUserResult.Value; 24 | 25 | var avatarStreamResult = await PocketBase.Collection("users").DownloadFileAsync(_currentUser.Id, _currentUser.Avatar); 26 | 27 | if (avatarStreamResult.IsSuccess) 28 | { 29 | _avatarAsBase64 = await GetBase64FromStream(avatarStreamResult.Value); 30 | } 31 | } 32 | 33 | await base.OnInitializedAsync(); 34 | } 35 | 36 | private async Task GetBase64FromStream(Stream stream) 37 | { 38 | await using MemoryStream ms = new(); 39 | await stream.CopyToAsync(ms); 40 | 41 | var base64 = Convert.ToBase64String(ms.ToArray()); 42 | return $"data:image/png;base64, {base64}"; 43 | } 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Example/Pages/Login/Login.razor: -------------------------------------------------------------------------------- 1 | @page "/login" 2 | @using Example.Pages.Login.Components 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Example/Pages/Login/Login.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.AspNetCore.Components.Authorization; 3 | using MudBlazor; 4 | using pocketbase_csharp_sdk; 5 | 6 | namespace Example.Pages.Login 7 | { 8 | public partial class Login 9 | { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Example/Pages/SharedComponents/TodoList.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | To-Do List 7 | 8 | 9 | 10 | 11 | 12 | 13 | @if (todos is null) 14 | { 15 | @for (int i = 0; i < 5; i++) 16 | { 17 | 18 | } 19 | } 20 | else 21 | { 22 | 23 | 24 | 25 | 26 | Name 27 | 28 | 29 | Created 30 | 31 | 32 | Updated 33 | 34 | 35 | 36 | 37 | 38 | Show entries 39 | 40 | 41 | 42 | @context.Name 43 | 44 | 45 | @context.Created 46 | 47 | 48 | @context.Updated 49 | 50 | 51 | 52 | } 53 | 54 | 55 | Create new list 56 | 57 | -------------------------------------------------------------------------------- /Example/Pages/SharedComponents/TodoList.razor.cs: -------------------------------------------------------------------------------- 1 | using Example.Models; 2 | using Microsoft.AspNetCore.Components; 3 | using pocketbase_csharp_sdk; 4 | 5 | namespace Example.Pages.SharedComponents 6 | { 7 | public partial class TodoList 8 | { 9 | [Inject] 10 | public PocketBase PocketBase { get; set; } = null!; 11 | 12 | [Inject] 13 | public NavigationManager NavigationManager { get; set; } = null!; 14 | 15 | private bool _loading = false; 16 | private IEnumerable? todos; 17 | 18 | protected override async Task OnInitializedAsync() 19 | { 20 | await LoadTodosFromPocketbase(); 21 | await base.OnInitializedAsync(); 22 | } 23 | 24 | private async Task LoadTodosFromPocketbase() 25 | { 26 | _loading = true; 27 | 28 | 29 | var result = await PocketBase.Collection("todos").GetFullListAsync(); 30 | 31 | 32 | if (result.IsSuccess) 33 | { 34 | todos = result.Value; 35 | } 36 | 37 | _loading = false; 38 | } 39 | 40 | private void GoToDetails(TodoModel dto) 41 | { 42 | var path = $"/details/{dto.Id}"; 43 | NavigationManager.NavigateTo(path); 44 | } 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Example/Pages/SharedComponents/TodoListEntries.razor: -------------------------------------------------------------------------------- 1 | @page "/details/{Id}" 2 | 3 | 4 | 5 | 6 | To-Do List 7 | 8 | 9 | @if (_entries is null) 10 | { 11 | for (int i = 0; i < 5; i++) 12 | { 13 | 14 | } 15 | } 16 | else 17 | { 18 | 19 | @foreach (var item in _entries) 20 | { 21 | 22 | 23 |
24 | @item.Name 25 |
26 |
27 | 28 | Remove 29 | 30 |
31 | } 32 |
33 | 34 | 35 | 36 | 37 | } 38 |
-------------------------------------------------------------------------------- /Example/Pages/SharedComponents/TodoListEntries.razor.cs: -------------------------------------------------------------------------------- 1 | using Example.Models; 2 | using Microsoft.AspNetCore.Components; 3 | using pocketbase_csharp_sdk; 4 | using static MudBlazor.CategoryTypes; 5 | 6 | namespace Example.Pages.SharedComponents 7 | { 8 | public partial class TodoListEntries 9 | { 10 | [Parameter] 11 | public string? Id { get; set; } 12 | 13 | [Inject] 14 | public PocketBase PocketBase { get; set; } = null!; 15 | 16 | private List? _entries; 17 | 18 | protected override async Task OnParametersSetAsync() 19 | { 20 | await LoadEntriesAsync(); 21 | await base.OnParametersSetAsync(); 22 | } 23 | 24 | 25 | protected async Task LoadEntriesAsync() 26 | { 27 | if (string.IsNullOrWhiteSpace(Id)) 28 | { 29 | return; 30 | } 31 | 32 | var result = 33 | await PocketBase.Collection("todos_entries").GetFullListAsync(filter: $"todo_id.id='{Id}'"); 34 | if (result.IsSuccess) 35 | { 36 | _entries = result.Value.ToList(); 37 | } 38 | } 39 | 40 | protected void AddNewEntry() 41 | { 42 | if (_entries is null) 43 | { 44 | return; 45 | } 46 | _entries.Add(new EntryModel()); 47 | } 48 | 49 | protected void Remove(EntryModel item) 50 | { 51 | if (_entries is null) 52 | { 53 | return; 54 | } 55 | _entries.Remove(item); 56 | } 57 | 58 | protected async Task SaveAsync() 59 | { 60 | if (_entries is null) 61 | { 62 | return; 63 | } 64 | foreach (var item in _entries) 65 | { 66 | await PocketBase.Collection("todos_entries").UpdateAsync(item); 67 | } 68 | } 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Example/PocketBaseAuthenticationStateProvider.cs: -------------------------------------------------------------------------------- 1 | using Blazored.LocalStorage; 2 | using Microsoft.AspNetCore.Components.Authorization; 3 | using pocketbase_csharp_sdk; 4 | using System.Diagnostics; 5 | using System.Security.Claims; 6 | using System.Text.Json; 7 | 8 | namespace Example 9 | { 10 | public class PocketBaseAuthenticationStateProvider : AuthenticationStateProvider 11 | { 12 | private readonly PocketBase _pocketBase; 13 | private readonly ILocalStorageService _localStorage; 14 | 15 | public PocketBaseAuthenticationStateProvider(PocketBase pocketBase, ILocalStorageService localStorage) 16 | { 17 | this._pocketBase = pocketBase; 18 | this._localStorage = localStorage; 19 | 20 | pocketBase.AuthStore.OnChange += AuthStore_OnChange; 21 | } 22 | 23 | private async void AuthStore_OnChange(object? sender, AuthStoreEvent e) 24 | { 25 | if (e is null || string.IsNullOrWhiteSpace(e.Token)) 26 | { 27 | MarkUserAsLoggedOut(); 28 | } 29 | else 30 | { 31 | await _localStorage.SetItemAsync("token", e.Token); 32 | var claims = ParseClaimsFromJwt(e.Token); 33 | MarkUserAsAuthenticated(claims); 34 | } 35 | 36 | Debug.WriteLine(e.Token); 37 | } 38 | 39 | public async override Task GetAuthenticationStateAsync() 40 | { 41 | var savedToken = await _localStorage.GetItemAsync("token"); 42 | 43 | if (string.IsNullOrWhiteSpace(savedToken)) 44 | { 45 | return new AuthenticationState(new ClaimsPrincipal()); 46 | } 47 | 48 | var parsedClaims = ParseClaimsFromJwt(savedToken); 49 | var expires = parsedClaims.FirstOrDefault(c => c.Type == "exp"); 50 | 51 | if (expires is null || IsTokenExpired()) 52 | { 53 | return new AuthenticationState(new ClaimsPrincipal()); 54 | } 55 | 56 | _pocketBase.AuthStore.Save(savedToken, null); 57 | await _pocketBase.User.RefreshAsync(); 58 | 59 | return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(savedToken), "jwt"))); 60 | } 61 | 62 | public void MarkUserAsAuthenticated(IEnumerable claims) 63 | { 64 | var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(claims, "pocketbase")); 65 | var authState = Task.FromResult(new AuthenticationState(authenticatedUser)); 66 | NotifyAuthenticationStateChanged(authState); 67 | } 68 | 69 | public void MarkUserAsLoggedOut() 70 | { 71 | var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity()); 72 | var authState = Task.FromResult(new AuthenticationState(anonymousUser)); 73 | NotifyAuthenticationStateChanged(authState); 74 | } 75 | 76 | public static IEnumerable ParseClaimsFromJwt(string? jwt) 77 | { 78 | if (string.IsNullOrWhiteSpace(jwt)) 79 | { 80 | return Enumerable.Empty(); 81 | } 82 | 83 | var payload = jwt.Split(".")[1]; 84 | var jsonBytes = ParseBase64WithoutPadding(payload); 85 | var keyValuePairs = JsonSerializer.Deserialize>(jsonBytes); 86 | 87 | if (keyValuePairs is null) 88 | { 89 | return Enumerable.Empty(); 90 | } 91 | 92 | return keyValuePairs 93 | .Select(kvp => new Claim(kvp.Key, kvp.Value.ToString() ?? "")) 94 | .ToList(); 95 | } 96 | 97 | public static byte[] ParseBase64WithoutPadding(string base64) 98 | { 99 | switch (base64.Length % 4) 100 | { 101 | case 2: 102 | base64 += "=="; 103 | break; 104 | case 3: 105 | base64 += "="; 106 | break; 107 | } 108 | return Convert.FromBase64String(base64); 109 | } 110 | 111 | private bool IsTokenExpired() 112 | { 113 | return _pocketBase.AuthStore.IsValid; 114 | } 115 | 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Example/Program.cs: -------------------------------------------------------------------------------- 1 | using Blazored.LocalStorage; 2 | using Example; 3 | using Example.Options; 4 | using Microsoft.AspNetCore.Components.Authorization; 5 | using Microsoft.AspNetCore.Components.Web; 6 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 7 | using MudBlazor.Services; 8 | using pocketbase_csharp_sdk; 9 | 10 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 11 | builder.RootComponents.Add("#app"); 12 | builder.RootComponents.Add("head::after"); 13 | 14 | // Make the loaded config available via dependency injection 15 | builder.Services.AddSingleton(builder.Configuration); 16 | 17 | 18 | var pbConfigurationSection = builder.Configuration.GetSection(PocketBaseOptions.Position); 19 | 20 | builder.Services.Configure(pbConfigurationSection); 21 | 22 | var pbOptions = pbConfigurationSection.Get(); 23 | 24 | builder.Services.AddSingleton(sp => 25 | { 26 | return new PocketBase(pbOptions.BaseUrl); 27 | }); 28 | 29 | builder.Services.AddBlazoredLocalStorage(); 30 | builder.Services.AddMudServices(); 31 | 32 | //Authentication 33 | builder.Services.AddScoped(); 34 | builder.Services.AddAuthorizationCore(); 35 | 36 | await builder.Build().RunAsync(); 37 | -------------------------------------------------------------------------------- /Example/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:44938", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "Example": { 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:5148", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "IIS Express": { 22 | "commandName": "IISExpress", 23 | "launchBrowser": true, 24 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Example/Shared/Loading.razor: -------------------------------------------------------------------------------- 1 | @if (!Visible ?? false) 2 | { 3 | 4 | } 5 | else 6 | { 7 | @ChildContent 8 | } -------------------------------------------------------------------------------- /Example/Shared/Loading.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace Example.Shared 4 | { 5 | public partial class Loading 6 | { 7 | [Parameter] 8 | public bool? Visible { get; set; } 9 | 10 | [Parameter] 11 | public RenderFragment? ChildContent { get; set; } 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Example/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 | 4 | 5 | Example-Todo-App 6 | 7 | 8 | 9 | 10 | 11 | @Body 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Example/Shared/MainLayout.razor.cs: -------------------------------------------------------------------------------- 1 | using MudBlazor; 2 | 3 | namespace Example.Shared 4 | { 5 | public partial class MainLayout 6 | { 7 | 8 | public bool UseDarkmode { get; set; } = false; 9 | 10 | //fields 11 | protected MudThemeProvider? themeProvider = null!; 12 | 13 | protected override async Task OnAfterRenderAsync(bool firstRender) 14 | { 15 | if (firstRender) 16 | { 17 | UseDarkmode = await themeProvider!.GetSystemPreference(); 18 | StateHasChanged(); 19 | } 20 | await base.OnAfterRenderAsync(firstRender); 21 | } 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Example/Shared/RedirectTo.razor: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /Example/Shared/RedirectTo.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace Example.Shared 4 | { 5 | public partial class RedirectTo 6 | { 7 | [Inject] 8 | public NavigationManager NavigationManager { get; set; } = null!; 9 | 10 | [Parameter] 11 | public string? To { get; set; } 12 | 13 | protected override void OnInitialized() 14 | { 15 | if (!string.IsNullOrWhiteSpace(To)) 16 | { 17 | NavigationManager.NavigateTo(To); 18 | } 19 | 20 | base.OnInitialized(); 21 | } 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Example/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Authorization; 4 | @using Microsoft.AspNetCore.Components.Authorization 5 | @using Microsoft.AspNetCore.Components.Forms 6 | @using Microsoft.AspNetCore.Components.Routing 7 | @using Microsoft.AspNetCore.Components.Web 8 | @using Microsoft.AspNetCore.Components.Web.Virtualization 9 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 10 | @using Microsoft.JSInterop 11 | @using Example 12 | @using Example.Shared 13 | @using MudBlazor -------------------------------------------------------------------------------- /Example/wwwroot/appsettings.development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | }, 9 | "PocketBase": { 10 | "BaseUrl": "https://sdk-todo-example.pockethost.io/" 11 | } 12 | } -------------------------------------------------------------------------------- /Example/wwwroot/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | }, 9 | "PocketBase": { 10 | "BaseUrl": "https://sdk-todo-example.pockethost.io/" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Example/wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | #blazor-error-ui { 2 | background: lightyellow; 3 | bottom: 0; 4 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 5 | display: none; 6 | left: 0; 7 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 8 | position: fixed; 9 | width: 100%; 10 | z-index: 1000; 11 | } 12 | 13 | #blazor-error-ui .dismiss { 14 | cursor: pointer; 15 | position: absolute; 16 | right: 0.75rem; 17 | top: 0.5rem; 18 | } 19 | 20 | .blazor-error-boundary { 21 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 22 | padding: 1rem 1rem 1rem 3.7rem; 23 | color: white; 24 | } 25 | 26 | .blazor-error-boundary::after { 27 | content: "An error has occurred." 28 | } 29 | -------------------------------------------------------------------------------- /Example/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PRCV1/pocketbase-csharp-sdk/b553a812124c3c0cb073c15a7fa248595fb100e9/Example/wwwroot/favicon.ico -------------------------------------------------------------------------------- /Example/wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PRCV1/pocketbase-csharp-sdk/b553a812124c3c0cb073c15a7fa248595fb100e9/Example/wwwroot/icon-192.png -------------------------------------------------------------------------------- /Example/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
Loading...
18 | 19 |
20 | An unhandled error has occurred. 21 | Reload 22 | 🗙 23 |
24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 PRCV1 (the main developer) 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 | PocketBase C# SDK 2 | ====================================================================== 3 | This project is currently still under development. It is not recommended to use it in a productive environment. Things can and will change. 4 | 5 | Community-developed C# SDK (Multiplatform) for interacting with the [PocketBase API](https://pocketbase.io/docs) 6 | 7 | - [PocketBase C# SDK](#pocketbase-c-sdk) 8 | - [Installation](#installation) 9 | - [Nuget](#nuget) 10 | - [Usage](#usage) 11 | - [Development](#development) 12 | - [Requirements](#requirements) 13 | - [Steps](#steps) 14 | 15 | # Installation 16 | 17 | ## Nuget 18 | 19 | Coming soon 20 | 21 | # Usage 22 | ```c# 23 | //create a new Client which connects to your PocketBase-API 24 | var client = new PocketBase("http://127.0.0.1:8090"); 25 | 26 | //authenticate as a Admin 27 | var admin = await client.Admin.AuthenticateWithPassword("test@test.de", "0123456789"); 28 | 29 | //or as a User 30 | var user = await client.User.AuthenticateWithPassword("kekw@kekw.com", "0123456789"); 31 | 32 | //query some data (for example, some restaurants) 33 | //note that each CRUD action requires a data type which inherits from the base class 'ItemBaseModel'. 34 | var restaurantList = await client.Records.ListAsync("restaurants"); 35 | 36 | //like this one 37 | class Restaurant : ItemBaseModel 38 | { 39 | public string? Name { get; set; } 40 | } 41 | ``` 42 | 43 | # Development 44 | 45 | ## Requirements 46 | - Visual Studio (Community Edition should work just fine) 47 | - .NET 6/7 SDK 48 | 49 | ## Steps 50 | 1. Clone this repository 51 | ```cmd 52 | git clone https://github.com/PRCV1/pocketbase-csharp-sdk 53 | ``` 54 | 2. Open the [pocketbase-csharp-sdk.sln](pocketbase-csharp-sdk.sln) with Visual Studio (Community Edition should work just fine) -------------------------------------------------------------------------------- /pocketbase-csharp-sdk.Tests/AuthStoreTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using pocketbase_csharp_sdk.Models; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace pocketbase_csharp_sdk.Tests 10 | { 11 | [TestClass] 12 | public class AuthStoreTests 13 | { 14 | [TestMethod] 15 | public void Test_Is_Not_Valid_With_Empty_Token() 16 | { 17 | AuthStore store = new(); 18 | 19 | store.IsValid.Should().BeFalse(); 20 | } 21 | 22 | [TestMethod] 23 | public void Test_Is_Not_Valid_With_Invalid_Token() 24 | { 25 | AuthStore store = new(); 26 | 27 | store.Save("invalid", null); 28 | 29 | store.IsValid.Should().BeFalse(); 30 | } 31 | 32 | [TestMethod] 33 | public void Test_Is_Not_Valid_With_Expired_Token() 34 | { 35 | AuthStore store = new(); 36 | string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NDA5OTE2NjF9.TxZjXz_Ks665Hju0FkZSGqHFCYBbgBmMGOLnIzkg9Dg"; 37 | 38 | store.Save(token, null); 39 | 40 | store.IsValid.Should().BeFalse(); 41 | } 42 | 43 | [TestMethod] 44 | public void Test_Is_Valid_With_Valid_Token() 45 | { 46 | AuthStore store = new(); 47 | string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE4OTM0NTI0NjF9.yVr-4JxMz6qUf1MIlGx8iW2ktUrQaFecjY_TMm7Bo4o"; 48 | 49 | store.Save(token, null); 50 | 51 | store.IsValid.Should().BeTrue(); 52 | } 53 | 54 | [TestMethod] 55 | public void Test_Authtoken_Can_be_Read() 56 | { 57 | AuthStore store = new(); 58 | string token = "test_token"; 59 | 60 | store.Save(token, null); 61 | 62 | store.Token.Should().Be(token); 63 | } 64 | 65 | [TestMethod] 66 | public void Test_Model_Can_be_Read() 67 | { 68 | AuthStore store = new(); 69 | UserModel model = new UserModel(); 70 | 71 | store.Save(null, model); 72 | 73 | store.Model.Should().Be(model); 74 | } 75 | 76 | [TestMethod] 77 | public void Test_Store_Can_be_Saved() 78 | { 79 | AuthStore store = new(); 80 | 81 | string token = "token123"; 82 | UserModel model = new UserModel(); 83 | 84 | store.OnChange += (sender, e) => 85 | { 86 | e.Token.Should().Be(token); 87 | e.Model.Should().Be(model); 88 | }; 89 | 90 | store.Save(token, model); 91 | 92 | store.Model.Should().Be(model); 93 | store.Token.Should().Be(token); 94 | } 95 | 96 | [TestMethod] 97 | public void Test_Store_Can_be_Cleared() 98 | { 99 | AuthStore store = new(); 100 | 101 | string token = "token123"; 102 | UserModel model = new UserModel(); 103 | 104 | store.Save(token, model); 105 | 106 | store.Token.Should().Be(token); 107 | store.Model.Should().Be(model); 108 | 109 | store.OnChange += (sender, e) => 110 | { 111 | e.Token.Should().BeNull(); 112 | e.Model.Should().BeNull(); 113 | }; 114 | 115 | store.Clear(); 116 | 117 | store.Token.Should().BeNull(); 118 | store.Model.Should().BeNull(); 119 | } 120 | 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk.Tests/PocketBaseTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Moq; 3 | using Moq.Protected; 4 | using System.Net; 5 | 6 | namespace pocketbase_csharp_sdk.Tests 7 | { 8 | [TestClass] 9 | public class PocketBaseTests 10 | { 11 | 12 | [TestMethod] 13 | [DataRow("https://example.com/", "test")] 14 | [DataRow("https://example.com/", "/test")] 15 | [DataRow("https://example.com", "test")] 16 | [DataRow("https://example.com", "/test")] 17 | public void Test_BaseUrls(string baseUrl, string url) 18 | { 19 | var client = new PocketBase(baseUrl); 20 | 21 | var fullUrl = client.BuildUrl(url).ToString(); 22 | 23 | fullUrl.Should().Be("https://example.com/test"); 24 | } 25 | 26 | [TestMethod] 27 | [DataRow("test")] 28 | [DataRow("/test")] 29 | public void Test_Relative_Urls(string relativeUrl) 30 | { 31 | var client = new PocketBase("/api"); 32 | 33 | var url = client.BuildUrl(relativeUrl).ToString(); 34 | 35 | url.Should().Be("/api/test"); 36 | } 37 | 38 | [TestMethod] 39 | public void Test_With_Query_Paramters() 40 | { 41 | var client = new PocketBase("https://example.com"); 42 | 43 | Dictionary parameters = new() 44 | { 45 | { "a", null }, 46 | { "b", 123 }, 47 | { "c", "123" }, 48 | { "d", new object?[] { "1", 2, null } }, 49 | { "@encodeA", "@encodeB" }, 50 | }; 51 | var url = client.BuildUrl("/test", parameters).ToString(); 52 | 53 | url.Should().Be("https://example.com/test?b=123&c=123&d=1&d=2&%40encodeA=%40encodeB"); 54 | } 55 | 56 | } 57 | } -------------------------------------------------------------------------------- /pocketbase-csharp-sdk.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Microsoft.VisualStudio.TestTools.UnitTesting; -------------------------------------------------------------------------------- /pocketbase-csharp-sdk.Tests/pocketbase-csharp-sdk.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | pocketbase_csharp_sdk.Tests 6 | enable 7 | enable 8 | 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.32912.340 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "pocketbase-csharp-sdk", "pocketbase-csharp-sdk\pocketbase-csharp-sdk.csproj", "{21E2CDE7-8BCE-4B46-A84E-84D4F1BD9918}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "pocketbase-csharp-sdk.Tests", "pocketbase-csharp-sdk.Tests\pocketbase-csharp-sdk.Tests.csproj", "{6E0330EB-EA4D-44DC-9693-712F79F9083E}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Projektmappenelemente", "Projektmappenelemente", "{686D73F6-3E39-44B6-8494-77CD3D0447BC}" 11 | ProjectSection(SolutionItems) = preProject 12 | README.md = README.md 13 | EndProjectSection 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Licenses", "Licenses", "{18252485-A231-4ADF-9266-E5F88BC09B96}" 16 | ProjectSection(SolutionItems) = preProject 17 | LICENSE.txt = LICENSE.txt 18 | EndProjectSection 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example", "Example\Example.csproj", "{6F123D24-7AAE-4FB6-9659-92A7A94E48D9}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {21E2CDE7-8BCE-4B46-A84E-84D4F1BD9918}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {21E2CDE7-8BCE-4B46-A84E-84D4F1BD9918}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {21E2CDE7-8BCE-4B46-A84E-84D4F1BD9918}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {21E2CDE7-8BCE-4B46-A84E-84D4F1BD9918}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {6E0330EB-EA4D-44DC-9693-712F79F9083E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {6E0330EB-EA4D-44DC-9693-712F79F9083E}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {6E0330EB-EA4D-44DC-9693-712F79F9083E}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {6E0330EB-EA4D-44DC-9693-712F79F9083E}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {6F123D24-7AAE-4FB6-9659-92A7A94E48D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {6F123D24-7AAE-4FB6-9659-92A7A94E48D9}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {6F123D24-7AAE-4FB6-9659-92A7A94E48D9}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {6F123D24-7AAE-4FB6-9659-92A7A94E48D9}.Release|Any CPU.Build.0 = Release|Any CPU 40 | EndGlobalSection 41 | GlobalSection(SolutionProperties) = preSolution 42 | HideSolutionNode = FALSE 43 | EndGlobalSection 44 | GlobalSection(ExtensibilityGlobals) = postSolution 45 | SolutionGuid = {5504ADED-0B03-4550-835E-82C2C7DCA8D7} 46 | EndGlobalSection 47 | EndGlobal 48 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | [*.cs] 3 | #### Benennungsstile #### 4 | 5 | # Benennungsregeln 6 | 7 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 8 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 9 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 10 | 11 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 12 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 13 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 14 | 15 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 16 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 17 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 18 | 19 | # Symbolspezifikationen 20 | 21 | dotnet_naming_symbols.interface.applicable_kinds = interface 22 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 23 | dotnet_naming_symbols.interface.required_modifiers = 24 | 25 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 26 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 27 | dotnet_naming_symbols.types.required_modifiers = 28 | 29 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 30 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 31 | dotnet_naming_symbols.non_field_members.required_modifiers = 32 | 33 | # Benennungsstile 34 | 35 | dotnet_naming_style.begins_with_i.required_prefix = I 36 | dotnet_naming_style.begins_with_i.required_suffix = 37 | dotnet_naming_style.begins_with_i.word_separator = 38 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 39 | 40 | dotnet_naming_style.pascal_case.required_prefix = 41 | dotnet_naming_style.pascal_case.required_suffix = 42 | dotnet_naming_style.pascal_case.word_separator = 43 | dotnet_naming_style.pascal_case.capitalization = pascal_case 44 | 45 | dotnet_naming_style.pascal_case.required_prefix = 46 | dotnet_naming_style.pascal_case.required_suffix = 47 | dotnet_naming_style.pascal_case.word_separator = 48 | dotnet_naming_style.pascal_case.capitalization = pascal_case 49 | csharp_space_around_binary_operators = before_and_after 50 | csharp_indent_labels = one_less_than_current 51 | csharp_style_expression_bodied_methods = false:silent 52 | csharp_style_expression_bodied_constructors = false:silent 53 | csharp_style_expression_bodied_operators = false:silent 54 | csharp_style_expression_bodied_properties = true:silent 55 | csharp_style_expression_bodied_indexers = true:silent 56 | csharp_style_expression_bodied_accessors = true:silent 57 | csharp_style_expression_bodied_lambdas = true:silent 58 | csharp_style_expression_bodied_local_functions = false:silent 59 | csharp_style_throw_expression = true:suggestion 60 | csharp_style_prefer_null_check_over_type_check = true:suggestion 61 | csharp_prefer_simple_default_expression = true:suggestion 62 | csharp_style_prefer_local_over_anonymous_function = true:suggestion 63 | csharp_style_prefer_index_operator = true:suggestion 64 | csharp_style_prefer_range_operator = true:suggestion 65 | csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion 66 | csharp_style_prefer_tuple_swap = true:suggestion 67 | csharp_style_prefer_utf8_string_literals = true:suggestion 68 | csharp_style_inlined_variable_declaration = true:suggestion 69 | csharp_style_deconstructed_variable_declaration = true:suggestion 70 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion 71 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent 72 | csharp_prefer_simple_using_statement = true:suggestion 73 | csharp_prefer_braces = true:silent 74 | csharp_style_namespace_declarations = block_scoped:silent 75 | csharp_style_prefer_method_group_conversion = true:silent 76 | csharp_style_prefer_top_level_statements = true:silent 77 | csharp_style_prefer_switch_expression = true:suggestion 78 | csharp_style_prefer_pattern_matching = true:silent 79 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 80 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 81 | csharp_style_prefer_not_pattern = true:suggestion 82 | csharp_style_prefer_extended_property_pattern = true:suggestion 83 | csharp_prefer_static_local_function = true:suggestion 84 | csharp_style_prefer_readonly_struct = true:suggestion 85 | csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent 86 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent 87 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent 88 | csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent 89 | csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent 90 | csharp_style_conditional_delegate_call = true:suggestion 91 | csharp_using_directive_placement = outside_namespace:silent 92 | csharp_style_var_for_built_in_types = false:silent 93 | csharp_style_var_when_type_is_apparent = false:silent 94 | csharp_style_var_elsewhere = false:silent 95 | csharp_new_line_before_open_brace = types,methods,anonymous_methods,control_blocks,anonymous_types,object_collection_array_initializers,lambdas 96 | 97 | [*.vb] 98 | #### Benennungsstile #### 99 | 100 | # Benennungsregeln 101 | 102 | dotnet_naming_rule.interface_should_be_beginnt_mit_i.severity = suggestion 103 | dotnet_naming_rule.interface_should_be_beginnt_mit_i.symbols = interface 104 | dotnet_naming_rule.interface_should_be_beginnt_mit_i.style = beginnt_mit_i 105 | 106 | dotnet_naming_rule.typen_should_be_pascal_schreibweise.severity = suggestion 107 | dotnet_naming_rule.typen_should_be_pascal_schreibweise.symbols = typen 108 | dotnet_naming_rule.typen_should_be_pascal_schreibweise.style = pascal_schreibweise 109 | 110 | dotnet_naming_rule.nicht_feldmember_should_be_pascal_schreibweise.severity = suggestion 111 | dotnet_naming_rule.nicht_feldmember_should_be_pascal_schreibweise.symbols = nicht_feldmember 112 | dotnet_naming_rule.nicht_feldmember_should_be_pascal_schreibweise.style = pascal_schreibweise 113 | 114 | # Symbolspezifikationen 115 | 116 | dotnet_naming_symbols.interface.applicable_kinds = interface 117 | dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected 118 | dotnet_naming_symbols.interface.required_modifiers = 119 | 120 | dotnet_naming_symbols.typen.applicable_kinds = class, struct, interface, enum 121 | dotnet_naming_symbols.typen.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected 122 | dotnet_naming_symbols.typen.required_modifiers = 123 | 124 | dotnet_naming_symbols.nicht_feldmember.applicable_kinds = property, event, method 125 | dotnet_naming_symbols.nicht_feldmember.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected 126 | dotnet_naming_symbols.nicht_feldmember.required_modifiers = 127 | 128 | # Benennungsstile 129 | 130 | dotnet_naming_style.beginnt_mit_i.required_prefix = I 131 | dotnet_naming_style.beginnt_mit_i.required_suffix = 132 | dotnet_naming_style.beginnt_mit_i.word_separator = 133 | dotnet_naming_style.beginnt_mit_i.capitalization = pascal_case 134 | 135 | dotnet_naming_style.pascal_schreibweise.required_prefix = 136 | dotnet_naming_style.pascal_schreibweise.required_suffix = 137 | dotnet_naming_style.pascal_schreibweise.word_separator = 138 | dotnet_naming_style.pascal_schreibweise.capitalization = pascal_case 139 | 140 | dotnet_naming_style.pascal_schreibweise.required_prefix = 141 | dotnet_naming_style.pascal_schreibweise.required_suffix = 142 | dotnet_naming_style.pascal_schreibweise.word_separator = 143 | dotnet_naming_style.pascal_schreibweise.capitalization = pascal_case 144 | 145 | [*.{cs,vb}] 146 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 147 | end_of_line = crlf 148 | dotnet_style_coalesce_expression = true:suggestion 149 | dotnet_style_null_propagation = true:suggestion 150 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 151 | dotnet_style_prefer_auto_properties = true:silent 152 | dotnet_style_object_initializer = true:suggestion 153 | dotnet_style_collection_initializer = true:suggestion 154 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 155 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 156 | dotnet_style_prefer_conditional_expression_over_return = true:silent 157 | dotnet_style_explicit_tuple_names = true:suggestion 158 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 159 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 160 | dotnet_style_prefer_compound_assignment = true:suggestion 161 | dotnet_style_prefer_simplified_interpolation = true:suggestion 162 | dotnet_style_namespace_match_folder = true:suggestion 163 | dotnet_style_readonly_field = true:suggestion 164 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 165 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 166 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 167 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 168 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 169 | dotnet_style_allow_multiple_blank_lines_experimental = true:silent 170 | dotnet_style_allow_statement_immediately_after_block_experimental = true:silent 171 | dotnet_code_quality_unused_parameters = all:suggestion 172 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent 173 | dotnet_style_predefined_type_for_member_access = true:silent 174 | dotnet_style_qualification_for_property = false:silent 175 | dotnet_style_qualification_for_field = false:silent 176 | dotnet_style_qualification_for_method = false:silent 177 | dotnet_style_qualification_for_event = false:silent 178 | tab_width = 4 179 | indent_size = 4 -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | // In Projekten im SDK-Stil wie dem vorliegenden, bei dem verschiedene Assemblyattribute 4 | // üblicherweise in dieser Datei definiert wurden, werden diese Attribute jetzt während 5 | // der Builderstellung automatisch hinzugefügt und mit Werten aufgefüllt, die in den 6 | // Projekteigenschaften definiert sind. Informationen dazu, welche Attribute einbezogen 7 | // werden und wie dieser Prozess angepasst werden kann, finden Sie unter https://aka.ms/assembly-info-properties. 8 | 9 | 10 | // Wenn "ComVisible" auf FALSE festgelegt wird, sind die Typen in dieser Assembly 11 | // für COM-Komponenten nicht sichtbar. Wenn Sie von COM aus auf einen Typ in dieser 12 | // Assembly zugreifen müssen, legen Sie das ComVisible-Attribut für den betreffenden 13 | // Typ auf TRUE fest. 14 | 15 | [assembly: ComVisible(false)] 16 | 17 | // Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM 18 | // bereitgestellt wird. 19 | 20 | [assembly: Guid("b364068a-74d7-4f7e-b966-e3e2cce15b1c")] 21 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/AuthStore.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Models; 2 | using System.Text; 3 | using System.Text.Json; 4 | 5 | namespace pocketbase_csharp_sdk 6 | { 7 | public class AuthStore 8 | { 9 | public event EventHandler? OnChange; 10 | 11 | public string? Token { get; set; } 12 | public IBaseModel? Model { get; set; } 13 | 14 | public bool IsValid { get => GetIsValid(); } 15 | 16 | private bool GetIsValid() 17 | { 18 | if (string.IsNullOrWhiteSpace(Token)) 19 | { 20 | return false; 21 | } 22 | 23 | var parts = Token.Split('.', StringSplitOptions.RemoveEmptyEntries); 24 | if (parts.Length != 3) 25 | { 26 | return false; 27 | } 28 | 29 | string rawPayload = parts[1]; 30 | string payload = Encoding.UTF8.GetString(ParsePayload(rawPayload)); 31 | var encoded = JsonSerializer.Deserialize>(payload)!; 32 | 33 | if (encoded["exp"] is JsonElement jsonElement && jsonElement.ValueKind == JsonValueKind.Number) 34 | { 35 | var exp = jsonElement.GetInt32(); 36 | var expiredAt = DateTimeOffset.FromUnixTimeSeconds(exp); 37 | return expiredAt > DateTimeOffset.Now; 38 | } 39 | 40 | return false; 41 | } 42 | 43 | private byte[] ParsePayload(string payload) 44 | { 45 | switch (payload.Length % 4) 46 | { 47 | case 2: 48 | payload += "=="; 49 | break; 50 | case 3: 51 | payload += "="; 52 | break; 53 | } 54 | 55 | return Convert.FromBase64String(payload); 56 | } 57 | 58 | public void Save(string? token, IBaseModel? model) 59 | { 60 | this.Token = token; 61 | this.Model = model; 62 | 63 | OnChange?.Invoke(this, new AuthStoreEvent(this.Token, this.Model)); 64 | } 65 | 66 | public void Clear() 67 | { 68 | this.Token = null; 69 | this.Model = null; 70 | 71 | OnChange?.Invoke(this, new AuthStoreEvent(this.Token, this.Model)); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/AuthStoreEvent.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Models; 2 | 3 | namespace pocketbase_csharp_sdk 4 | { 5 | public class AuthStoreEvent 6 | { 7 | 8 | public string? Token { get; private set; } 9 | public IBaseModel? Model { get; private set; } 10 | 11 | public AuthStoreEvent(string? token, IBaseModel? model) 12 | { 13 | Token = token; 14 | Model = model; 15 | } 16 | 17 | public override string ToString() 18 | { 19 | return $"token: {Token}{Environment.NewLine}model: {Model}"; 20 | } 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Class Diagrams/Services.cd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= 7 | Services\AdminService.cs 8 | 9 | 10 | 11 | 12 | 13 | BAAAAAAIAAIAEAAAACAAAAAAIAAAAAAAAAAAAAAAAAA= 14 | Services\BaseService.cs 15 | 16 | 17 | 18 | 19 | 20 | AAAAAAAAAAAAAAIAAAAAAAAAAAAQIAAAAAAABAAAAAI= 21 | Services\BaseCrudService.cs 22 | 23 | 24 | 25 | 26 | 27 | ACQAAAAAAAAAAAIAAAAAEAIAAAASIEgAAAAABIAAgAI= 28 | Services\BaseSubCrudService.cs 29 | 30 | 31 | 32 | 33 | 34 | FAAAAAAgAAAAAAgAAAAAAACAAQAAAAAAAIAABAAAAAA= 35 | Services\LogService.cs 36 | 37 | 38 | 39 | 40 | 41 | BAUAAAAAAAAAAAAAAAIAAAAAABABAAgAggAABAAAAAA= 42 | Services\SettingsService.cs 43 | 44 | 45 | 46 | 47 | 48 | BAAAAAAAAAAAAAAAAAAAAAAAAACEAAAAAAAABAAAAAA= 49 | Services\RecordService.cs 50 | 51 | 52 | 53 | 54 | 55 | BAAAAAAAAAAEAAAAIAAAAAAAAAEAAAAAAAAABAQAAIg= 56 | Services\CollectionService.cs 57 | 58 | 59 | 60 | 61 | 62 | RAXAAAAAACLACAIgAAgJABAAAAAQIUigAAFEBIBgAAY= 63 | Services\UserService.cs 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/ClientException.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk 2 | { 3 | public class ClientException : Exception 4 | { 5 | 6 | /// 7 | /// The Url of the failed request 8 | /// 9 | public string Url { get; } 10 | 11 | /// 12 | /// Indicates whether the error is a result from request cancellation/abort 13 | /// 14 | public bool IsAbort { get; } 15 | 16 | /// 17 | /// The status code of the failed request 18 | /// 19 | public int StatusCode { get; } 20 | 21 | /// 22 | /// Contains the JSON API error response 23 | /// 24 | public IDictionary? Response { get; } 25 | 26 | /// 27 | /// The original response error 28 | /// 29 | public Exception? OriginalError { get; } 30 | 31 | public ClientException(string url, bool isAbort = false, int statusCode = 500, IDictionary? response = null, Exception? originalError = null) 32 | { 33 | Url = url; 34 | IsAbort = isAbort; 35 | StatusCode = statusCode; 36 | Response = response; 37 | OriginalError = originalError; 38 | } 39 | 40 | public override string Message => FormatMessage(); 41 | 42 | private string FormatMessage() 43 | { 44 | Dictionary result = new() 45 | { 46 | {"url", Url }, 47 | {"isAbort", IsAbort }, 48 | {"statusCode", StatusCode }, 49 | {"response", Response }, 50 | {"originalError", OriginalError }, 51 | }; 52 | 53 | return $"ClientException: {result}"; 54 | } 55 | 56 | public override string ToString() 57 | { 58 | return $"ClientException: {FormatMessage()}"; 59 | } 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Errors/ClientError.cs: -------------------------------------------------------------------------------- 1 | using FluentResults; 2 | 3 | namespace pocketbase_csharp_sdk.Errors 4 | { 5 | public class ClientError : Error 6 | { 7 | public ClientError(HttpMethod method, string url, int statusCode) : base($"{method}request to {url} resulted in {statusCode}") 8 | { 9 | Metadata.Add("Method", method); 10 | Metadata.Add("URL", url); 11 | Metadata.Add("Statuscode", statusCode); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Event/RequestEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Event 2 | { 3 | public class RequestEventArgs : EventArgs 4 | { 5 | 6 | public Uri Url { get; } 7 | public HttpRequestMessage HttpRequest { get; } 8 | 9 | public RequestEventArgs(Uri url, HttpRequestMessage httpRequest) 10 | { 11 | Url = url; 12 | HttpRequest = httpRequest; 13 | } 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Event/ResponseEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Event 2 | { 3 | public class ResponseEventArgs : EventArgs 4 | { 5 | 6 | public Uri Url { get; } 7 | public HttpResponseMessage? HttpResponse { get; } 8 | 9 | public ResponseEventArgs(Uri url, HttpResponseMessage? httpResponse) 10 | { 11 | Url = url; 12 | HttpResponse = httpResponse; 13 | } 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Extensions/PocketBaseExtensions.cs: -------------------------------------------------------------------------------- 1 | using FluentResults; 2 | using pocketbase_csharp_sdk.Models; 3 | 4 | namespace pocketbase_csharp_sdk.Extensions 5 | { 6 | public static class PocketBaseExtensions 7 | { 8 | 9 | public static Result GetCurrentUser(this PocketBase pocketBase) 10 | { 11 | if (pocketBase.AuthStore.Model is null || string.IsNullOrWhiteSpace(pocketBase.AuthStore.Model.Id)) 12 | { 13 | return Result.Fail("AuthStore.Model is null or AuthStore.Model.Id is null"); 14 | } 15 | 16 | return pocketBase.User.GetOne(pocketBase.AuthStore.Model.Id); 17 | } 18 | 19 | public static Task> GetCurrentUserAsync(this PocketBase pocketBase) 20 | { 21 | if (pocketBase.AuthStore.Model is null || string.IsNullOrWhiteSpace(pocketBase.AuthStore.Model.Id)) 22 | { 23 | var fail = Result.Fail($"AuthStore.Model is null or AuthStore.Model.Id is null"); 24 | return Task.FromResult>(fail); 25 | } 26 | 27 | return pocketBase.User.GetOneAsync(pocketBase.AuthStore.Model.Id); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Helper/Convert/SafeConvert.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Helper.Convert 2 | { 3 | public static class SafeConvert 4 | { 5 | public static int ToInt(this object? obj, int defaultValue = 0) 6 | { 7 | if (obj is null) 8 | { 9 | return defaultValue; 10 | } 11 | 12 | if (int.TryParse(obj.ToString(), out var result)) 13 | { 14 | return result; 15 | } 16 | 17 | return defaultValue; 18 | } 19 | 20 | public static string ToString(this object? obj, string defaultValue = "") 21 | { 22 | if (obj is null) 23 | { 24 | return defaultValue; 25 | } 26 | 27 | return obj.ToString()!; 28 | } 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Helper/Extensions/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Helper.Extensions 2 | { 3 | internal static class DictionaryExtensions 4 | { 5 | public static void AddIfNotNull(this IDictionary dictionary, TKey key, TValue? value) 6 | { 7 | if (value is null) 8 | { 9 | return; 10 | } 11 | dictionary[key] = value; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Json/DateTimeConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace pocketbase_csharp_sdk.Json 5 | { 6 | public class DateTimeConverter : JsonConverter 7 | { 8 | public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 9 | { 10 | var value = reader.GetString(); 11 | if (!DateTime.TryParse(value, out var dt)) 12 | { 13 | return null; 14 | } 15 | 16 | return DateTime.SpecifyKind(dt, DateTimeKind.Utc); ; 17 | } 18 | 19 | public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) 20 | { 21 | if (value is null) 22 | { 23 | writer.WriteNullValue(); 24 | } 25 | else 26 | { 27 | writer.WriteStringValue(value?.ToString()); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/AdminModel.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace pocketbase_csharp_sdk.Models 4 | { 5 | public class AdminModel : BaseModel 6 | { 7 | [JsonPropertyName("email")] 8 | public string? Email { get; set; } 9 | 10 | [JsonPropertyName("avatar")] 11 | public int? Avatar { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/ApiHealthModel.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Models 2 | { 3 | public class ApiHealthModel 4 | { 5 | public int? Code { get; set; } 6 | public string? Message { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/Auth/AdminAuthModel.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace pocketbase_csharp_sdk.Models.Auth 4 | { 5 | public class AdminAuthModel : AuthModel 6 | { 7 | [JsonIgnore] 8 | public override IBaseModel? Model => Admin; 9 | 10 | 11 | [JsonPropertyName("admin")] 12 | public AdminModel? Admin { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/Auth/AuthModel.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace pocketbase_csharp_sdk.Models.Auth 4 | { 5 | public abstract class AuthModel 6 | { 7 | public string? Token { get; set; } 8 | 9 | [JsonIgnore] 10 | public abstract IBaseModel? Model { get; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/Auth/RecordAuthModel.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace pocketbase_csharp_sdk.Models.Auth 4 | { 5 | public class RecordAuthModel : AuthModel where T : IBaseAuthModel 6 | { 7 | [JsonIgnore] 8 | public override IBaseModel? Model => Record; 9 | 10 | [JsonPropertyName("record")] 11 | public T? Record { get; set; } 12 | 13 | public IDictionary? meta { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/Auth/UserAuthModel.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Models.Auth 2 | { 3 | public class UserAuthModel : RecordAuthModel 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/BackupModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.Json.Serialization; 6 | using System.Threading.Tasks; 7 | 8 | namespace pocketbase_csharp_sdk.Models 9 | { 10 | public class BackupModel : BaseModel 11 | { 12 | [JsonPropertyName("key")] 13 | public string? Key { get; set; } 14 | 15 | [JsonPropertyName("size")] 16 | public int? Size { get; set; } 17 | 18 | [JsonPropertyName("modified")] 19 | public DateTime? Modified { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/BaseAuthModel.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace pocketbase_csharp_sdk.Models 4 | { 5 | public class BaseAuthModel : BaseModel, IBaseAuthModel 6 | { 7 | [JsonPropertyName("email")] 8 | public string? Email { get; set; } 9 | 10 | [JsonPropertyName("emailVisibility")] 11 | public bool? EmailVisibility { get; set; } 12 | 13 | [JsonPropertyName("username")] 14 | public string? Username { get; set; } 15 | 16 | [JsonPropertyName("verified")] 17 | public bool? Verified { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/BaseModel.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Json; 2 | using System.Text.Json; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace pocketbase_csharp_sdk.Models 6 | { 7 | public abstract class BaseModel : IBaseModel 8 | { 9 | [JsonPropertyName("id")] 10 | public virtual string? Id { get; set; } 11 | 12 | [JsonPropertyName("created")] 13 | [JsonConverter(typeof(DateTimeConverter))] 14 | public virtual DateTime? Created { get; set; } 15 | 16 | [JsonPropertyName("updated")] 17 | [JsonConverter(typeof(DateTimeConverter))] 18 | public virtual DateTime? Updated { get; set; } 19 | 20 | [JsonPropertyName("collectionId")] 21 | public virtual string? CollectionId { get; set; } 22 | 23 | [JsonPropertyName("collectionName")] 24 | public virtual string? CollectionName { get; set; } 25 | 26 | public override string ToString() 27 | { 28 | return JsonSerializer.Serialize(this); 29 | } 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/Collection/CollectionModel.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace pocketbase_csharp_sdk.Models.Collection 5 | { 6 | public class CollectionModel 7 | { 8 | public string? Id { get; set; } 9 | 10 | [JsonConverter(typeof(DateTimeConverter))] 11 | public DateTime? Created { get; set; } 12 | [JsonConverter(typeof(DateTimeConverter))] 13 | public DateTime? Updated { get; set; } 14 | 15 | public string? Name { get; set; } 16 | public bool? System { get; set; } 17 | public string? Type { get; set; } 18 | 19 | public string? ListRule { get; set; } 20 | public string? ViewRule { get; set; } 21 | public string? CreateRule { get; set; } 22 | public string? UpdateRule { get; set; } 23 | public string? DeleteRule { get; set; } 24 | 25 | public CollectionOptionsModel? Options { get; set; } 26 | public IEnumerable? Schema { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/Collection/CollectionOptionsModel.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Models.Collection 2 | { 3 | public class CollectionOptionsModel 4 | { 5 | public bool? AllowEmailAuth { get; set; } 6 | public bool? AllowOAuth2Auth { get; set; } 7 | public bool? AllowUsernameAuth { get; set; } 8 | public bool? RequireEmail { get; set; } 9 | public int? MinPasswordLength { get; set; } 10 | 11 | public string? ExceptEmailDomains { get; set; } 12 | public string? OnlyEmailDomains { get; set; } 13 | 14 | public string? ManageRule { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/Collection/SchemaFieldModel.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Models.Collection 2 | { 3 | public class SchemaFieldModel 4 | { 5 | public string? Id { get; set; } 6 | public string? Name { get; set; } 7 | public string? Type { get; set; } 8 | public bool? System { get; set; } 9 | public bool? Required { get; set; } 10 | public bool? Unique { get; set; } 11 | public IDictionary? Options { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/Enum/ThumbFormat.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Enum 2 | { 3 | public enum ThumbFormat 4 | { 5 | CropToWxHFromCenter, 6 | CropToWxHFromTop, 7 | CropToWxHFromBottom, 8 | FitInsideWxHViewbox, 9 | ResizeToHeight, 10 | ResizeToWidth 11 | } 12 | 13 | internal static class ThumbFormatHelper 14 | { 15 | 16 | public static string GetNameForQuery(ThumbFormat? thumbFormat) 17 | { 18 | return thumbFormat switch 19 | { 20 | ThumbFormat.CropToWxHFromCenter => "WxH", 21 | ThumbFormat.CropToWxHFromTop => "WxT", 22 | ThumbFormat.CropToWxHFromBottom => "WxB", 23 | ThumbFormat.FitInsideWxHViewbox => "WxHf", 24 | ThumbFormat.ResizeToHeight => "0xH", 25 | ThumbFormat.ResizeToWidth => "Wx0", 26 | _ => string.Empty 27 | }; 28 | } 29 | 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/ExternalAuthModel.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace pocketbase_csharp_sdk.Models 5 | { 6 | public class ExternalAuthModel 7 | { 8 | public string? Id { get; set; } 9 | 10 | [JsonConverter(typeof(DateTimeConverter))] 11 | public DateTime? Created { get; set; } 12 | 13 | [JsonConverter(typeof(DateTimeConverter))] 14 | public DateTime? Updated { get; set; } 15 | 16 | public string? RecordId { get; set; } 17 | public string? CollectionId { get; set; } 18 | public string? Provider { get; set; } 19 | public string? ProviderId { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/Files/BaseFile.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Models.Files 2 | { 3 | public abstract class BaseFile 4 | { 5 | public string? FieldName { get; set; } 6 | public string? FileName { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/Files/FilepathFile.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Models.Files 2 | { 3 | 4 | /// 5 | /// simple class for uploading files to PocketBase, accepting a path to a file 6 | /// 7 | public class FilepathFile : BaseFile, IFile 8 | { 9 | public string? FilePath { get; set; } 10 | 11 | public Stream? GetStream() 12 | { 13 | 14 | if (string.IsNullOrWhiteSpace(FilePath)) 15 | { 16 | return null; 17 | } 18 | 19 | try 20 | { 21 | return File.OpenRead(FilePath); 22 | } 23 | catch 24 | { 25 | return null; 26 | } 27 | } 28 | 29 | public FilepathFile() 30 | { 31 | 32 | } 33 | 34 | public FilepathFile(string? filePath) 35 | { 36 | this.FilePath = filePath; 37 | } 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/Files/IFile.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Models.Files 2 | { 3 | 4 | /// 5 | /// simple Interface needed for uploading files to PocketBase 6 | /// 7 | public interface IFile 8 | { 9 | public string? FieldName { get; set; } 10 | public string? FileName { get; set; } 11 | public Stream? GetStream(); 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/Files/StreamFile.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Models.Files 2 | { 3 | 4 | /// 5 | /// simple class for uploading files to PocketBase, accepting a Stream 6 | /// 7 | public class StreamFile : BaseFile, IFile 8 | { 9 | public Stream? Stream { get; set; } 10 | 11 | public Stream? GetStream() 12 | { 13 | return Stream; 14 | } 15 | 16 | public StreamFile() 17 | { 18 | 19 | } 20 | 21 | public StreamFile(Stream? stream) 22 | { 23 | this.Stream = stream; 24 | } 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/IBaseAuthModel.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Models 2 | { 3 | public interface IBaseAuthModel : IBaseModel 4 | { 5 | string? Email { get; } 6 | 7 | bool? EmailVisibility { get; } 8 | 9 | string? Username { get; } 10 | 11 | bool? Verified { get; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/IBaseModel.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Models 2 | { 3 | public interface IBaseModel 4 | { 5 | string? Id { get; } 6 | 7 | DateTime? Created { get; } 8 | 9 | DateTime? Updated { get; } 10 | 11 | string? CollectionId { get; } 12 | 13 | string? CollectionName { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/Log/AuthMethodProvider.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Models.Log 2 | { 3 | public class AuthMethodProvider 4 | { 5 | public string? Name { get; set; } 6 | public string? State { get; set; } 7 | public string? CodeVerifier { get; set; } 8 | public string? CodeChallenge { get; set; } 9 | public string? CodeChallengeMethod { get; set; } 10 | public string? AuthUrl { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/Log/AuthMethodsList.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Models.Log 2 | { 3 | public class AuthMethodsList 4 | { 5 | public bool? UsernamePassword { get; set; } 6 | public bool? EmailPassword { get; set; } 7 | public IEnumerable? AuthProviders { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/Log/LogRequestModel.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace pocketbase_csharp_sdk.Models.Log 5 | { 6 | public class LogRequestModel 7 | { 8 | public string? Id { get; set; } 9 | 10 | [JsonConverter(typeof(DateTimeConverter))] 11 | public DateTime? Created { get; set; } 12 | 13 | [JsonConverter(typeof(DateTimeConverter))] 14 | public DateTime? Updated { get; set; } 15 | 16 | public string? Url { get; set; } 17 | public string? Method { get; set; } 18 | public int? Status { get; set; } 19 | public string? Auth { get; set; } 20 | public string? RemoteIP { get; set; } 21 | public string? UserIP { get; set; } 22 | public string? Referer { get; set; } 23 | public string? UserAgent { get; set; } 24 | public IDictionary? Meta { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/Log/LogRequestStatistic.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace pocketbase_csharp_sdk.Models.Log 5 | { 6 | public class LogRequestStatistic 7 | { 8 | public int? Total { get; set; } 9 | 10 | [JsonConverter(typeof(DateTimeConverter))] 11 | public DateTime? Date { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/PagedCollectionModel.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Models 2 | { 3 | public class PagedCollectionModel 4 | { 5 | public int Page { get; set; } 6 | public int PerPage { get; set; } 7 | public int TotalItems { get; set; } 8 | public ICollection? Items { get; set; } 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/ResultList.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Models 2 | { 3 | public class ResultList where T : class 4 | { 5 | public int? Page { get; set; } 6 | public int? PerPage { get; set; } 7 | public int? TotalItems { get; set; } 8 | public int? TotalPages { get; set; } 9 | public IEnumerable? Items { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/SseMessage.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Helper.Convert; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace pocketbase_csharp_sdk.Models 5 | { 6 | /// 7 | /// A Server-Sent message 8 | /// 9 | /// 10 | public class SseMessage 11 | { 12 | /// 13 | /// The event ID 14 | /// 15 | public string? Id { get; private set; } 16 | 17 | /// 18 | /// A string identifying the type of event described 19 | /// 20 | public string? Event { get; private set; } 21 | 22 | /// 23 | /// The data field for the message. 24 | /// When the receives multiple consecutive lines that begin with data:, 25 | /// it concatenates them, inserting a newline character between each one. 26 | /// Trailing newlines are removed. 27 | /// 28 | public string? Data { get; private set; } 29 | 30 | /// 31 | /// The reconnection time. 32 | /// If the connection to the server is lost, the browser will wait 33 | /// for the specified time before attempting to reconnect. 34 | /// This must be an integer, specifying the reconnection time in milliseconds. 35 | /// If a non-integer value is specified, the field is ignored. 36 | /// 37 | public int? Retry { get; private set; } 38 | 39 | public override string ToString() 40 | { 41 | return $"Id:{Id}{Environment.NewLine}Event:{Event}{Environment.NewLine}Data:{Data}{Environment.NewLine}Retry:{Retry}"; 42 | } 43 | 44 | /// 45 | /// Factory for the SseMessage from received message 46 | /// 47 | /// 48 | /// 49 | public static async Task FromReceivedMessageAsync(string? receivedMessage) 50 | { 51 | if (receivedMessage == null) 52 | return null; 53 | var message = new SseMessage(); 54 | string? line; 55 | using (var stringReader = new StringReader(receivedMessage)) 56 | { 57 | while ((line = await stringReader.ReadLineAsync()) != null) 58 | { 59 | if (line.StartsWith("id:")) 60 | message.Id = line["id:".Length..].Trim(); 61 | else if (line.StartsWith("event:")) 62 | message.Event = line["event:".Length..].Trim(); 63 | else if (line.StartsWith("retry:")) 64 | message.Retry = SafeConvert.ToInt(line["retry:".Length..].Trim()); 65 | else if (line.StartsWith("data:")) 66 | { 67 | // PocketBase returns multiple datas? 68 | // If true, every data is a Json? 69 | // -> then it must be stored in a list of strings 70 | var data = line["data:".Length..].Trim(); 71 | if (message.Data == null) 72 | message.Data = data; 73 | else 74 | message.Data += Environment.NewLine + data; 75 | } 76 | } 77 | } 78 | return message; 79 | } 80 | 81 | private static bool ProcessMessage(string? line, SseMessage message) 82 | { 83 | Regex regex = new Regex("^(\\w+)[\\s\\:]+(.*)?$"); 84 | if (string.IsNullOrWhiteSpace(line)) 85 | { 86 | return true; 87 | } 88 | 89 | var match = regex.Match(line); 90 | if (match is null) 91 | { 92 | return false; 93 | } 94 | 95 | var field = match.Groups[1].Value ?? ""; 96 | var value = match.Groups[2].Value ?? ""; 97 | 98 | switch (field) 99 | { 100 | case "id": 101 | message.Id = value; 102 | break; 103 | case "event": 104 | message.Event = value; 105 | break; 106 | case "retry": 107 | message.Retry = SafeConvert.ToInt(value, 0); 108 | break; 109 | case "data": 110 | message.Data = value; 111 | break; 112 | } 113 | 114 | return false; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Models/UserModel.cs: -------------------------------------------------------------------------------- 1 | namespace pocketbase_csharp_sdk.Models 2 | { 3 | public class UserModel : BaseAuthModel 4 | { 5 | public string? Avatar { get; set; } 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/PocketBase.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Event; 2 | using pocketbase_csharp_sdk.Models; 3 | using pocketbase_csharp_sdk.Models.Files; 4 | using pocketbase_csharp_sdk.Services; 5 | using System.Collections; 6 | using System.Net.Http.Headers; 7 | using System.Net.Http.Json; 8 | using System.Text.Json; 9 | using System.Web; 10 | using FluentResults; 11 | using pocketbase_csharp_sdk.Errors; 12 | 13 | namespace pocketbase_csharp_sdk 14 | { 15 | public class PocketBase 16 | { 17 | #region Private Fields 18 | private readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(JsonSerializerDefaults.Web); 19 | private Dictionary recordServices = new Dictionary(); 20 | #endregion 21 | 22 | #region Events 23 | public delegate HttpRequestMessage BeforeSendEventHandler(object sender, RequestEventArgs e); 24 | public event BeforeSendEventHandler? BeforeSend; 25 | 26 | public delegate void AfterSendEventHandler(object sender, ResponseEventArgs e); 27 | public event AfterSendEventHandler? AfterSend; 28 | #endregion 29 | 30 | 31 | public AuthStore AuthStore { private set; get; } 32 | public AdminService Admin { private set; get; } 33 | public UserService User { private set; get; } 34 | public LogService Log { private set; get; } 35 | public SettingsService Settings { private set; get; } 36 | public CollectionService Collections { private set; get; } 37 | //public RecordService Records { private set; get; } 38 | public RealTimeService RealTime { private set; get; } 39 | public HealthService Health { private set; get; } 40 | public BackupService Backup { private set; get; } 41 | 42 | private readonly string _baseUrl; 43 | private readonly string _language; 44 | private readonly HttpClient _httpClient; 45 | 46 | public PocketBase(string baseUrl, AuthStore? authStore = null, string language = "en-US", HttpClient? httpClient = null) 47 | { 48 | this._baseUrl = baseUrl; 49 | this._language = language; 50 | this._httpClient = httpClient ?? new HttpClient(); 51 | 52 | AuthStore = authStore ?? new AuthStore(); 53 | Admin = new AdminService(this); 54 | User = new UserService(this); 55 | Log = new LogService(this); 56 | Settings = new SettingsService(this); 57 | Collections = new CollectionService(this); 58 | //Records = new RecordService(this); 59 | RealTime = new RealTimeService(this); 60 | Health = new HealthService(this); 61 | Backup = new BackupService(this); 62 | } 63 | 64 | public CollectionAuthService AuthCollection(string collectionName) 65 | where T : IBaseAuthModel 66 | { 67 | return new CollectionAuthService(this, collectionName); 68 | } 69 | 70 | public RecordService Collection(string collectionName) 71 | { 72 | if (recordServices.ContainsKey(collectionName)) 73 | { 74 | return recordServices[collectionName]; 75 | } 76 | var newService = new RecordService(this, collectionName); 77 | recordServices[collectionName] = newService; 78 | return newService; 79 | } 80 | 81 | public async Task SendAsync(string path, HttpMethod method, IDictionary? headers = null, IDictionary? query = null, IDictionary? body = null, IEnumerable? files = null, CancellationToken cancellationToken = default) 82 | { 83 | headers ??= new Dictionary(); 84 | query ??= new Dictionary(); 85 | body ??= new Dictionary(); 86 | files ??= new List(); 87 | 88 | Uri url = BuildUrl(path, query); 89 | 90 | HttpRequestMessage request = CreateRequest(url, method, headers: headers, query: query, body: body, files: files); 91 | 92 | try 93 | { 94 | if (BeforeSend is not null) 95 | { 96 | request = BeforeSend.Invoke(this, new RequestEventArgs(url, request)); 97 | } 98 | 99 | var response = await _httpClient.SendAsync(request, cancellationToken); 100 | 101 | if (AfterSend is not null) 102 | { 103 | AfterSend.Invoke(this, new ResponseEventArgs(url, response)); 104 | } 105 | 106 | #if DEBUG 107 | var json = await response.Content.ReadAsStringAsync(cancellationToken); 108 | #endif 109 | 110 | if ((int)response.StatusCode >= 400) 111 | { 112 | var error = new ClientError(method, url.ToString(), (int)response.StatusCode); 113 | return Result.Fail(error); 114 | } 115 | 116 | return Result.Ok(); 117 | } 118 | catch (Exception ex) 119 | { 120 | if (ex is HttpRequestException requestException) 121 | { 122 | ClientError error = new ClientError(method, url.ToString(), (int)requestException.StatusCode!); 123 | return Result.Fail(error); 124 | } 125 | 126 | return Result.Fail(new Error(ex.Message)); 127 | } 128 | } 129 | 130 | public Result Send(string path, HttpMethod method, IDictionary? headers = null, IDictionary? query = null, IDictionary? body = null, IEnumerable? files = null, CancellationToken cancellationToken = default) 131 | { 132 | //RETURN RESULT 133 | 134 | headers ??= new Dictionary(); 135 | query ??= new Dictionary(); 136 | body ??= new Dictionary(); 137 | files ??= new List(); 138 | 139 | Uri url = BuildUrl(path, query); 140 | 141 | HttpRequestMessage request = CreateRequest(url, method, headers: headers, query: query, body: body, files: files); 142 | 143 | try 144 | { 145 | if (BeforeSend is not null) 146 | { 147 | request = BeforeSend.Invoke(this, new RequestEventArgs(url, request)); 148 | } 149 | 150 | var response = _httpClient.Send(request, cancellationToken); 151 | 152 | if (AfterSend is not null) 153 | { 154 | AfterSend.Invoke(this, new ResponseEventArgs(url, response)); 155 | } 156 | 157 | if ((int)response.StatusCode >= 400) 158 | { 159 | var error = new ClientError(method, url.ToString(), (int)response.StatusCode); 160 | return Result.Fail(error); 161 | } 162 | 163 | return Result.Ok(); 164 | } 165 | catch (Exception ex) 166 | { 167 | if (ex is HttpRequestException requestException) 168 | { 169 | ClientError error = new ClientError(method, url.ToString(), (int)requestException.StatusCode!); 170 | return Result.Fail(error); 171 | } 172 | 173 | return Result.Fail(new Error(ex.Message)); 174 | } 175 | } 176 | 177 | public async Task> SendAsync(string path, HttpMethod method, IDictionary? headers = null, IDictionary? query = null, IDictionary? body = null, IEnumerable? files = null, CancellationToken cancellationToken = default) 178 | { 179 | headers ??= new Dictionary(); 180 | query ??= new Dictionary(); 181 | body ??= new Dictionary(); 182 | files ??= new List(); 183 | 184 | Uri url = BuildUrl(path, query); 185 | 186 | HttpRequestMessage request = CreateRequest(url, method, headers: headers, query: query, body: body, files: files); 187 | 188 | try 189 | { 190 | if (BeforeSend is not null) 191 | { 192 | request = BeforeSend.Invoke(this, new RequestEventArgs(url, request)); 193 | } 194 | 195 | var response = await _httpClient.SendAsync(request, cancellationToken); 196 | 197 | if (AfterSend is not null) 198 | { 199 | AfterSend.Invoke(this, new ResponseEventArgs(url, response)); 200 | } 201 | 202 | #if DEBUG 203 | var json = await response.Content.ReadAsStringAsync(cancellationToken); 204 | #endif 205 | 206 | if ((int)response.StatusCode >= 400) 207 | { 208 | ClientError error = new ClientError(method, url.ToString(), (int)response.StatusCode); 209 | return Result.Fail(error); 210 | } 211 | 212 | var parsedResponse = 213 | await response.Content.ReadFromJsonAsync(jsonSerializerOptions, cancellationToken); 214 | return Result.Ok(parsedResponse!); 215 | } 216 | catch (Exception ex) 217 | { 218 | if (ex is HttpRequestException requestException) 219 | { 220 | ClientError error = new ClientError(method, url.ToString(), (int)requestException.StatusCode!); 221 | return Result.Fail(error); 222 | } 223 | 224 | return Result.Fail(new Error(ex.Message)); 225 | } 226 | } 227 | 228 | public Result Send(string path, HttpMethod method, IDictionary? headers = null, IDictionary? query = null, IDictionary? body = null, IEnumerable? files = null, CancellationToken cancellationToken = default) 229 | { 230 | headers ??= new Dictionary(); 231 | query ??= new Dictionary(); 232 | body ??= new Dictionary(); 233 | files ??= new List(); 234 | 235 | Uri url = BuildUrl(path, query); 236 | 237 | HttpRequestMessage request = CreateRequest(url, method, headers: headers, query: query, body: body, files: files); 238 | 239 | try 240 | { 241 | if (BeforeSend is not null) 242 | { 243 | request = BeforeSend.Invoke(this, new RequestEventArgs(url, request)); 244 | } 245 | 246 | var response = _httpClient.Send(request, cancellationToken); 247 | 248 | if (AfterSend is not null) 249 | { 250 | AfterSend.Invoke(this, new ResponseEventArgs(url, response)); 251 | } 252 | 253 | if ((int)response.StatusCode >= 400) 254 | { 255 | ClientError error = new ClientError(method, url.ToString(), (int)response.StatusCode); 256 | return Result.Fail(error); 257 | } 258 | 259 | using var stream = response.Content.ReadAsStream(); 260 | var parsedResponse = JsonSerializer.Deserialize(stream, jsonSerializerOptions); 261 | return Result.Ok(parsedResponse!); 262 | } 263 | catch (Exception ex) 264 | { 265 | if (ex is HttpRequestException requestException) 266 | { 267 | ClientError error = new ClientError(method, url.ToString(), (int)requestException.StatusCode!); 268 | return Result.Fail(error); 269 | } 270 | 271 | return Result.Fail(new Error(ex.Message)); 272 | } 273 | } 274 | 275 | public async Task> GetStreamAsync(string path, IDictionary? query = null, CancellationToken cancellationToken = default) 276 | { 277 | query ??= new Dictionary(); 278 | 279 | Uri url = BuildUrl(path, query); 280 | 281 | try 282 | { 283 | var stream = await _httpClient.GetStreamAsync(url, cancellationToken); 284 | return Result.Ok(stream); 285 | } 286 | catch (Exception ex) 287 | { 288 | if (ex is HttpRequestException requestException) 289 | { 290 | ClientError error = new ClientError(HttpMethod.Get, url.ToString(), (int)requestException.StatusCode!); 291 | return Result.Fail(error); 292 | } 293 | 294 | return Result.Fail(new Error(ex.Message)); 295 | } 296 | } 297 | 298 | private HttpRequestMessage CreateRequest(Uri url, HttpMethod method, IDictionary headers, IDictionary query, IDictionary body, IEnumerable files) 299 | { 300 | HttpRequestMessage request; 301 | 302 | if (files.Any()) 303 | { 304 | request = BuildFileRequest(method, url, headers, body, files); 305 | } 306 | else 307 | { 308 | request = BuildJsonRequest(method, url, headers, body); 309 | } 310 | 311 | if (!headers.ContainsKey("Authorization") && AuthStore.IsValid) 312 | { 313 | request.Headers.Add("Authorization", AuthStore.Token); 314 | } 315 | 316 | if (!headers.ContainsKey("Accept-Language")) 317 | { 318 | request.Headers.Add("Accept-Language", _language); 319 | } 320 | 321 | return request; 322 | } 323 | 324 | public Uri BuildUrl(string path, IDictionary? queryParameters = null) 325 | { 326 | var url = _baseUrl + (_baseUrl.EndsWith("/") ? "" : "/"); 327 | 328 | if (!string.IsNullOrWhiteSpace(path)) 329 | { 330 | url += path.StartsWith("/") ? path.Substring(1) : path; 331 | } 332 | 333 | if (queryParameters is not null) 334 | { 335 | var query = NormalizeQueryParameters(queryParameters); 336 | 337 | List urlSegments = new(); 338 | foreach (var kvp in query) 339 | { 340 | var encodedKey = HttpUtility.UrlEncode(kvp.Key); 341 | foreach (var item in kvp.Value) 342 | { 343 | var encodedValue = HttpUtility.UrlEncode(item.ToString()); 344 | urlSegments.Add($"{encodedKey}={encodedValue}"); 345 | } 346 | } 347 | 348 | var queryString = string.Join("&", urlSegments); 349 | 350 | if (!string.IsNullOrWhiteSpace(queryString)) 351 | { 352 | url = url + "?" + queryString; 353 | } 354 | } 355 | 356 | return new Uri(url, UriKind.RelativeOrAbsolute); 357 | } 358 | 359 | private IDictionary NormalizeQueryParameters(IDictionary? parameters) 360 | { 361 | Dictionary result = new(); 362 | 363 | if (parameters is null) 364 | { 365 | return result; 366 | } 367 | 368 | foreach (var item in parameters) 369 | { 370 | List normalizedValue = new(); 371 | IEnumerable valueAsList; 372 | 373 | if (item.Value is IEnumerable && item.Value is not string) 374 | { 375 | valueAsList = (IEnumerable)item.Value; 376 | } 377 | else 378 | { 379 | valueAsList = new List() { item.Value }; 380 | } 381 | 382 | foreach (var subItem in valueAsList) 383 | { 384 | if (subItem is null) 385 | { 386 | continue; 387 | } 388 | normalizedValue.Add(subItem.ToString() ?? ""); 389 | } 390 | 391 | if (normalizedValue.Count > 0) 392 | { 393 | result[item.Key] = normalizedValue; 394 | } 395 | } 396 | 397 | return result; 398 | } 399 | 400 | private HttpRequestMessage BuildJsonRequest(HttpMethod method, Uri url, IDictionary? headers = null, IDictionary? body = null) 401 | { 402 | var request = new HttpRequestMessage(method, url); 403 | if (body is not null && body.Count > 0) 404 | { 405 | request.Content = JsonContent.Create(body); 406 | } 407 | 408 | if (headers is not null) 409 | { 410 | foreach (var header in headers) 411 | { 412 | request.Headers.Add(header.Key, header.Value); 413 | } 414 | } 415 | 416 | return request; 417 | } 418 | 419 | private HttpRequestMessage BuildFileRequest(HttpMethod method, Uri url, IDictionary? headers, IDictionary? body, IEnumerable files) 420 | { 421 | var request = new HttpRequestMessage(method, url); 422 | 423 | if (headers is not null) 424 | { 425 | foreach (var header in headers) 426 | { 427 | request.Headers.Add(header.Key, header.Value); 428 | } 429 | } 430 | 431 | var form = new MultipartFormDataContent(); 432 | foreach (var file in files) 433 | { 434 | var stream = file.GetStream(); 435 | if (stream is null || string.IsNullOrWhiteSpace(file.FieldName) || string.IsNullOrWhiteSpace(file.FileName)) 436 | { 437 | continue; 438 | } 439 | 440 | var fileContent = new StreamContent(stream); 441 | var mimeType = GetMimeType(file); 442 | fileContent.Headers.ContentType = new MediaTypeHeaderValue(mimeType); 443 | form.Add(fileContent, file.FieldName, file.FileName); 444 | } 445 | 446 | 447 | if (body is not null && body.Count > 0) 448 | { 449 | Dictionary additionalBody = new Dictionary(); 450 | foreach (var item in body) 451 | { 452 | if (item.Value is IList valueAsList && item.Value is not string) 453 | { 454 | for (int i = 0; i < valueAsList.Count; i++) 455 | { 456 | var listValue = valueAsList[i]?.ToString(); 457 | if (string.IsNullOrWhiteSpace(listValue)) 458 | { 459 | continue; 460 | } 461 | additionalBody[$"{item.Key}{i}"] = listValue; 462 | } 463 | } 464 | else 465 | { 466 | var value = item.Value?.ToString(); 467 | if (string.IsNullOrWhiteSpace(value)) 468 | { 469 | continue; 470 | } 471 | additionalBody[item.Key] = value; 472 | } 473 | } 474 | 475 | foreach (var item in additionalBody) 476 | { 477 | var content = new StringContent(item.Value); 478 | form.Add(content, item.Key); 479 | } 480 | } 481 | 482 | request.Content = form; 483 | return request; 484 | } 485 | 486 | private string GetMimeType(IFile file) 487 | { 488 | if (file is FilepathFile filePath) 489 | { 490 | var fileName = Path.GetFileName(filePath.FilePath); 491 | return MimeMapping.MimeUtility.GetMimeMapping(fileName); 492 | } 493 | return MimeMapping.MimeUtility.UnknownMimeType; 494 | } 495 | 496 | } 497 | } -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Services/AdminService.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Models.Auth; 2 | using pocketbase_csharp_sdk.Services.Base; 3 | 4 | namespace pocketbase_csharp_sdk.Services 5 | { 6 | public class AdminService : BaseAuthService 7 | { 8 | 9 | protected override string BasePath(string? url = null) => "/api/admins"; 10 | 11 | public AdminService(PocketBase client) : base(client) 12 | { 13 | } 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Services/BackupService.cs: -------------------------------------------------------------------------------- 1 | using FluentResults; 2 | using pocketbase_csharp_sdk.Models; 3 | using pocketbase_csharp_sdk.Services.Base; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace pocketbase_csharp_sdk.Services 11 | { 12 | public class BackupService : BaseService 13 | { 14 | readonly PocketBase _pocketBase; 15 | public BackupService(PocketBase pocketBase) 16 | { 17 | this._pocketBase = pocketBase; 18 | } 19 | 20 | protected override string BasePath(string? path = null) 21 | { 22 | return path ?? string.Empty; 23 | } 24 | 25 | public async Task>> GetFullListAsync() 26 | { 27 | var b = await _pocketBase.SendAsync>("/api/backups", HttpMethod.Get); 28 | return b; 29 | } 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Services/Base/BaseAuthService.cs: -------------------------------------------------------------------------------- 1 | using FluentResults; 2 | using pocketbase_csharp_sdk.Models.Auth; 3 | 4 | namespace pocketbase_csharp_sdk.Services.Base 5 | { 6 | public abstract class BaseAuthService : BaseService 7 | where T : AuthModel 8 | { 9 | private readonly PocketBase _client; 10 | protected BaseAuthService(PocketBase client) 11 | { 12 | this._client = client; 13 | } 14 | 15 | /// 16 | /// authenticates a user with the API using their email/username and password. 17 | /// 18 | /// The email/username of the user to authenticate. 19 | /// The password of the user to authenticate. 20 | /// The request body to send to the API. Default is null. 21 | /// The query parameters to send to the API. Default is null. 22 | /// The headers to send to the API. Default is null. 23 | /// A cancellation token to cancel the operation. Default is the default cancellation token. 24 | /// An object of type T containing the authenticated user's information. 25 | public async Task> AuthenticateWithPasswordAsync(string usernameOrEmail, string password, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 26 | { 27 | var body = new Dictionary 28 | { 29 | { "identity", usernameOrEmail }, 30 | { "password", password } 31 | }; 32 | 33 | var url = $"{BasePath()}/auth-with-password"; 34 | var result = await _client.SendAsync(url, HttpMethod.Post, headers: headers, body: body, query: query, cancellationToken: cancellationToken); 35 | 36 | return SetAndReturn(result); 37 | } 38 | 39 | /// 40 | /// authenticates a user with the API using their email/username and password. 41 | /// 42 | /// The email/username of the user to authenticate. 43 | /// The password of the user to authenticate. 44 | /// The request body to send to the API. Default is null. 45 | /// The query parameters to send to the API. Default is null. 46 | /// The headers to send to the API. Default is null. 47 | /// A cancellation token to cancel the operation. Default is the default cancellation token. 48 | /// An object of type T containing the authenticated user's information. 49 | public Result AuthenticateWithPassword(string usernameOrEmail, string password, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 50 | { 51 | var body = new Dictionary { 52 | { "identity", usernameOrEmail }, 53 | { "password", password } 54 | }; 55 | 56 | var url = $"{BasePath()}/auth-with-password"; 57 | var result = _client.Send(url, HttpMethod.Post, headers: headers, body: body, query: query, cancellationToken: cancellationToken); 58 | 59 | return SetAndReturn(result); 60 | } 61 | 62 | /// 63 | /// refreshes an authenticated user's token 64 | /// 65 | /// The request body to send to the API. Default is null. 66 | /// The query parameters to send to the API. Default is null. 67 | /// The headers to send to the API. Default is null. 68 | /// A cancellation token to cancel the operation. Default is the default cancellation token. 69 | /// An object of type T containing the authenticated user's updated information. 70 | public async Task> RefreshAsync(IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 71 | { 72 | var url = $"{BasePath()}/auth-refresh"; 73 | var result = await _client.SendAsync(url, HttpMethod.Post, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 74 | 75 | return SetAndReturn(result); 76 | } 77 | 78 | /// 79 | /// refreshes an authenticated user's token 80 | /// 81 | /// The request body to send to the API. Default is null. 82 | /// The query parameters to send to the API. Default is null. 83 | /// The headers to send to the API. Default is null. 84 | /// A cancellation token to cancel the operation. Default is the default cancellation token. 85 | /// An object of type T containing the authenticated user's updated information. 86 | public Result Refresh(IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 87 | { 88 | var url = $"{BasePath()}/auth-refresh"; 89 | var result = _client.Send(url, HttpMethod.Post, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 90 | 91 | return SetAndReturn(result); 92 | } 93 | 94 | /// 95 | /// sends a password reset request for a specific email. 96 | /// 97 | /// The email of the user to send the password reset request to. 98 | /// The request body to send to the API. Default is null. 99 | /// The query parameters to send to the API. Default is null. 100 | /// The headers to send to the API. Default is null. 101 | /// A cancellation token to cancel the operation. Default is the default cancellation token. 102 | public Task RequestPasswordResetAsync(string email, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 103 | { 104 | body ??= new Dictionary(); 105 | body.Add("email", email); 106 | 107 | var url = $"{BasePath()}/request-password-reset"; 108 | return _client.SendAsync(url, HttpMethod.Post, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 109 | } 110 | 111 | /// 112 | /// sends a password reset request for a specific email. 113 | /// 114 | /// The email of the user to send the password reset request to. 115 | /// The request body to send to the API. Default is null. 116 | /// The query parameters to send to the API. Default is null. 117 | /// The headers to send to the API. Default is null. 118 | /// A cancellation token to cancel the operation. Default is the default cancellation token. 119 | public Result RequestPasswordReset(string email, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 120 | { 121 | body ??= new Dictionary(); 122 | body.Add("email", email); 123 | 124 | var url = $"{BasePath()}/request-password-reset"; 125 | return _client.Send(url, HttpMethod.Post, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 126 | } 127 | 128 | /// 129 | /// confirms a password reset request using a reset token, and a new password. 130 | /// 131 | /// The password reset token sent to the user's email. 132 | /// The new password to set for the user. 133 | /// The confirmation of the new password. 134 | /// The request body to send to the API. Default is null. 135 | /// The query parameters to send to the API. Default is null. 136 | /// The headers to send to the API. Default is null. 137 | /// A cancellation token to cancel the operation. Default is the default cancellation token. 138 | /// An object of type T containing the authenticated user's updated information. 139 | public async Task> ConfirmPasswordResetAsync(string passwordResetToken, string password, string passwordConfirm, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 140 | { 141 | body ??= new Dictionary(); 142 | body.Add("token", passwordResetToken); 143 | body.Add("password", password); 144 | body.Add("passwordConfirm", passwordConfirm); 145 | 146 | var url = $"{BasePath()}/confirm-password-reset"; 147 | var result = await _client.SendAsync(url, HttpMethod.Post, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 148 | 149 | return SetAndReturn(result); 150 | } 151 | 152 | /// 153 | /// confirms a password reset request using a reset token, and a new password. 154 | /// 155 | /// The password reset token sent to the user's email. 156 | /// The new password to set for the user. 157 | /// The confirmation of the new password. 158 | /// The request body to send to the API. Default is null. 159 | /// The query parameters to send to the API. Default is null. 160 | /// The headers to send to the API. Default is null. 161 | /// A cancellation token to cancel the operation. Default is the default cancellation token. 162 | /// An object of type T containing the authenticated user's updated information. 163 | public Result ConfirmPasswordReset(string passwordResetToken, string password, string passwordConfirm, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 164 | { 165 | body ??= new Dictionary(); 166 | body.Add("token", passwordResetToken); 167 | body.Add("password", password); 168 | body.Add("passwordConfirm", passwordConfirm); 169 | 170 | var url = $"{BasePath()}/confirm-password-reset"; 171 | var result = _client.Send(url, HttpMethod.Post, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 172 | 173 | return SetAndReturn(result); 174 | } 175 | 176 | 177 | protected void SaveAuthentication(T? authModel) 178 | { 179 | _client.AuthStore.Save(authModel?.Token, authModel?.Model); 180 | } 181 | 182 | protected Result SetAndReturn(Result result) 183 | { 184 | if (result.IsSuccess) 185 | { 186 | SaveAuthentication(result.Value); 187 | } 188 | 189 | return result; 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Services/Base/BaseCrudService.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Models; 2 | using FluentResults; 3 | 4 | namespace pocketbase_csharp_sdk.Services.Base 5 | { 6 | public abstract class BaseCrudService : BaseService 7 | { 8 | private readonly PocketBase _client; 9 | 10 | protected BaseCrudService(PocketBase client) 11 | { 12 | this._client = client; 13 | } 14 | 15 | public virtual Result> List(int page = 1, int perPage = 30, string? filter = null, string? sort = null, CancellationToken cancellationToken = default) 16 | { 17 | var path = BasePath(); 18 | var query = new Dictionary() 19 | { 20 | { "filter", filter }, 21 | { "page", page }, 22 | { "perPage", perPage }, 23 | { "sort", sort } 24 | }; 25 | 26 | return _client.Send>(path, HttpMethod.Get, query: query, cancellationToken: cancellationToken); 27 | } 28 | 29 | public virtual Task>> ListAsync(int page = 1, int perPage = 30, string? filter = null, string? sort = null, CancellationToken cancellationToken = default) 30 | { 31 | var path = BasePath(); 32 | var query = new Dictionary() 33 | { 34 | { "filter", filter }, 35 | { "page", page }, 36 | { "perPage", perPage }, 37 | { "sort", sort } 38 | }; 39 | 40 | return _client.SendAsync>(path, HttpMethod.Get, query: query, cancellationToken: cancellationToken); 41 | } 42 | 43 | public virtual Result> GetFullList(int batch = 100, string? filter = null, string? sort = null, CancellationToken cancellationToken = default) 44 | { 45 | List result = new(); 46 | int currentPage = 1; 47 | Result> lastResponse; 48 | do 49 | { 50 | lastResponse = List(currentPage, perPage: batch, filter: filter, sort: sort, cancellationToken: cancellationToken); 51 | if (lastResponse.IsSuccess && lastResponse.Value.Items is not null) 52 | { 53 | result.AddRange(lastResponse.Value.Items); 54 | } 55 | currentPage++; 56 | } while (lastResponse.IsSuccess && lastResponse.Value.Items?.Count > 0 && lastResponse.Value.TotalItems > result.Count); 57 | 58 | return result; 59 | } 60 | 61 | public virtual async Task> GetFullListAsync(int batch = 100, string? filter = null, string? sort = null, CancellationToken cancellationToken = default) 62 | { 63 | List result = new(); 64 | int currentPage = 1; 65 | Result> lastResponse; 66 | do 67 | { 68 | lastResponse = await ListAsync(currentPage, perPage: batch, filter: filter, sort: sort, cancellationToken: cancellationToken); 69 | if (lastResponse.IsSuccess && lastResponse.Value.Items is not null) 70 | { 71 | result.AddRange(lastResponse.Value.Items); 72 | } 73 | currentPage++; 74 | } while (lastResponse.IsSuccess && lastResponse.Value.Items?.Count > 0 && lastResponse.Value.TotalItems > result.Count); 75 | 76 | return result; 77 | } 78 | 79 | public virtual Result GetOne(string id) 80 | { 81 | string url = $"{BasePath()}/{UrlEncode(id)}"; 82 | return _client.Send(url, HttpMethod.Get); 83 | } 84 | 85 | public virtual Task> GetOneAsync(string id) 86 | { 87 | string url = $"{BasePath()}/{UrlEncode(id)}"; 88 | return _client.SendAsync(url, HttpMethod.Get); 89 | } 90 | 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Services/Base/BaseService.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Models; 2 | using System.Web; 3 | 4 | namespace pocketbase_csharp_sdk.Services.Base 5 | { 6 | public abstract class BaseService 7 | { 8 | private readonly string[] _itemProperties; 9 | 10 | protected BaseService() 11 | { 12 | this._itemProperties = this.GetPropertyNames().ToArray(); 13 | } 14 | 15 | protected abstract string BasePath(string? path = null); 16 | 17 | protected Dictionary ConstructBody(object item) 18 | { 19 | var body = new Dictionary(); 20 | 21 | foreach (var prop in item.GetType().GetProperties()) 22 | { 23 | if (_itemProperties.Contains(prop.Name)) continue; 24 | var propValue = prop.GetValue(item, null); 25 | if (propValue is not null) body.Add(ToCamelCase(prop.Name), propValue); 26 | } 27 | 28 | return body; 29 | } 30 | 31 | private string ToCamelCase(string str) 32 | { 33 | return char.ToLowerInvariant(str[0]) + str.Substring(1); 34 | } 35 | 36 | private IEnumerable GetPropertyNames() 37 | { 38 | return from prop in typeof(BaseModel).GetProperties() 39 | select prop.Name; 40 | } 41 | 42 | protected string UrlEncode(string? param) 43 | { 44 | return HttpUtility.UrlEncode(param) ?? ""; 45 | } 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Services/Base/BaseSubCrudService.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Models; 2 | using pocketbase_csharp_sdk.Models.Files; 3 | using FluentResults; 4 | 5 | namespace pocketbase_csharp_sdk.Services.Base 6 | { 7 | public abstract class BaseSubCrudService : BaseService 8 | { 9 | private readonly PocketBase _client; 10 | readonly string _collectionName; 11 | 12 | protected BaseSubCrudService(PocketBase client, string collectionName) 13 | { 14 | this._collectionName = collectionName; 15 | this._client = client; 16 | } 17 | 18 | public virtual Result> List(int page = 1, int perPage = 30, string? filter = null, string? sort = null, CancellationToken cancellationToken = default) 19 | { 20 | var path = BasePath(_collectionName); 21 | var query = new Dictionary() 22 | { 23 | { "filter", filter }, 24 | { "page", page }, 25 | { "perPage", perPage }, 26 | { "sort", sort } 27 | }; 28 | 29 | return _client.Send>(path, HttpMethod.Get, query: query, cancellationToken: cancellationToken); 30 | } 31 | 32 | public virtual Task>> ListAsync(int page = 1, int perPage = 30, string? filter = null, string? sort = null, CancellationToken cancellationToken = default) 33 | { 34 | var path = BasePath(_collectionName); 35 | var query = new Dictionary() 36 | { 37 | { "filter", filter }, 38 | { "page", page }, 39 | { "perPage", perPage }, 40 | { "sort", sort } 41 | }; 42 | return _client.SendAsync>(path, HttpMethod.Get, query: query, cancellationToken: cancellationToken);; 43 | } 44 | 45 | public virtual Result> GetFullList(int batch = 100, string? filter = null, string? sort = null, CancellationToken cancellationToken = default) 46 | { 47 | List result = new(); 48 | int currentPage = 1; 49 | Result> lastResponse; 50 | do 51 | { 52 | lastResponse = List(page: currentPage, perPage: batch, filter: filter, sort: sort, cancellationToken: cancellationToken); 53 | if (lastResponse.IsSuccess && lastResponse.Value.Items is not null) 54 | { 55 | result.AddRange(lastResponse.Value.Items); 56 | } 57 | currentPage++; 58 | } while (lastResponse.IsSuccess && lastResponse.Value.Items?.Count > 0 && lastResponse.Value.TotalItems > result.Count); 59 | 60 | return result; 61 | } 62 | 63 | public virtual async Task>> GetFullListAsync(int batch = 100, string? filter = null, string? sort = null, CancellationToken cancellationToken = default) 64 | { 65 | List result = new(); 66 | int currentPage = 1; 67 | Result> lastResponse; 68 | do 69 | { 70 | lastResponse = await ListAsync(page: currentPage, perPage: batch, filter: filter, sort: sort, cancellationToken: cancellationToken); 71 | if (lastResponse.IsSuccess && lastResponse.Value.Items is not null) 72 | { 73 | result.AddRange(lastResponse.Value.Items); 74 | } 75 | currentPage++; 76 | } while (lastResponse.IsSuccess && lastResponse.Value.Items?.Count > 0 && lastResponse.Value.TotalItems > result.Count); 77 | 78 | return result; 79 | } 80 | 81 | public virtual Result GetOne(string id) 82 | { 83 | string url = $"{BasePath(_collectionName)}/{UrlEncode(id)}"; 84 | return _client.Send(url, HttpMethod.Get); 85 | } 86 | 87 | public virtual Task> GetOneAsync(string id) 88 | { 89 | string url = $"{BasePath(_collectionName)}/{UrlEncode(id)}"; 90 | return _client.SendAsync(url, HttpMethod.Get); 91 | } 92 | 93 | public Task> CreateAsync(T item, string? expand = null, IDictionary? headers = null, IEnumerable? files = null, CancellationToken cancellationToken = default) where T : BaseModel 94 | { 95 | var query = new Dictionary() 96 | { 97 | { "expand", expand }, 98 | }; 99 | var body = ConstructBody(item); 100 | var url = this.BasePath(_collectionName); 101 | return _client.SendAsync(url, HttpMethod.Post, body: body, headers: headers, query: query, files: files, cancellationToken: cancellationToken); 102 | } 103 | 104 | public Result Create(T item, string? expand = null, IDictionary? headers = null, IEnumerable? files = null, CancellationToken cancellationToken = default) where T : BaseModel 105 | { 106 | var query = new Dictionary() 107 | { 108 | { "expand", expand }, 109 | }; 110 | var body = ConstructBody(item); 111 | var url = this.BasePath(_collectionName); 112 | return _client.Send(url, HttpMethod.Post, body: body, headers: headers, query: query, files: files, cancellationToken: cancellationToken); 113 | } 114 | 115 | public Task> UpdateAsync(T item, string? expand = null, IDictionary? headers = null, CancellationToken cancellationToken = default) where T : BaseModel 116 | { 117 | var query = new Dictionary() 118 | { 119 | { "expand", expand }, 120 | }; 121 | var body = ConstructBody(item); 122 | var url = this.BasePath(_collectionName) + "/" + UrlEncode(item.Id); 123 | return _client.SendAsync(url, HttpMethod.Patch, body: body, headers: headers, query: query, cancellationToken: cancellationToken); 124 | } 125 | 126 | public Result Update(T item, string? expand = null, IDictionary? headers = null, CancellationToken cancellationToken = default) where T : BaseModel 127 | { 128 | var query = new Dictionary() 129 | { 130 | { "expand", expand }, 131 | }; 132 | var body = ConstructBody(item); 133 | var url = this.BasePath(_collectionName) + "/" + UrlEncode(item.Id); 134 | return _client.Send(url, HttpMethod.Patch, body: body, headers: headers, query: query, cancellationToken: cancellationToken); 135 | } 136 | 137 | /// 138 | /// subscribe to the specified topic for realtime updates 139 | /// 140 | /// the topic to subscribe to 141 | /// the id of the specific record or * for the whole collection 142 | /// callback, that is invoked every time something changes 143 | public async void Subscribe(string recordId, Func callback) 144 | { 145 | string subscribeTo = recordId != "*" 146 | ? $"{_collectionName}/{recordId}" 147 | : _collectionName; 148 | 149 | try 150 | { 151 | await _client.RealTime.SubscribeAsync(subscribeTo, callback); 152 | } 153 | catch (Exception ex) 154 | { 155 | throw; 156 | } 157 | } 158 | 159 | /// 160 | /// unsubscribe all listeners from the specified topic 161 | /// 162 | /// the topic to unsubscribe from 163 | public Task UnsubscribeAsync(string? topic = null) 164 | { 165 | return _client.RealTime.UnsubscribeAsync(topic); 166 | } 167 | 168 | /// 169 | /// unsubscribe all listeners from the specified prefix 170 | /// 171 | /// the prefix to unsubscribe from 172 | /// 173 | public Task UnsubscribeByPrefixAsync(string prefix) 174 | { 175 | return _client.RealTime.UnsubscribeByPrefixAsync(prefix); 176 | } 177 | 178 | /// 179 | /// unsubscribe the specified listener from the specified topic 180 | /// 181 | /// the topic to unsubscribe from 182 | /// the listener to remove 183 | /// 184 | public Task UnsubscribeByTopicAndListenerAsync(string topic, Func listener) 185 | { 186 | return _client.RealTime.UnsubscribeByTopicAndListenerAsync(topic, listener); 187 | } 188 | 189 | 190 | public async Task UploadFileAsync(string field, string fileName, Stream stream, CancellationToken cancellationToken = default) 191 | { 192 | var file = new StreamFile() 193 | { 194 | FileName = fileName, 195 | FieldName = field, 196 | Stream = stream 197 | }; 198 | var url = this.BasePath(_collectionName); 199 | await _client.SendAsync(url, HttpMethod.Post, files: new[] { file }, cancellationToken: cancellationToken); 200 | } 201 | 202 | public void UploadFile(string field, string fileName, Stream stream, CancellationToken cancellationToken = default) 203 | { 204 | var file = new StreamFile() 205 | { 206 | FileName = fileName, 207 | FieldName = field, 208 | Stream = stream 209 | }; 210 | var url = this.BasePath(_collectionName); 211 | _client.Send(url, HttpMethod.Post, files: new[] { file }, cancellationToken: cancellationToken); 212 | } 213 | 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Services/CollectionAuthService.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Models; 2 | using pocketbase_csharp_sdk.Models.Auth; 3 | using pocketbase_csharp_sdk.Models.Log; 4 | using System.Web; 5 | using FluentResults; 6 | using pocketbase_csharp_sdk.Services.Base; 7 | 8 | namespace pocketbase_csharp_sdk.Services 9 | { 10 | public class CollectionAuthService : CollectionAuthService, T> 11 | where T : IBaseAuthModel 12 | { 13 | public CollectionAuthService(PocketBase client, string collectionName) : base(client, collectionName) { } 14 | } 15 | 16 | public class CollectionAuthService : BaseAuthService 17 | where R : RecordAuthModel 18 | where T : IBaseAuthModel 19 | { 20 | protected override string BasePath(string? url = null) => $"/api/collections/{this._collectionName}"; 21 | 22 | private readonly PocketBase _client; 23 | private readonly string _collectionName; 24 | 25 | public CollectionAuthService(PocketBase client, string collectionName) : base(client) 26 | { 27 | this._client = client; 28 | this._collectionName = collectionName; 29 | } 30 | 31 | public async Task> AuthenticateViaOAuth2Async(string provider, string code, string codeVerifier, string redirectUrl, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 32 | { 33 | body ??= new Dictionary(); 34 | body.Add("provider", provider); 35 | body.Add("code", code); 36 | body.Add("codeVerifier", codeVerifier); 37 | body.Add("redirectUrl", redirectUrl); 38 | 39 | var url = $"{BasePath()}/auth-via-oauth2"; 40 | var result = await _client.SendAsync(url, HttpMethod.Post, headers: headers, body: body, query: query, cancellationToken: cancellationToken); 41 | 42 | return SetAndReturn(result); 43 | } 44 | 45 | public Result AuthenticateViaOAuth2(string provider, string code, string codeVerifier, string redirectUrl, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 46 | { 47 | body ??= new Dictionary(); 48 | body.Add("provider", provider); 49 | body.Add("code", code); 50 | body.Add("codeVerifier", codeVerifier); 51 | body.Add("redirectUrl", redirectUrl); 52 | 53 | var url = $"{BasePath()}/auth-via-oauth2"; 54 | var result = _client.Send(url, HttpMethod.Post, headers: headers, body: body, query: query, cancellationToken: cancellationToken); 55 | 56 | return SetAndReturn(result); 57 | } 58 | 59 | public Task RequestVerificationAsync(string email, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 60 | { 61 | body ??= new Dictionary(); 62 | body.Add("email", email); 63 | 64 | var url = $"{BasePath()}/request-verification"; 65 | return _client.SendAsync(url, HttpMethod.Post, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 66 | } 67 | 68 | public Result RequestVerification(string email, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 69 | { 70 | body ??= new Dictionary(); 71 | body.Add("email", email); 72 | 73 | var url = $"{BasePath()}/request-verification"; 74 | return _client.Send(url, HttpMethod.Post, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 75 | } 76 | 77 | public async Task> ConfirmVerificationAsync(string token, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 78 | { 79 | body ??= new Dictionary(); 80 | body.Add("token", token); 81 | 82 | var url = $"{BasePath()}/confirm-verification"; 83 | var result = await _client.SendAsync(url, HttpMethod.Post, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 84 | 85 | return SetAndReturn(result); 86 | } 87 | 88 | public Result ConfirmVerification(string token, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 89 | { 90 | body ??= new Dictionary(); 91 | body.Add("token", token); 92 | 93 | var url = $"{BasePath()}/confirm-verification"; 94 | var result = _client.Send(url, HttpMethod.Post, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 95 | 96 | return SetAndReturn(result); 97 | } 98 | 99 | public Task RequestEmailChangeAsync(string newEmail, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 100 | { 101 | body ??= new Dictionary(); 102 | body.Add("newEmail", newEmail); 103 | 104 | var url = $"{BasePath()}/request-email-change"; 105 | return _client.SendAsync(url, HttpMethod.Post, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 106 | } 107 | 108 | public Result RequestEmailChange(string newEmail, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 109 | { 110 | body ??= new Dictionary(); 111 | body.Add("newEmail", newEmail); 112 | 113 | var url = $"{BasePath()}/request-email-change"; 114 | return _client.Send(url, HttpMethod.Post, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 115 | } 116 | 117 | public async Task> ConfirmEmailChangeAsync(string emailChangeToken, string userPassword, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 118 | { 119 | body ??= new Dictionary(); 120 | body.Add("token", emailChangeToken); 121 | body.Add("password", userPassword); 122 | 123 | var url = $"{BasePath()}/confirm-email-change"; 124 | var result = await _client.SendAsync(url, HttpMethod.Post, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 125 | 126 | return SetAndReturn(result); 127 | } 128 | 129 | public Result ConfirmEmailChange(string emailChangeToken, string userPassword, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 130 | { 131 | body ??= new Dictionary(); 132 | body.Add("token", emailChangeToken); 133 | body.Add("password", userPassword); 134 | 135 | var url = $"{BasePath()}/confirm-email-change"; 136 | var result = _client.Send(url, HttpMethod.Post, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 137 | 138 | return SetAndReturn(result); 139 | } 140 | 141 | public Task> GetAuthenticationMethodsAsync(IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 142 | { 143 | var url = $"{BasePath()}/auth-methods"; 144 | return _client.SendAsync(url, HttpMethod.Get, headers: headers, query: query, cancellationToken: cancellationToken); 145 | } 146 | 147 | public Result GetAuthenticationMethods(IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 148 | { 149 | var url = $"{BasePath()}/auth-methods"; 150 | return _client.Send(url, HttpMethod.Get, headers: headers, query: query, cancellationToken: cancellationToken); 151 | } 152 | 153 | public Task>> GetExternalAuthenticationMethodsAsync(string userId, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 154 | { 155 | var url = $"{BasePath()}/records/{HttpUtility.HtmlEncode(userId)}/external-auths"; 156 | return _client.SendAsync>(url, HttpMethod.Get, headers: headers, query: query, cancellationToken: cancellationToken); 157 | } 158 | 159 | public Result> GetExternalAuthenticationMethods(string userId, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 160 | { 161 | var url = $"{BasePath()}/records/{HttpUtility.HtmlEncode(userId)}/external-auths"; 162 | return _client.Send>(url, HttpMethod.Get, headers: headers, query: query, cancellationToken: cancellationToken); 163 | } 164 | 165 | public Task UnlinkExternalAuthenticationAsync(string userId, string provider, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 166 | { 167 | var url = $"{BasePath()}/records/{HttpUtility.HtmlEncode(userId)}/external-auths/{HttpUtility.HtmlEncode(provider)}"; 168 | return _client.SendAsync(url, HttpMethod.Delete, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 169 | } 170 | 171 | public Result UnlinkExternalAuthentication(string userId, string provider, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 172 | { 173 | var url = $"{BasePath()}/records/{HttpUtility.HtmlEncode(userId)}/external-auths/{HttpUtility.HtmlEncode(provider)}"; 174 | return _client.Send(url, HttpMethod.Delete, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 175 | } 176 | 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Services/CollectionService.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Models.Collection; 2 | using FluentResults; 3 | using pocketbase_csharp_sdk.Services.Base; 4 | 5 | namespace pocketbase_csharp_sdk.Services 6 | { 7 | public class CollectionService : BaseCrudService 8 | { 9 | private readonly PocketBase _client; 10 | 11 | protected override string BasePath(string? url = null) => "/api/collections"; 12 | 13 | public CollectionService(PocketBase client) : base(client) 14 | { 15 | this._client = client; 16 | } 17 | 18 | public Task ImportAsync(IEnumerable collections, bool deleteMissing = false, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 19 | { 20 | body ??= new Dictionary(); 21 | body.Add("collections", collections); 22 | body.Add("deleteMissing", deleteMissing); 23 | 24 | var url = $"{BasePath()}/import"; 25 | return _client.SendAsync(url, HttpMethod.Put, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 26 | } 27 | 28 | public Result Import(IEnumerable collections, bool deleteMissing = false, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 29 | { 30 | body ??= new Dictionary(); 31 | body.Add("collections", collections); 32 | body.Add("deleteMissing", deleteMissing); 33 | 34 | var url = $"{BasePath()}/import"; 35 | return _client.Send(url, HttpMethod.Put, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 36 | } 37 | 38 | public Task> GetByNameAsync(string name, CancellationToken cancellationToken = default) 39 | { 40 | var url = $"{BasePath()}/{UrlEncode(name)}"; 41 | return _client.SendAsync(url, HttpMethod.Get, cancellationToken: cancellationToken); 42 | } 43 | 44 | public Result GetByName(string name, CancellationToken cancellationToken = default) 45 | { 46 | var url = $"{BasePath()}/{UrlEncode(name)}"; 47 | return _client.Send(url, HttpMethod.Get, cancellationToken: cancellationToken); 48 | } 49 | 50 | public Task DeleteAsync(string name, CancellationToken cancellationToken = default) 51 | { 52 | var url = $"{BasePath()}/{UrlEncode(name)}"; 53 | return _client.SendAsync(url, HttpMethod.Delete, cancellationToken: cancellationToken); 54 | } 55 | 56 | public Result Delete(string name, CancellationToken cancellationToken = default) 57 | { 58 | var url = $"{BasePath()}/{UrlEncode(name)}"; 59 | return _client.Send(url, HttpMethod.Delete, cancellationToken: cancellationToken); 60 | } 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Services/HealthService.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Models; 2 | using FluentResults; 3 | using pocketbase_csharp_sdk.Services.Base; 4 | 5 | namespace pocketbase_csharp_sdk.Services 6 | { 7 | public class HealthService : BaseService 8 | { 9 | private readonly PocketBase _pocketBase; 10 | 11 | protected override string BasePath(string? path = null) => "api/health"; 12 | 13 | public HealthService(PocketBase pocketBase) 14 | { 15 | this._pocketBase = pocketBase; 16 | } 17 | 18 | /// 19 | /// Returns the health status of the server. 20 | /// 21 | public Task> CheckHealthAsync() 22 | { 23 | return _pocketBase.SendAsync(BasePath(), HttpMethod.Get); 24 | } 25 | 26 | /// 27 | /// Returns the health status of the server. 28 | /// 29 | public Result CheckHealth() 30 | { 31 | return _pocketBase.Send(BasePath(), HttpMethod.Get); 32 | } 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Services/LogService.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Models; 2 | using pocketbase_csharp_sdk.Models.Log; 3 | using FluentResults; 4 | using pocketbase_csharp_sdk.Services.Base; 5 | 6 | namespace pocketbase_csharp_sdk.Services 7 | { 8 | public class LogService : BaseService 9 | { 10 | 11 | protected override string BasePath(string? path = null) => "api/logs/requests"; 12 | 13 | private readonly PocketBase _client; 14 | 15 | public LogService(PocketBase client) 16 | { 17 | this._client = client; 18 | } 19 | 20 | /// 21 | /// retrieves a specific request log from the API by its ID. 22 | /// 23 | /// The ID of the request log to retrieve. 24 | /// The request body to send to the API. Default is null. 25 | /// The query parameters to send to the API. Default is null. 26 | /// The headers to send to the API. Default is null. 27 | /// A cancellation token to cancel the operation. Default is the default cancellation token. 28 | /// A LogRequestModel object containing the request log. 29 | public Task> GetRequestAsync(string id, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 30 | { 31 | var url = $"{BasePath()}/{UrlEncode(id)}"; 32 | return _client.SendAsync(url, HttpMethod.Get, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 33 | } 34 | 35 | /// 36 | /// retrieves a specific request log from the API by its ID. 37 | /// 38 | /// The ID of the request log to retrieve. 39 | /// The request body to send to the API. Default is null. 40 | /// The query parameters to send to the API. Default is null. 41 | /// The headers to send to the API. Default is null. 42 | /// A cancellation token to cancel the operation. Default is the default cancellation token. 43 | /// A LogRequestModel object containing the request log. 44 | public Result GetRequest(string id, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 45 | { 46 | var url = $"{BasePath()}/{UrlEncode(id)}"; 47 | return _client.Send(url, HttpMethod.Get, headers: headers, query: query, body: body, cancellationToken: cancellationToken); ; 48 | } 49 | 50 | /// 51 | /// retrieves a paginated list of request logs from the API. 52 | /// 53 | /// The page number of the list to retrieve. Default is 1. 54 | /// The number of request logs per page. Default is 30. 55 | /// A filter string to apply to the list. Default is null. 56 | /// A sort string to apply to the list. Default is null. 57 | /// The request body to send to the API. Default is null. 58 | /// The query parameters to send to the API. Default is null. 59 | /// The headers to send to the API. Default is null. 60 | /// A cancellation token to cancel the operation. Default is the default cancellation token. 61 | /// A ResultList object containing the paginated list of request logs. 62 | public Task>> GetRequestsAsync(int page = 1, int perPage = 30, string? filter = null, string? sort = null, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 63 | { 64 | query ??= new Dictionary(); 65 | query.Add("page", page); 66 | query.Add("perPage", perPage); 67 | query.Add("filter", filter); 68 | query.Add("sort", sort); 69 | 70 | return _client.SendAsync>(BasePath(), HttpMethod.Get, headers: headers, query: query, body: body, cancellationToken: cancellationToken); ; 71 | } 72 | 73 | /// 74 | /// retrieves a paginated list of request logs from the API. 75 | /// 76 | /// The page number of the list to retrieve. Default is 1. 77 | /// The number of request logs per page. Default is 30. 78 | /// A filter string to apply to the list. Default is null. 79 | /// A sort string to apply to the list. Default is null. 80 | /// The request body to send to the API. Default is null. 81 | /// The query parameters to send to the API. Default is null. 82 | /// The headers to send to the API. Default is null. 83 | /// A cancellation token to cancel the operation. Default is the default cancellation token. 84 | /// A ResultList object containing the paginated list of request logs. 85 | public Result> GetRequests(int page = 1, int perPage = 30, string? filter = null, string? sort = null, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 86 | { 87 | query ??= new Dictionary(); 88 | query.Add("page", page); 89 | query.Add("perPage", perPage); 90 | query.Add("filter", filter); 91 | query.Add("sort", sort); 92 | 93 | return _client.Send>(BasePath(), HttpMethod.Get, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 94 | } 95 | 96 | /// 97 | /// retrieves statistics for the request logs 98 | /// 99 | /// The query parameters to send to the API. Default is null. 100 | /// The headers to send to the API. Default is null. 101 | /// A cancellation token to cancel the operation. Default is the default cancellation token. 102 | /// An IEnumerable object containing the request log statistics. 103 | public Task>> GetRequestsStatisticsAsync(IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 104 | { 105 | var url = $"{BasePath()}/stats"; 106 | return _client.SendAsync>(url, HttpMethod.Get, headers: headers, query: query, cancellationToken: cancellationToken); 107 | } 108 | 109 | /// 110 | /// retrieves statistics for the request logs 111 | /// 112 | /// The query parameters to send to the API. Default is null. 113 | /// The headers to send to the API. Default is null. 114 | /// A cancellation token to cancel the operation. Default is the default cancellation token. 115 | /// An IEnumerable object containing the request log statistics. 116 | public Result> GetRequestsStatistics(IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 117 | { 118 | var url = $"{BasePath()}/stats"; 119 | return _client.Send>(url, HttpMethod.Get, headers: headers, query: query, cancellationToken: cancellationToken); 120 | } 121 | 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Services/RealTimeService.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Models; 2 | using pocketbase_csharp_sdk.Services.Base; 3 | using pocketbase_csharp_sdk.Sse; 4 | 5 | namespace pocketbase_csharp_sdk.Services 6 | { 7 | public class RealTimeService : BaseService 8 | { 9 | protected override string BasePath(string? path = null) => "/api/realtime"; 10 | 11 | private readonly PocketBase _client; 12 | 13 | private SseClient? _sseClient = null; 14 | private SseClient SseClient => _sseClient ??= new SseClient(_client, RealTimeCallBackAsync); 15 | 16 | private readonly Dictionary>> _subscriptions = new(); 17 | 18 | public RealTimeService(PocketBase client) 19 | { 20 | this._client = client; 21 | } 22 | 23 | private async Task RealTimeCallBackAsync(SseMessage message) 24 | { 25 | var messageEvent = message.Event ?? ""; 26 | if (_subscriptions.ContainsKey(messageEvent)) 27 | foreach (var callBack in _subscriptions[messageEvent]) 28 | await callBack(message); 29 | } 30 | 31 | public async Task SubscribeAsync(string subscription, Func callback) 32 | { 33 | if (!_subscriptions.ContainsKey(subscription)) 34 | { 35 | // New subscription 36 | _subscriptions.Add(subscription, new List> { callback }); 37 | await SubmitSubscriptionsAsync(); 38 | } 39 | else 40 | { 41 | var subcriptionCallbacks = _subscriptions[subscription]; 42 | if (!subcriptionCallbacks.Contains(callback)) 43 | subcriptionCallbacks.Add(callback); 44 | } 45 | } 46 | 47 | public Task UnsubscribeAsync(string? topic = null) 48 | { 49 | if (string.IsNullOrEmpty(topic)) 50 | _subscriptions.Clear(); 51 | else if (_subscriptions.ContainsKey(topic)) 52 | _subscriptions.Remove(topic); 53 | else 54 | return Task.CompletedTask; 55 | return SubmitSubscriptionsAsync(); 56 | } 57 | 58 | public async Task UnsubscribeByPrefixAsync(string prefix) 59 | { 60 | var subscriptionsToRemove = _subscriptions.Keys.Where(k => k.StartsWith(prefix)).ToList(); 61 | if (subscriptionsToRemove.Any()) 62 | { 63 | foreach (var subs in subscriptionsToRemove) 64 | _subscriptions.Remove(subs); 65 | 66 | await SubmitSubscriptionsAsync(); 67 | } 68 | } 69 | 70 | public async Task UnsubscribeByTopicAndListenerAsync(string topic, Func listener) 71 | { 72 | if (!_subscriptions.ContainsKey(topic)) 73 | return; 74 | 75 | var listeners = _subscriptions[topic]; 76 | if (listeners.Remove(listener) && !listeners.Any()) 77 | await UnsubscribeAsync(topic); 78 | } 79 | 80 | private async Task SubmitSubscriptionsAsync() 81 | { 82 | if (!_subscriptions.Any()) 83 | SseClient.Disconnect(); 84 | else 85 | { 86 | await SseClient.EnsureIsConnectedAsync(); 87 | Dictionary body = new() 88 | { 89 | { "clientId", SseClient.Id! }, 90 | { "subscriptions", _subscriptions.Keys.ToList() } 91 | }; 92 | 93 | await _client.SendAsync(BasePath(), HttpMethod.Post, body: body); 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Services/RecordService.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Enum; 2 | using FluentResults; 3 | using pocketbase_csharp_sdk.Services.Base; 4 | 5 | namespace pocketbase_csharp_sdk.Services 6 | { 7 | public class RecordService : BaseSubCrudService 8 | { 9 | 10 | protected override string BasePath(string? path = null) 11 | { 12 | var encoded = UrlEncode(path); 13 | return $"/api/collections/{encoded}/records"; 14 | } 15 | 16 | private readonly PocketBase _client; 17 | readonly string _collectionName; 18 | 19 | public RecordService(PocketBase client, string collectionName) : base(client, collectionName) 20 | { 21 | this._collectionName = collectionName; 22 | this._client = client; 23 | } 24 | 25 | private Uri GetFileUrl(string recordId, string fileName, IDictionary? query = null) 26 | { 27 | var url = $"api/files/{UrlEncode(_collectionName)}/{UrlEncode(recordId)}/{fileName}"; 28 | return _client.BuildUrl(url, query); 29 | } 30 | 31 | public Task> DownloadFileAsync(string recordId, string fileName, ThumbFormat? thumbFormat = null, CancellationToken cancellationToken = default) 32 | { 33 | var url = $"api/files/{UrlEncode(_collectionName)}/{UrlEncode(recordId)}/{fileName}"; 34 | 35 | //TODO find out how the specify the actual resolution to resize 36 | var query = new Dictionary() 37 | { 38 | { "thumb", ThumbFormatHelper.GetNameForQuery(thumbFormat) } 39 | }; 40 | 41 | return _client.GetStreamAsync(url, query, cancellationToken); 42 | } 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Services/SettingsService.cs: -------------------------------------------------------------------------------- 1 | using FluentResults; 2 | using pocketbase_csharp_sdk.Services.Base; 3 | 4 | namespace pocketbase_csharp_sdk.Services 5 | { 6 | public class SettingsService : BaseService 7 | { 8 | 9 | protected override string BasePath(string? path = null) => "api/settings"; 10 | 11 | private readonly PocketBase _client; 12 | 13 | public SettingsService(PocketBase client) 14 | { 15 | this._client = client; 16 | } 17 | 18 | public Task>> GetAllAsync(IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 19 | { 20 | return _client.SendAsync>(BasePath(), HttpMethod.Get, headers: headers, query: query, cancellationToken: cancellationToken); 21 | } 22 | 23 | public Result> GetAll(IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 24 | { 25 | return _client.Send>(BasePath(), HttpMethod.Get, headers: headers, query: query, cancellationToken: cancellationToken); 26 | } 27 | 28 | public Task>> UpdateAsync(IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 29 | { 30 | return _client.SendAsync>(BasePath(), HttpMethod.Patch, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 31 | } 32 | 33 | public Result> Update(IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 34 | { 35 | return _client.Send>(BasePath(), HttpMethod.Patch, headers: headers, query: query, body: body, cancellationToken: cancellationToken); 36 | } 37 | 38 | public Task TestS3Async(IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 39 | { 40 | var url = $"{BasePath()}/test/s3"; 41 | return _client.SendAsync(url, HttpMethod.Post, headers: headers, body: body, query: query, cancellationToken: cancellationToken); 42 | } 43 | 44 | public Result TestS3(IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 45 | { 46 | var url = $"{BasePath()}/test/s3"; 47 | return _client.Send(url, HttpMethod.Post, headers: headers, body: body, query: query, cancellationToken: cancellationToken); 48 | } 49 | 50 | public Task TestEmailAsync(string toEmail, string template, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 51 | { 52 | query ??= new Dictionary(); 53 | query.Add("email", toEmail); 54 | query.Add("template", template); 55 | 56 | var url = $"{BasePath()}/test/email"; 57 | return _client.SendAsync(url, HttpMethod.Post, headers: headers, body: body, query: query, cancellationToken: cancellationToken); 58 | } 59 | 60 | public Result TestEmail(string toEmail, string template, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 61 | { 62 | query ??= new Dictionary(); 63 | query.Add("email", toEmail); 64 | query.Add("template", template); 65 | 66 | var url = $"{BasePath()}/test/email"; 67 | return _client.Send(url, HttpMethod.Post, headers: headers, body: body, query: query, cancellationToken: cancellationToken); 68 | } 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Services/UserService.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Helper.Extensions; 2 | using pocketbase_csharp_sdk.Models; 3 | using pocketbase_csharp_sdk.Models.Auth; 4 | using pocketbase_csharp_sdk.Models.Log; 5 | using FluentResults; 6 | using pocketbase_csharp_sdk.Services.Base; 7 | 8 | namespace pocketbase_csharp_sdk.Services 9 | { 10 | public class UserService : BaseCrudService 11 | { 12 | protected override string BasePath(string? url = null) => "/api/collections/users/records"; 13 | 14 | private readonly PocketBase _client; 15 | private readonly CollectionAuthService _authService; 16 | 17 | public UserService(PocketBase client) : base(client) 18 | { 19 | this._client = client; 20 | this._authService = new CollectionAuthService(client, "users"); 21 | } 22 | 23 | /// 24 | /// Asynchronously creates a new user. 25 | /// 26 | /// The email address of the user. 27 | /// The password of the user. 28 | /// The confirmation of the password. 29 | /// The cancellation token for the request. 30 | /// A task that returns the created `UserModel`. 31 | /// Thrown if the client fails to send the request or receive a response. 32 | public Task> CreateAsync(string email, string password, string passwordConfirm, CancellationToken cancellationToken = default) 33 | { 34 | Dictionary body = new() 35 | { 36 | { "email", email }, 37 | { "password", password }, 38 | { "passwordConfirm", passwordConfirm }, 39 | }; 40 | 41 | return _client.SendAsync(BasePath(), HttpMethod.Post, body: body, cancellationToken: cancellationToken); 42 | } 43 | 44 | /// 45 | /// creates a new user. 46 | /// 47 | /// The email address of the user. 48 | /// The password of the user. 49 | /// The confirmation of the password. 50 | /// The cancellation token for the request. 51 | /// A task that returns the created `UserModel`. 52 | /// Thrown if the client fails to send the request or receive a response. 53 | public Result Create(string email, string password, string passwordConfirm, CancellationToken cancellationToken = default) 54 | { 55 | Dictionary body = new() 56 | { 57 | { "email", email }, 58 | { "password", password }, 59 | { "passwordConfirm", passwordConfirm }, 60 | }; 61 | 62 | return _client.Send(BasePath(), HttpMethod.Post, body: body, cancellationToken: cancellationToken); 63 | } 64 | 65 | /// 66 | /// Updates a users info. Only non-null values will be updated 67 | /// 68 | /// the id from the user to update 69 | /// The username of the auth record. 70 | /// The auth record email address. 71 | /// Whether to show/hide the auth record email when fetching the record data. 72 | /// Old auth record password. 73 | /// New auth record password. 74 | /// New auth record password confirmation. 75 | /// Indicates whether the auth record is verified or not. 76 | /// 77 | /// The cancellation token for the request. 78 | /// 79 | public Task> UpdateAsync(string id, string? username = null, string? email = null, bool? emailVisibility = null, string? oldPassword = null, string? password = null, string? passwordConfirm = null, bool? verified = null, object? file = null, CancellationToken cancellationToken = default) 80 | { 81 | //TODO File 82 | Dictionary body = new Dictionary(); 83 | body.AddIfNotNull("username", username); 84 | body.AddIfNotNull("email", email); 85 | body.AddIfNotNull("emailVisibility", emailVisibility); 86 | body.AddIfNotNull("oldPassword", oldPassword); 87 | body.AddIfNotNull("password", password); 88 | body.AddIfNotNull("passwordConfirm", passwordConfirm); 89 | body.AddIfNotNull("verified", verified); 90 | 91 | string url = $"{BasePath()}/records/{UrlEncode(id)}"; 92 | return _client.SendAsync(url, HttpMethod.Patch, body: body, cancellationToken: cancellationToken); 93 | } 94 | 95 | /// 96 | /// Updates a users info. Only non-null values will be updated 97 | /// 98 | /// the id from the user to update 99 | /// The username of the auth record. 100 | /// The auth record email address. 101 | /// Whether to show/hide the auth record email when fetching the record data. 102 | /// Old auth record password. 103 | /// New auth record password. 104 | /// New auth record password confirmation. 105 | /// Indicates whether the auth record is verified or not. 106 | /// 107 | /// The cancellation token for the request. 108 | /// 109 | public Result Update(string id, string? username = null, string? email = null, bool? emailVisibility = null, string? oldPassword = null, string? password = null, string? passwordConfirm = null, bool? verified = null, object? file = null, CancellationToken cancellationToken = default) 110 | { 111 | //TODO File 112 | Dictionary body = new Dictionary(); 113 | body.AddIfNotNull("username", username); 114 | body.AddIfNotNull("email", email); 115 | body.AddIfNotNull("emailVisibility", emailVisibility); 116 | body.AddIfNotNull("oldPassword", oldPassword); 117 | body.AddIfNotNull("password", password); 118 | body.AddIfNotNull("passwordConfirm", passwordConfirm); 119 | body.AddIfNotNull("verified", verified); 120 | 121 | string url = $"{BasePath()}/records/{UrlEncode(id)}"; 122 | return _client.Send(url, HttpMethod.Patch, body: body, cancellationToken: cancellationToken); 123 | } 124 | 125 | /// 126 | /// Returns all available application authentication methods 127 | /// 128 | /// 129 | /// 130 | /// The cancellation token for the request. 131 | /// 132 | public Task> GetAuthenticationMethodsAsync(IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 133 | { 134 | return _authService.GetAuthenticationMethodsAsync(query, headers, cancellationToken); 135 | } 136 | 137 | /// 138 | /// Returns all available application authentication methods 139 | /// 140 | /// 141 | /// 142 | /// The cancellation token for the request. 143 | /// 144 | public Result GetAuthenticationMethods(IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 145 | { 146 | return _authService.GetAuthenticationMethods(query, headers, cancellationToken); 147 | } 148 | 149 | /// 150 | /// authenticates a user with the API using their email/username and password. 151 | /// 152 | /// The email/username of the user to authenticate. 153 | /// The password of the user to authenticate. 154 | /// The query parameters to send to the API. Default is null. 155 | /// The headers to send to the API. Default is null. 156 | /// A cancellation token to cancel the operation. Default is the default cancellation token. 157 | /// An object of type T containing the authenticated user's information. 158 | public Task> AuthenticateWithPasswordAsync(string usernameOrEmail, string password, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 159 | { 160 | return _authService.AuthenticateWithPasswordAsync(usernameOrEmail, password, query, headers, cancellationToken); 161 | } 162 | 163 | /// 164 | /// authenticates a user with the API using their email/username and password. 165 | /// 166 | /// The email/username of the user to authenticate. 167 | /// The password of the user to authenticate. 168 | /// The query parameters to send to the API. Default is null. 169 | /// The headers to send to the API. Default is null. 170 | /// A cancellation token to cancel the operation. Default is the default cancellation token. 171 | /// An object of type T containing the authenticated user's information. 172 | public Result AuthenticateWithPassword(string usernameOrEmail, string password, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 173 | { 174 | return _authService.AuthenticateWithPassword(usernameOrEmail, password, query, headers, cancellationToken); 175 | } 176 | 177 | public Task> AuthenticateViaOAuth2Async(string provider, string code, string codeVerifier, string redirectUrl, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 178 | { 179 | return _authService.AuthenticateViaOAuth2Async(provider, code, codeVerifier, redirectUrl, body, query, headers, cancellationToken); 180 | } 181 | 182 | public Result AuthenticateViaOAuth2(string provider, string code, string codeVerifier, string redirectUrl, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 183 | { 184 | return _authService.AuthenticateViaOAuth2(provider, code, codeVerifier, redirectUrl, body, query, headers, cancellationToken); 185 | } 186 | 187 | public Task> RefreshAsync(IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 188 | { 189 | return _authService.RefreshAsync(body, query, headers, cancellationToken); 190 | } 191 | 192 | public Result Refresh(IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 193 | { 194 | return _authService.Refresh(body, query, headers, cancellationToken); 195 | } 196 | 197 | public Task RequestPasswordResetAsync(string email, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 198 | { 199 | return _authService.RequestPasswordResetAsync(email, body, query, headers, cancellationToken); 200 | } 201 | 202 | public Result RequestPasswordReset(string email, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 203 | { 204 | return _authService.RequestPasswordReset(email, body, query, headers, cancellationToken); 205 | } 206 | 207 | public Task> ConfirmPasswordResetAsync(string passwordResetToken, string password, string passwordConfirm, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 208 | { 209 | return _authService.ConfirmPasswordResetAsync(passwordResetToken, password, passwordConfirm, body, query, headers, cancellationToken); 210 | } 211 | 212 | public Result ConfirmPasswordReset(string passwordResetToken, string password, string passwordConfirm, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 213 | { 214 | return _authService.ConfirmPasswordReset(passwordResetToken, password, passwordConfirm, body, query, headers, cancellationToken); 215 | } 216 | 217 | public Task RequestVerificationAsync(string email, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 218 | { 219 | return _authService.RequestVerificationAsync(email, body, query, headers, cancellationToken); 220 | } 221 | 222 | public Result RequestVerification(string email, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 223 | { 224 | return _authService.RequestVerification(email, body, query, headers, cancellationToken); 225 | } 226 | 227 | public Task> ConfirmVerificationAsync(string token, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 228 | { 229 | return _authService.ConfirmVerificationAsync(token, body, query, headers, cancellationToken); 230 | } 231 | 232 | public Result ConfirmVerification(string token, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 233 | { 234 | return _authService.ConfirmVerification(token, body, query, headers, cancellationToken); 235 | } 236 | 237 | public Task RequestEmailChangeAsync(string newEmail, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 238 | { 239 | return _authService.RequestEmailChangeAsync(newEmail, body, query, headers, cancellationToken); 240 | } 241 | 242 | public Result RequestEmailChange(string newEmail, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 243 | { 244 | return _authService.RequestEmailChange(newEmail, body, query, headers, cancellationToken); 245 | } 246 | 247 | public Task> ConfirmEmailChangeAsync(string emailChangeToken, string userPassword, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 248 | { 249 | return _authService.ConfirmEmailChangeAsync(emailChangeToken, userPassword, body, query, headers, cancellationToken); 250 | } 251 | 252 | public Result ConfirmEmailChange(string emailChangeToken, string userPassword, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 253 | { 254 | return _authService.ConfirmEmailChange(emailChangeToken, userPassword, body, query, headers, cancellationToken); 255 | } 256 | 257 | public Task>> GetExternalAuthenticationMethodsAsync(string userId, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 258 | { 259 | return _authService.GetExternalAuthenticationMethodsAsync(userId, query, headers, cancellationToken); 260 | } 261 | 262 | public Result> GetExternalAuthenticationMethods(string userId, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 263 | { 264 | return _authService.GetExternalAuthenticationMethods(userId, query, headers, cancellationToken); 265 | } 266 | 267 | public Task UnlinkExternalAuthenticationAsync(string userId, string provider, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 268 | { 269 | return _authService.UnlinkExternalAuthenticationAsync(userId, provider, body, query, headers, cancellationToken); 270 | } 271 | 272 | public Result UnlinkExternalAuthentication(string userId, string provider, IDictionary? body = null, IDictionary? query = null, IDictionary? headers = null, CancellationToken cancellationToken = default) 273 | { 274 | return _authService.UnlinkExternalAuthentication(userId, provider, body, query, headers, cancellationToken); 275 | } 276 | 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/Sse/SseClient.cs: -------------------------------------------------------------------------------- 1 | using pocketbase_csharp_sdk.Models; 2 | using System.Text; 3 | 4 | namespace pocketbase_csharp_sdk.Sse 5 | { 6 | public class SseClient 7 | { 8 | const string BasePath = "/api/realtime"; 9 | 10 | private readonly PocketBase client; 11 | private CancellationTokenSource? tokenSource = null; 12 | private Task? eventListenerTask = null; 13 | 14 | public string? Id { get; private set; } 15 | public bool IsConnected { get; private set; } = false; 16 | 17 | public Func CallbackAsync { get; private set; } 18 | 19 | public SseClient(PocketBase client, Func callbackAsync) 20 | { 21 | this.client = client; 22 | CallbackAsync = callbackAsync; 23 | } 24 | ~SseClient() 25 | { 26 | Disconnect(); 27 | } 28 | 29 | public async Task EnsureIsConnectedAsync() 30 | { 31 | if (!IsConnected) 32 | await ConnectAsync(); 33 | } 34 | 35 | public async Task ConnectAsync() 36 | { 37 | Disconnect(); 38 | tokenSource = new CancellationTokenSource(); 39 | try 40 | { 41 | eventListenerTask = ConnectEventStreamAsync(tokenSource.Token); 42 | 43 | while (!IsConnected) 44 | await Task.Delay(250); 45 | } 46 | catch { throw; } 47 | } 48 | 49 | public void Disconnect() 50 | { 51 | tokenSource?.Cancel(); 52 | tokenSource?.Dispose(); 53 | tokenSource = null; 54 | 55 | try { eventListenerTask?.Dispose(); } 56 | catch { } 57 | eventListenerTask = null; 58 | 59 | IsConnected = false; 60 | Id = null; 61 | } 62 | 63 | private async Task ConnectEventStreamAsync(CancellationToken token) 64 | { 65 | var httpClient = new HttpClient 66 | { 67 | Timeout = Timeout.InfiniteTimeSpan 68 | }; 69 | httpClient.DefaultRequestHeaders.ConnectionClose = false; 70 | try 71 | { 72 | 73 | var response = await httpClient.GetAsync(client.BuildUrl(BasePath).ToString(), 74 | HttpCompletionOption.ResponseHeadersRead, 75 | token); 76 | if (!response.IsSuccessStatusCode) 77 | throw new Exception("Unable to connect the stream"); 78 | 79 | var isTextEventStream = response.Content.Headers.ContentType?.MediaType == "text/event-stream"; 80 | 81 | if (!isTextEventStream) 82 | throw new InvalidOperationException("Invalid resource content type"); 83 | 84 | //TODO this never completes 85 | var stream = await response.Content.ReadAsStreamAsync(token); 86 | var buffer = new byte[4096]; 87 | while (!token.IsCancellationRequested) 88 | { 89 | var readCount = await stream.ReadAsync(buffer, token); 90 | if (readCount > 0) 91 | { 92 | var data = Encoding.UTF8.GetString(buffer, 0, readCount); 93 | var sseMessage = await SseMessage.FromReceivedMessageAsync(data); 94 | if (sseMessage != null) 95 | { 96 | if (sseMessage.Id != null && sseMessage.Event == "PB_CONNECT") 97 | { 98 | Id = sseMessage.Id; 99 | IsConnected = true; 100 | } 101 | await CallbackAsync(sseMessage); 102 | } 103 | } 104 | await Task.Delay(125, token); 105 | } 106 | } 107 | finally 108 | { 109 | httpClient.Dispose(); 110 | IsConnected = false; 111 | Id = null; 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/pocketbase-csharp-sdk - Backup.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.1.0-prerelease 5 | net6.0;net7.0 6 | pocketbase_csharp_sdk 7 | enable 8 | enable 9 | https://github.com/PRCV1/pocketbase-csharp-sdk 10 | https://github.com/PRCV1/pocketbase-csharp-sdk 11 | git 12 | PocketBase C# SDK 13 | Lukas Müller 14 | Multiplatform C# SDK for PocketBase 15 | True 16 | README.md 17 | True 18 | snupkg 19 | LICENSE.txt 20 | True 21 | 22 | 23 | 24 | 25 | True 26 | \ 27 | 28 | 29 | True 30 | \ 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /pocketbase-csharp-sdk/pocketbase-csharp-sdk.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1.3.0-prerelease 5 | pocketbase_csharp_sdk 6 | enable 7 | enable 8 | https://github.com/PRCV1/pocketbase-csharp-sdk 9 | https://github.com/PRCV1/pocketbase-csharp-sdk 10 | git 11 | PocketBase C# SDK 12 | Lukas Müller 13 | Multiplatform C# SDK for PocketBase 14 | True 15 | README.md 16 | True 17 | snupkg 18 | LICENSE.txt 19 | True 20 | net6.0 21 | latest 22 | 23 | 24 | 25 | 26 | True 27 | \ 28 | 29 | 30 | True 31 | \ 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | --------------------------------------------------------------------------------