diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj
index 96b150e3fd..4c75faefbc 100644
--- a/src/Core/Core.csproj
+++ b/src/Core/Core.csproj
@@ -24,6 +24,7 @@
+
diff --git a/src/Core/Repositories/SqlServer/BaseRepository.cs b/src/Core/Repositories/BaseRepository.cs
similarity index 95%
rename from src/Core/Repositories/SqlServer/BaseRepository.cs
rename to src/Core/Repositories/BaseRepository.cs
index b86a78e647..86bff6a29b 100644
--- a/src/Core/Repositories/SqlServer/BaseRepository.cs
+++ b/src/Core/Repositories/BaseRepository.cs
@@ -1,7 +1,7 @@
using System;
using Dapper;
-namespace Bit.Core.Repositories.SqlServer
+namespace Bit.Core.Repositories
{
public abstract class BaseRepository
{
diff --git a/src/Core/Repositories/PostgreSql/BasePostgreSqlRepository.cs b/src/Core/Repositories/PostgreSql/BasePostgreSqlRepository.cs
new file mode 100644
index 0000000000..24c351364b
--- /dev/null
+++ b/src/Core/Repositories/PostgreSql/BasePostgreSqlRepository.cs
@@ -0,0 +1,28 @@
+using System.Text.RegularExpressions;
+using Dapper;
+
+namespace Bit.Core.Repositories.PostgreSql
+{
+ public abstract class BasePostgreSqlRepository : BaseRepository
+ {
+ static BasePostgreSqlRepository()
+ {
+ // Support snake case property names
+ DefaultTypeMap.MatchNamesWithUnderscores = true;
+ }
+
+ public BasePostgreSqlRepository(string connectionString, string readOnlyConnectionString)
+ : base(connectionString, readOnlyConnectionString)
+ { }
+
+ protected static string SnakeCase(string input)
+ {
+ if(string.IsNullOrWhiteSpace(input))
+ {
+ return input;
+ }
+ var startUnderscores = Regex.Match(input, @"^_+");
+ return startUnderscores + Regex.Replace(input, @"([a-z0-9])([A-Z])", "$1_$2").ToLowerInvariant();
+ }
+ }
+}
diff --git a/src/Core/Repositories/PostgreSql/Repository.cs b/src/Core/Repositories/PostgreSql/Repository.cs
new file mode 100644
index 0000000000..789f3810f4
--- /dev/null
+++ b/src/Core/Repositories/PostgreSql/Repository.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using Dapper;
+using Bit.Core.Models.Table;
+using Npgsql;
+
+namespace Bit.Core.Repositories.PostgreSql
+{
+ public abstract class Repository : BasePostgreSqlRepository, IRepository
+ where TId : IEquatable
+ where T : class, ITableObject
+ {
+ public Repository(string connectionString, string readOnlyConnectionString, string table)
+ : base(connectionString, readOnlyConnectionString)
+ {
+ if(!string.IsNullOrWhiteSpace(table))
+ {
+ Table = table;
+ }
+ else
+ {
+ Table = SnakeCase(typeof(T).Name);
+ }
+ }
+
+ protected string Table { get; private set; }
+
+ public virtual async Task GetByIdAsync(TId id)
+ {
+ using(var connection = new NpgsqlConnection(ConnectionString))
+ {
+ var results = await connection.QueryAsync(
+ $"{Table}_read_by_id",
+ new { id = id },
+ commandType: CommandType.StoredProcedure);
+
+ return results.SingleOrDefault();
+ }
+ }
+
+ public virtual async Task CreateAsync(T obj)
+ {
+ obj.SetNewId();
+ using(var connection = new NpgsqlConnection(ConnectionString))
+ {
+ var results = await connection.ExecuteAsync(
+ $"{Table}_create",
+ obj,
+ commandType: CommandType.StoredProcedure);
+ }
+ }
+
+ public virtual async Task ReplaceAsync(T obj)
+ {
+ using(var connection = new NpgsqlConnection(ConnectionString))
+ {
+ var results = await connection.ExecuteAsync(
+ $"{Table}_update",
+ obj,
+ commandType: CommandType.StoredProcedure);
+ }
+ }
+
+ public virtual async Task UpsertAsync(T obj)
+ {
+ if(obj.Id.Equals(default(TId)))
+ {
+ await CreateAsync(obj);
+ }
+ else
+ {
+ await ReplaceAsync(obj);
+ }
+ }
+
+ public virtual async Task DeleteAsync(T obj)
+ {
+ using(var connection = new NpgsqlConnection(ConnectionString))
+ {
+ await connection.ExecuteAsync(
+ $"{Table}_delete_by_id",
+ new { id = obj.Id },
+ commandType: CommandType.StoredProcedure);
+ }
+ }
+ }
+}
diff --git a/src/Sql/PostgreSQL/Functions/user_read_by_id.sql b/src/Sql/PostgreSQL/Functions/user_read_by_id.sql
new file mode 100644
index 0000000000..2f4e584c84
--- /dev/null
+++ b/src/Sql/PostgreSQL/Functions/user_read_by_id.sql
@@ -0,0 +1,14 @@
+CREATE OR REPLACE FUNCTION user_read_by_id
+(
+ id uuid
+)
+RETURNS SETOF "user"
+LANGUAGE 'sql'
+AS $BODY$
+ SELECT
+ *
+ FROM
+ "user"
+ WHERE
+ "id" = id;
+$BODY$;