Loading console/console.pb.go +161 −170 File changed.Preview size limit exceeded, changes collapsed. Show changes console/console.proto +2 −4 Original line number Diff line number Diff line Loading @@ -216,7 +216,7 @@ service Console { } // Write a new storage object or replace an existing one. rpc WriteStorageObject (WriteStorageObjectRequest) returns (google.protobuf.Empty) { rpc WriteStorageObject (WriteStorageObjectRequest) returns (nakama.api.StorageObjectAck) { option (google.api.http).post = "/v2/console/storage/{collection}/{key}/{user_id}"; } } Loading Loading @@ -343,8 +343,6 @@ message StorageList { repeated nakama.api.StorageObject objects = 1; // Approximate total number of storage objects. int32 total_count = 2; // An (optional) cursor for paging results. bytes cursor = 3; } // Unlink a particular device ID from a user's account. Loading Loading @@ -466,7 +464,7 @@ message WriteStorageObjectRequest { // Owner user ID. string user_id = 3; // Value. google.protobuf.StringValue value = 4; string value = 4; // Version for OCC. string version = 5; // Read permission value. Loading console/console.swagger.json +23 −6 Original line number Diff line number Diff line Loading @@ -699,7 +699,7 @@ "200": { "description": "A successful response.", "schema": { "properties": {} "$ref": "#/definitions/apiStorageObjectAck" } } }, Loading Loading @@ -1278,6 +1278,28 @@ }, "description": "An object within the storage engine." }, "apiStorageObjectAck": { "type": "object", "properties": { "collection": { "type": "string", "description": "The collection which stores the object." }, "key": { "type": "string", "description": "The key of the object within the collection." }, "version": { "type": "string", "description": "The version hash of the object." }, "user_id": { "type": "string", "description": "The owner of the object." } }, "description": "A storage acknowledgement." }, "apiUser": { "type": "object", "properties": { Loading Loading @@ -1492,11 +1514,6 @@ "type": "integer", "format": "int32", "description": "Approximate total number of storage objects." }, "cursor": { "type": "string", "format": "byte", "description": "An (optional) cursor for paging results." } }, "description": "List of storage objects." Loading console/ui/src/api.gen.ts +17 −7 Original line number Diff line number Diff line Loading @@ -206,6 +206,17 @@ export interface ApiStorageObject { // The version hash of the object. version?: string; } /** A storage acknowledgement. */ export interface ApiStorageObjectAck { // The collection which stores the object. collection?: string; // The key of the object within the collection. key?: string; // The owner of the object. user_id?: string; // The version hash of the object. version?: string; } /** A user in the server. */ export interface ApiUser { // A URL for an avatar image. Loading Loading @@ -291,15 +302,15 @@ export interface ConsoleStatusList { } /** List of storage objects. */ export interface ConsoleStorageList { // An (optional) cursor for paging results. cursor?: string; // List of storage objects matching list/filter operation. objects?: Array<ApiStorageObject>; // Approximate total number of storage objects. total_count?: number; } /** A list of users. */ export interface ConsoleUserList { // A cursor to fetch more results. cursor?: string; // Approximate total number of users. total_count?: number; // A list of users. users?: Array<ApiUser>; } Loading Loading @@ -759,7 +770,7 @@ export const NakamaApi = (configuration: ConfigurationParameters = { return this.doFetch(urlPath, "DELETE", queryParams, _body, options) }, /** Write a new storage object or replace an existing one. */ writeStorageObject(collection: string, key: string, userId: string, options: any = {}): Promise<any> { writeStorageObject(collection: string, key: string, userId: string, options: any = {}): Promise<ApiStorageObjectAck> { if (collection === null || collection === undefined) { throw new Error("'collection' is a required parameter but is null or undefined."); } Loading Loading @@ -820,14 +831,13 @@ export const NakamaApi = (configuration: ConfigurationParameters = { return this.doFetch(urlPath, "DELETE", queryParams, _body, options) }, /** List (and optionally filter) users. */ listUsers(filter?: string, banned?: boolean, tombstones?: boolean, cursor?: string, options: any = {}): Promise<ConsoleUserList> { listUsers(filter?: string, banned?: boolean, tombstones?: boolean, options: any = {}): Promise<ConsoleUserList> { const urlPath = "/v2/console/user"; const queryParams = { filter: filter, banned: banned, tombstones: tombstones, cursor: cursor, } as any; let _body = null; Loading server/console_storage.go +160 −3 Original line number Diff line number Diff line Loading @@ -16,23 +16,180 @@ package server import ( "context" "database/sql" "encoding/json" "github.com/gofrs/uuid" "github.com/golang/protobuf/ptypes/timestamp" "github.com/heroiclabs/nakama/api" "github.com/heroiclabs/nakama/console" "github.com/lib/pq" "go.uber.org/zap" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "github.com/golang/protobuf/ptypes/empty" ) func (s *ConsoleServer) DeleteStorage(ctx context.Context, in *empty.Empty) (*empty.Empty, error) { _, err := s.db.Exec("TRUNCATE TABLE storage") if err != nil { s.logger.Error("Failed to truncate Storage table.", zap.Error(err)) return nil, status.Error(codes.Internal, "An error occurred while deleting storage objects.") } return &empty.Empty{}, nil } func (s *ConsoleServer) DeleteStorageObject(ctx context.Context, in *console.DeleteStorageObjectRequest) (*empty.Empty, error) { if in.Collection == "" { return nil, status.Error(codes.InvalidArgument, "Requires a valid collection.") } if in.Key == "" { return nil, status.Error(codes.InvalidArgument, "Requires a valid key.") } userID, err := uuid.FromString(in.UserId) if err != nil { return nil, status.Error(codes.InvalidArgument, "Requires a valid user ID.") } code, err := StorageDeleteObjects(ctx, s.logger, s.db, true, map[uuid.UUID][]*api.DeleteStorageObjectId{ userID: []*api.DeleteStorageObjectId{ &api.DeleteStorageObjectId{ Collection: in.Collection, Key: in.Key, Version: in.Version, }, }, }) if err != nil { if code == codes.Internal { s.logger.Error("Failed to delete storage object.", zap.Error(err)) return nil, status.Error(codes.Internal, "An error occurred while deleting storage object.") } // OCC error or storage not found, no need to log. return nil, err } return &empty.Empty{}, nil } func (s *ConsoleServer) ListStorage(ctx context.Context, in *console.ListStorageRequest) (*console.StorageList, error) { return &console.StorageList{}, nil var userID *uuid.UUID if in.UserId != "" { uid, err := uuid.FromString(in.UserId) if err != nil { return nil, status.Error(codes.InvalidArgument, "Requires a valid user ID when provided.") } userID = &uid } func (s *ConsoleServer) WriteStorageObject(ctx context.Context, in *console.WriteStorageObjectRequest) (*empty.Empty, error) { return &empty.Empty{}, nil var query string params := make([]interface{}, 0, 1) if userID == nil { query = "SELECT collection, key, user_id, value, version, read, write, create_time, update_time FROM storage LIMIT 50" } else { query = "SELECT collection, key, user_id, value, version, read, write, create_time, update_time FROM storage WHERE user_id = $1 LIMIT 50" params = append(params, *userID) } rows, err := s.db.QueryContext(ctx, query, params...) if err != nil { s.logger.Error("Error querying storage objects.", zap.Any("in", in), zap.Error(err)) return nil, status.Error(codes.Internal, "An error occurred while trying to list storage objects.") } objects := make([]*api.StorageObject, 0, 50) for rows.Next() { o := &api.StorageObject{CreateTime: ×tamp.Timestamp{}, UpdateTime: ×tamp.Timestamp{}} var createTime pq.NullTime var updateTime pq.NullTime var userID sql.NullString if err := rows.Scan(&o.Collection, &o.Key, &userID, &o.Value, &o.Version, &o.PermissionRead, &o.PermissionWrite, &createTime, &updateTime); err != nil { rows.Close() s.logger.Error("Error scanning storage objects.", zap.Any("in", in), zap.Error(err)) return nil, status.Error(codes.Internal, "An error occurred while trying to list storage objects.") } o.CreateTime.Seconds = createTime.Time.Unix() o.UpdateTime.Seconds = updateTime.Time.Unix() o.UserId = userID.String objects = append(objects, o) } rows.Close() return &console.StorageList{ Objects: objects, TotalCount: countStorage(ctx, s.logger, s.db), }, nil } func (s *ConsoleServer) WriteStorageObject(ctx context.Context, in *console.WriteStorageObjectRequest) (*api.StorageObjectAck, error) { if in.Collection == "" { return nil, status.Error(codes.InvalidArgument, "Requires a valid collection.") } if in.Key == "" { return nil, status.Error(codes.InvalidArgument, "Requires a valid key.") } userID, err := uuid.FromString(in.UserId) if err != nil { return nil, status.Error(codes.InvalidArgument, "Requires a valid user ID.") } if in.PermissionRead != nil { permissionRead := in.PermissionRead.GetValue() if permissionRead < 0 || permissionRead > 2 { return nil, status.Error(codes.InvalidArgument, "Requires a valid read permission read if supplied (0-2).") } } if in.PermissionWrite != nil { permissionWrite := in.PermissionWrite.GetValue() if permissionWrite < 0 || permissionWrite > 1 { return nil, status.Error(codes.InvalidArgument, "Requires a valid write permission if supplied (0-1).") } } var maybeJSON map[string]interface{} if json.Unmarshal([]byte(in.Value), &maybeJSON) != nil { return nil, status.Error(codes.InvalidArgument, "Requires a valid JSON object value.") } acks, code, err := StorageWriteObjects(ctx, s.logger, s.db, true, map[uuid.UUID][]*api.WriteStorageObject{ userID: []*api.WriteStorageObject{ &api.WriteStorageObject{ Collection: in.Collection, Key: in.Key, Value: in.Value, Version: in.Version, PermissionRead: in.PermissionRead, PermissionWrite: in.PermissionWrite, }, }, }) if err != nil { if code == codes.Internal { s.logger.Error("Failed to write storage object.", zap.Error(err)) return nil, status.Error(codes.Internal, "An error occurred while writing storage object.") } // OCC error, no need to log. return nil, err } if acks == nil || len(acks.Acks) != 1 { s.logger.Error("Failed to get storage object acks.") return nil, status.Error(codes.Internal, "An error occurred while writing storage object.") } return acks.Acks[0], nil } func countStorage(ctx context.Context, logger *zap.Logger, db *sql.DB) int32 { var count int if err := db.QueryRowContext(ctx, "SELECT count(collection) FROM storage").Scan(&count); err != nil { logger.Error("Error counting storage objects.", zap.Error(err)) } return int32(count) } Loading
console/console.pb.go +161 −170 File changed.Preview size limit exceeded, changes collapsed. Show changes
console/console.proto +2 −4 Original line number Diff line number Diff line Loading @@ -216,7 +216,7 @@ service Console { } // Write a new storage object or replace an existing one. rpc WriteStorageObject (WriteStorageObjectRequest) returns (google.protobuf.Empty) { rpc WriteStorageObject (WriteStorageObjectRequest) returns (nakama.api.StorageObjectAck) { option (google.api.http).post = "/v2/console/storage/{collection}/{key}/{user_id}"; } } Loading Loading @@ -343,8 +343,6 @@ message StorageList { repeated nakama.api.StorageObject objects = 1; // Approximate total number of storage objects. int32 total_count = 2; // An (optional) cursor for paging results. bytes cursor = 3; } // Unlink a particular device ID from a user's account. Loading Loading @@ -466,7 +464,7 @@ message WriteStorageObjectRequest { // Owner user ID. string user_id = 3; // Value. google.protobuf.StringValue value = 4; string value = 4; // Version for OCC. string version = 5; // Read permission value. Loading
console/console.swagger.json +23 −6 Original line number Diff line number Diff line Loading @@ -699,7 +699,7 @@ "200": { "description": "A successful response.", "schema": { "properties": {} "$ref": "#/definitions/apiStorageObjectAck" } } }, Loading Loading @@ -1278,6 +1278,28 @@ }, "description": "An object within the storage engine." }, "apiStorageObjectAck": { "type": "object", "properties": { "collection": { "type": "string", "description": "The collection which stores the object." }, "key": { "type": "string", "description": "The key of the object within the collection." }, "version": { "type": "string", "description": "The version hash of the object." }, "user_id": { "type": "string", "description": "The owner of the object." } }, "description": "A storage acknowledgement." }, "apiUser": { "type": "object", "properties": { Loading Loading @@ -1492,11 +1514,6 @@ "type": "integer", "format": "int32", "description": "Approximate total number of storage objects." }, "cursor": { "type": "string", "format": "byte", "description": "An (optional) cursor for paging results." } }, "description": "List of storage objects." Loading
console/ui/src/api.gen.ts +17 −7 Original line number Diff line number Diff line Loading @@ -206,6 +206,17 @@ export interface ApiStorageObject { // The version hash of the object. version?: string; } /** A storage acknowledgement. */ export interface ApiStorageObjectAck { // The collection which stores the object. collection?: string; // The key of the object within the collection. key?: string; // The owner of the object. user_id?: string; // The version hash of the object. version?: string; } /** A user in the server. */ export interface ApiUser { // A URL for an avatar image. Loading Loading @@ -291,15 +302,15 @@ export interface ConsoleStatusList { } /** List of storage objects. */ export interface ConsoleStorageList { // An (optional) cursor for paging results. cursor?: string; // List of storage objects matching list/filter operation. objects?: Array<ApiStorageObject>; // Approximate total number of storage objects. total_count?: number; } /** A list of users. */ export interface ConsoleUserList { // A cursor to fetch more results. cursor?: string; // Approximate total number of users. total_count?: number; // A list of users. users?: Array<ApiUser>; } Loading Loading @@ -759,7 +770,7 @@ export const NakamaApi = (configuration: ConfigurationParameters = { return this.doFetch(urlPath, "DELETE", queryParams, _body, options) }, /** Write a new storage object or replace an existing one. */ writeStorageObject(collection: string, key: string, userId: string, options: any = {}): Promise<any> { writeStorageObject(collection: string, key: string, userId: string, options: any = {}): Promise<ApiStorageObjectAck> { if (collection === null || collection === undefined) { throw new Error("'collection' is a required parameter but is null or undefined."); } Loading Loading @@ -820,14 +831,13 @@ export const NakamaApi = (configuration: ConfigurationParameters = { return this.doFetch(urlPath, "DELETE", queryParams, _body, options) }, /** List (and optionally filter) users. */ listUsers(filter?: string, banned?: boolean, tombstones?: boolean, cursor?: string, options: any = {}): Promise<ConsoleUserList> { listUsers(filter?: string, banned?: boolean, tombstones?: boolean, options: any = {}): Promise<ConsoleUserList> { const urlPath = "/v2/console/user"; const queryParams = { filter: filter, banned: banned, tombstones: tombstones, cursor: cursor, } as any; let _body = null; Loading
server/console_storage.go +160 −3 Original line number Diff line number Diff line Loading @@ -16,23 +16,180 @@ package server import ( "context" "database/sql" "encoding/json" "github.com/gofrs/uuid" "github.com/golang/protobuf/ptypes/timestamp" "github.com/heroiclabs/nakama/api" "github.com/heroiclabs/nakama/console" "github.com/lib/pq" "go.uber.org/zap" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "github.com/golang/protobuf/ptypes/empty" ) func (s *ConsoleServer) DeleteStorage(ctx context.Context, in *empty.Empty) (*empty.Empty, error) { _, err := s.db.Exec("TRUNCATE TABLE storage") if err != nil { s.logger.Error("Failed to truncate Storage table.", zap.Error(err)) return nil, status.Error(codes.Internal, "An error occurred while deleting storage objects.") } return &empty.Empty{}, nil } func (s *ConsoleServer) DeleteStorageObject(ctx context.Context, in *console.DeleteStorageObjectRequest) (*empty.Empty, error) { if in.Collection == "" { return nil, status.Error(codes.InvalidArgument, "Requires a valid collection.") } if in.Key == "" { return nil, status.Error(codes.InvalidArgument, "Requires a valid key.") } userID, err := uuid.FromString(in.UserId) if err != nil { return nil, status.Error(codes.InvalidArgument, "Requires a valid user ID.") } code, err := StorageDeleteObjects(ctx, s.logger, s.db, true, map[uuid.UUID][]*api.DeleteStorageObjectId{ userID: []*api.DeleteStorageObjectId{ &api.DeleteStorageObjectId{ Collection: in.Collection, Key: in.Key, Version: in.Version, }, }, }) if err != nil { if code == codes.Internal { s.logger.Error("Failed to delete storage object.", zap.Error(err)) return nil, status.Error(codes.Internal, "An error occurred while deleting storage object.") } // OCC error or storage not found, no need to log. return nil, err } return &empty.Empty{}, nil } func (s *ConsoleServer) ListStorage(ctx context.Context, in *console.ListStorageRequest) (*console.StorageList, error) { return &console.StorageList{}, nil var userID *uuid.UUID if in.UserId != "" { uid, err := uuid.FromString(in.UserId) if err != nil { return nil, status.Error(codes.InvalidArgument, "Requires a valid user ID when provided.") } userID = &uid } func (s *ConsoleServer) WriteStorageObject(ctx context.Context, in *console.WriteStorageObjectRequest) (*empty.Empty, error) { return &empty.Empty{}, nil var query string params := make([]interface{}, 0, 1) if userID == nil { query = "SELECT collection, key, user_id, value, version, read, write, create_time, update_time FROM storage LIMIT 50" } else { query = "SELECT collection, key, user_id, value, version, read, write, create_time, update_time FROM storage WHERE user_id = $1 LIMIT 50" params = append(params, *userID) } rows, err := s.db.QueryContext(ctx, query, params...) if err != nil { s.logger.Error("Error querying storage objects.", zap.Any("in", in), zap.Error(err)) return nil, status.Error(codes.Internal, "An error occurred while trying to list storage objects.") } objects := make([]*api.StorageObject, 0, 50) for rows.Next() { o := &api.StorageObject{CreateTime: ×tamp.Timestamp{}, UpdateTime: ×tamp.Timestamp{}} var createTime pq.NullTime var updateTime pq.NullTime var userID sql.NullString if err := rows.Scan(&o.Collection, &o.Key, &userID, &o.Value, &o.Version, &o.PermissionRead, &o.PermissionWrite, &createTime, &updateTime); err != nil { rows.Close() s.logger.Error("Error scanning storage objects.", zap.Any("in", in), zap.Error(err)) return nil, status.Error(codes.Internal, "An error occurred while trying to list storage objects.") } o.CreateTime.Seconds = createTime.Time.Unix() o.UpdateTime.Seconds = updateTime.Time.Unix() o.UserId = userID.String objects = append(objects, o) } rows.Close() return &console.StorageList{ Objects: objects, TotalCount: countStorage(ctx, s.logger, s.db), }, nil } func (s *ConsoleServer) WriteStorageObject(ctx context.Context, in *console.WriteStorageObjectRequest) (*api.StorageObjectAck, error) { if in.Collection == "" { return nil, status.Error(codes.InvalidArgument, "Requires a valid collection.") } if in.Key == "" { return nil, status.Error(codes.InvalidArgument, "Requires a valid key.") } userID, err := uuid.FromString(in.UserId) if err != nil { return nil, status.Error(codes.InvalidArgument, "Requires a valid user ID.") } if in.PermissionRead != nil { permissionRead := in.PermissionRead.GetValue() if permissionRead < 0 || permissionRead > 2 { return nil, status.Error(codes.InvalidArgument, "Requires a valid read permission read if supplied (0-2).") } } if in.PermissionWrite != nil { permissionWrite := in.PermissionWrite.GetValue() if permissionWrite < 0 || permissionWrite > 1 { return nil, status.Error(codes.InvalidArgument, "Requires a valid write permission if supplied (0-1).") } } var maybeJSON map[string]interface{} if json.Unmarshal([]byte(in.Value), &maybeJSON) != nil { return nil, status.Error(codes.InvalidArgument, "Requires a valid JSON object value.") } acks, code, err := StorageWriteObjects(ctx, s.logger, s.db, true, map[uuid.UUID][]*api.WriteStorageObject{ userID: []*api.WriteStorageObject{ &api.WriteStorageObject{ Collection: in.Collection, Key: in.Key, Value: in.Value, Version: in.Version, PermissionRead: in.PermissionRead, PermissionWrite: in.PermissionWrite, }, }, }) if err != nil { if code == codes.Internal { s.logger.Error("Failed to write storage object.", zap.Error(err)) return nil, status.Error(codes.Internal, "An error occurred while writing storage object.") } // OCC error, no need to log. return nil, err } if acks == nil || len(acks.Acks) != 1 { s.logger.Error("Failed to get storage object acks.") return nil, status.Error(codes.Internal, "An error occurred while writing storage object.") } return acks.Acks[0], nil } func countStorage(ctx context.Context, logger *zap.Logger, db *sql.DB) int32 { var count int if err := db.QueryRowContext(ctx, "SELECT count(collection) FROM storage").Scan(&count); err != nil { logger.Error("Error counting storage objects.", zap.Error(err)) } return int32(count) }