Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 133 additions & 3 deletions PocketBaseSharp/PocketBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

namespace PocketBaseSharp
{
/// <summary>
/// Main client class for interacting with PocketBase backend services.
/// Provides access to all PocketBase APIs including authentication, collections, real-time subscriptions, and more.
/// </summary>
public class PocketBase
{
#region Private Fields
Expand All @@ -20,31 +24,93 @@ public class PocketBase
#endregion

#region Events
/// <summary>
/// Event handler delegate for intercepting HTTP requests before they are sent.
/// </summary>
/// <param name="sender">The PocketBase instance</param>
/// <param name="e">Request event arguments containing the request details</param>
/// <returns>The potentially modified HTTP request message</returns>
public delegate HttpRequestMessage BeforeSendEventHandler(object sender, RequestEventArgs e);

/// <summary>
/// Event fired before each HTTP request is sent, allowing for request modification.
/// </summary>
public event BeforeSendEventHandler? BeforeSend;

/// <summary>
/// Event handler delegate for processing HTTP responses after they are received.
/// </summary>
/// <param name="sender">The PocketBase instance</param>
/// <param name="e">Response event arguments containing the response details</param>
public delegate void AfterSendEventHandler(object sender, ResponseEventArgs e);

/// <summary>
/// Event fired after each HTTP response is received, allowing for response processing.
/// </summary>
public event AfterSendEventHandler? AfterSend;
#endregion


/// <summary>
/// Gets the authentication store for managing user authentication state.
/// </summary>
public AuthStore AuthStore { private set; get; }

/// <summary>
/// Gets the admin service for managing administrative operations.
/// </summary>
public AdminService Admin { private set; get; }

/// <summary>
/// Gets the user service for managing user operations.
/// </summary>
public UserService User { private set; get; }

/// <summary>
/// Gets the log service for accessing request logs and statistics.
/// </summary>
public LogService Log { private set; get; }

/// <summary>
/// Gets the settings service for managing application settings.
/// </summary>
public SettingsService Settings { private set; get; }

/// <summary>
/// Gets the collection service for managing database collections.
/// </summary>
public CollectionService Collections { private set; get; }
//public RecordService Records { private set; get; }

/// <summary>
/// Gets the real-time service for managing WebSocket connections and subscriptions.
/// </summary>
public RealTimeService RealTime { private set; get; }

/// <summary>
/// Gets the health service for checking server health status.
/// </summary>
public HealthService Health { private set; get; }

/// <summary>
/// Gets the backup service for managing database backups.
/// </summary>
public BackupService Backup { private set; get; }

/// <summary>
/// Gets the batch service for performing multiple operations in a single request.
/// </summary>
public BatchService Batch { private set; get; }

private readonly string _baseUrl;
private readonly string _language;
private readonly HttpClient _httpClient;

/// <summary>
/// Initializes a new instance of the PocketBase client.
/// </summary>
/// <param name="baseUrl">The base URL of the PocketBase server (e.g., "http://localhost:8090")</param>
/// <param name="authStore">Optional authentication store for managing auth state. If null, a new AuthStore will be created</param>
/// <param name="language">The preferred language for API responses (default: "en-US")</param>
/// <param name="httpClient">Optional HttpClient instance. If null, a new HttpClient will be created</param>
public PocketBase(string baseUrl, AuthStore? authStore = null, string language = "en-US", HttpClient? httpClient = null)
{
this._baseUrl = baseUrl;
Expand All @@ -64,12 +130,24 @@ public PocketBase(string baseUrl, AuthStore? authStore = null, string language =
Batch = new BatchService(this);
}

/// <summary>
/// Creates an authentication service for a specific collection that stores auth records.
/// </summary>
/// <typeparam name="T">The type of the auth model, must implement IBaseAuthModel</typeparam>
/// <param name="collectionName">The name of the collection containing auth records</param>
/// <returns>A CollectionAuthService instance for the specified collection</returns>
public CollectionAuthService<T> AuthCollection<T>(string collectionName)
where T : IBaseAuthModel
{
return new CollectionAuthService<T>(this, collectionName);
}

/// <summary>
/// Gets or creates a record service for the specified collection.
/// Services are cached and reused for the same collection name.
/// </summary>
/// <param name="collectionName">The name of the collection</param>
/// <returns>A RecordService instance for the specified collection</returns>
public RecordService Collection(string collectionName)
{
if (recordServices.ContainsKey(collectionName))
Expand All @@ -90,7 +168,18 @@ public BatchBuilder CreateBatch()
return Batch.CreateBatch();
}



/// <summary>
/// Sends an asynchronous HTTP request to the PocketBase API.
/// </summary>
/// <param name="path">The API endpoint path (without base URL)</param>
/// <param name="method">The HTTP method to use for the request</param>
/// <param name="headers">Optional HTTP headers to include with the request</param>
/// <param name="query">Optional query parameters to append to the URL</param>
/// <param name="body">Optional request body data</param>
/// <param name="files">Optional files to upload with the request</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation</param>
/// <returns>A Result indicating success or failure of the operation</returns>
public async Task<Result> SendAsync(string path, HttpMethod method, IDictionary<string, string>? headers = null, IDictionary<string, object?>? query = null, IDictionary<string, object>? body = null, IEnumerable<IFile>? files = null, CancellationToken cancellationToken = default)
{
headers ??= new Dictionary<string, string>();
Expand Down Expand Up @@ -140,6 +229,17 @@ public async Task<Result> SendAsync(string path, HttpMethod method, IDictionary<
}
}

/// <summary>
/// Sends a synchronous HTTP request to the PocketBase API.
/// </summary>
/// <param name="path">The API endpoint path (without base URL)</param>
/// <param name="method">The HTTP method to use for the request</param>
/// <param name="headers">Optional HTTP headers to include with the request</param>
/// <param name="query">Optional query parameters to append to the URL</param>
/// <param name="body">Optional request body data</param>
/// <param name="files">Optional files to upload with the request</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation</param>
/// <returns>A Result indicating success or failure of the operation</returns>
public Result Send(string path, HttpMethod method, IDictionary<string, string>? headers = null, IDictionary<string, object?>? query = null, IDictionary<string, object>? body = null, IEnumerable<IFile>? files = null, CancellationToken cancellationToken = default)
{
//RETURN RESULT
Expand Down Expand Up @@ -187,6 +287,18 @@ public Result Send(string path, HttpMethod method, IDictionary<string, string>?
}
}

/// <summary>
/// Sends an asynchronous HTTP request to the PocketBase API and deserializes the response to the specified type.
/// </summary>
/// <typeparam name="T">The type to deserialize the response to</typeparam>
/// <param name="path">The API endpoint path (without base URL)</param>
/// <param name="method">The HTTP method to use for the request</param>
/// <param name="headers">Optional HTTP headers to include with the request</param>
/// <param name="query">Optional query parameters to append to the URL</param>
/// <param name="body">Optional request body data</param>
/// <param name="files">Optional files to upload with the request</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation</param>
/// <returns>A Result containing the deserialized response of type T or an error</returns>
public async Task<Result<T>> SendAsync<T>(string path, HttpMethod method, IDictionary<string, string>? headers = null, IDictionary<string, object?>? query = null, IDictionary<string, object>? body = null, IEnumerable<IFile>? files = null, CancellationToken cancellationToken = default)
{
headers ??= new Dictionary<string, string>();
Expand Down Expand Up @@ -238,6 +350,18 @@ public async Task<Result<T>> SendAsync<T>(string path, HttpMethod method, IDicti
}
}

/// <summary>
/// Sends a synchronous HTTP request to the PocketBase API and deserializes the response to the specified type.
/// </summary>
/// <typeparam name="T">The type to deserialize the response to</typeparam>
/// <param name="path">The API endpoint path (without base URL)</param>
/// <param name="method">The HTTP method to use for the request</param>
/// <param name="headers">Optional HTTP headers to include with the request</param>
/// <param name="query">Optional query parameters to append to the URL</param>
/// <param name="body">Optional request body data</param>
/// <param name="files">Optional files to upload with the request</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation</param>
/// <returns>A Result containing the deserialized response of type T or an error</returns>
public Result<T> Send<T>(string path, HttpMethod method, IDictionary<string, string>? headers = null, IDictionary<string, object?>? query = null, IDictionary<string, object>? body = null, IEnumerable<IFile>? files = null, CancellationToken cancellationToken = default)
{
headers ??= new Dictionary<string, string>();
Expand Down Expand Up @@ -334,6 +458,12 @@ private HttpRequestMessage CreateRequest(Uri url, HttpMethod method, IDictionary
return request;
}

/// <summary>
/// Builds a complete URL for the specified API path with optional query parameters.
/// </summary>
/// <param name="path">The API endpoint path to append to the base URL</param>
/// <param name="queryParameters">Optional query parameters to append to the URL</param>
/// <returns>A complete URI for the API request</returns>
public Uri BuildUrl(string path, IDictionary<string, object?>? queryParameters = null)
{
var url = _baseUrl + (_baseUrl.EndsWith("/") ? "" : "/");
Expand Down
8 changes: 8 additions & 0 deletions PocketBaseSharp/Services/AdminService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@

namespace PocketBaseSharp.Services
{
/// <summary>
/// Service for managing administrative operations and authentication.
/// Provides functionality for admin user management in PocketBase.
/// </summary>
public class AdminService : BaseAuthService<AdminAuthModel>
{

protected override string BasePath(string? url = null) => "/api/admins";

/// <summary>
/// Initializes a new instance of the AdminService class.
/// </summary>
/// <param name="client">The PocketBase client instance</param>
public AdminService(PocketBase client) : base(client)
{
}
Expand Down
13 changes: 13 additions & 0 deletions PocketBaseSharp/Services/BackupService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,18 @@

namespace PocketBaseSharp.Services
{
/// <summary>
/// Service for managing database backups and backup operations.
/// Provides functionality to retrieve and manage PocketBase backup files.
/// </summary>
public class BackupService : BaseService
{
readonly PocketBase _pocketBase;

/// <summary>
/// Initializes a new instance of the BackupService class.
/// </summary>
/// <param name="pocketBase">The PocketBase client instance</param>
public BackupService(PocketBase pocketBase)
{
this._pocketBase = pocketBase;
Expand All @@ -22,6 +31,10 @@ protected override string BasePath(string? path = null)
return path ?? string.Empty;
}

/// <summary>
/// Retrieves the complete list of available backups from the server.
/// </summary>
/// <returns>A Result containing an enumerable of BackupModel objects representing all available backups</returns>
public async Task<Result<IEnumerable<BackupModel>>> GetFullListAsync()
{
var b = await _pocketBase.SendAsync<IEnumerable<BackupModel>>("/api/backups", HttpMethod.Get);
Expand Down
27 changes: 27 additions & 0 deletions PocketBaseSharp/Services/Base/BaseCrudService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,33 @@

namespace PocketBaseSharp.Services.Base
{
/// <summary>
/// Abstract base class for services that provide CRUD (Create, Read, Update, Delete) operations.
/// Provides standard implementation for common database operations on typed models.
/// </summary>
/// <typeparam name="T">The type of model this service manages</typeparam>
public abstract class BaseCrudService<T> : BaseService
{
private readonly PocketBase _client;

/// <summary>
/// Initializes a new instance of the BaseCrudService class.
/// </summary>
/// <param name="client">The PocketBase client instance</param>
protected BaseCrudService(PocketBase client)
{
this._client = client;
}

/// <summary>
/// Synchronously retrieves a paginated list of records.
/// </summary>
/// <param name="page">The page number to retrieve (default: 1)</param>
/// <param name="perPage">The number of records per page (default: 30)</param>
/// <param name="filter">Optional filter expression to apply</param>
/// <param name="sort">Optional sort expression to apply</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation</param>
/// <returns>A Result containing the paginated collection or an error</returns>
public virtual Result<PagedCollectionModel<T>> List(int page = 1, int perPage = 30, string? filter = null, string? sort = null, CancellationToken cancellationToken = default)
{
var path = BasePath();
Expand All @@ -26,6 +44,15 @@ public virtual Result<PagedCollectionModel<T>> List(int page = 1, int perPage =
return _client.Send<PagedCollectionModel<T>>(path, HttpMethod.Get, query: query, cancellationToken: cancellationToken);
}

/// <summary>
/// Asynchronously retrieves a paginated list of records.
/// </summary>
/// <param name="page">The page number to retrieve (default: 1)</param>
/// <param name="perPage">The number of records per page (default: 30)</param>
/// <param name="filter">Optional filter expression to apply</param>
/// <param name="sort">Optional sort expression to apply</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation</param>
/// <returns>A Result containing the paginated collection or an error</returns>
public virtual Task<Result<PagedCollectionModel<T>>> ListAsync(int page = 1, int perPage = 30, string? filter = null, string? sort = null, CancellationToken cancellationToken = default)
{
var path = BasePath();
Expand Down
22 changes: 22 additions & 0 deletions PocketBaseSharp/Services/Base/BaseService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,34 @@

namespace PocketBaseSharp.Services.Base
{
/// <summary>
/// Abstract base class for all PocketBase service classes.
/// Provides common functionality for URL encoding, body construction, and property management.
/// </summary>
public abstract class BaseService
{
private readonly string[] _itemProperties;

/// <summary>
/// Initializes a new instance of the BaseService class.
/// </summary>
protected BaseService()
{
this._itemProperties = this.GetPropertyNames().ToArray();
}

/// <summary>
/// Gets the base API path for this service. Must be implemented by derived classes.
/// </summary>
/// <param name="path">Optional additional path segment</param>
/// <returns>The base API path for this service</returns>
protected abstract string BasePath(string? path = null);

/// <summary>
/// Constructs a request body dictionary from an object, excluding base model properties.
/// </summary>
/// <param name="item">The object to convert to a request body</param>
/// <returns>A dictionary containing the object's properties as key-value pairs</returns>
protected Dictionary<string, object> ConstructBody(object item)
{
var body = new Dictionary<string, object>();
Expand All @@ -39,6 +56,11 @@ private IEnumerable<string> GetPropertyNames()
select prop.Name;
}

/// <summary>
/// URL-encodes a parameter value for safe inclusion in URLs.
/// </summary>
/// <param name="param">The parameter value to encode</param>
/// <returns>The URL-encoded parameter value, or an empty string if the parameter is null</returns>
protected string UrlEncode(string? param)
{
return HttpUtility.UrlEncode(param) ?? "";
Expand Down
Loading