diff --git a/gen/go/officeconvertapi/v1/conversion.pb.go b/gen/go/officeconvertapi/v1/conversion.pb.go index 1849792..0135ccb 100644 --- a/gen/go/officeconvertapi/v1/conversion.pb.go +++ b/gen/go/officeconvertapi/v1/conversion.pb.go @@ -259,7 +259,8 @@ func (x *Slide) GetImageUrl() string { // SlideDeck is the final structured conversion artifact. type SlideDeck struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState `protogen:"open.v1"` + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). ConversionId string `protobuf:"bytes,1,opt,name=conversion_id,json=conversionId,proto3" json:"conversion_id,omitempty"` SourceFilename string `protobuf:"bytes,2,opt,name=source_filename,json=sourceFilename,proto3" json:"source_filename,omitempty"` Slides []*Slide `protobuf:"bytes,3,rep,name=slides,proto3" json:"slides,omitempty"` @@ -397,7 +398,8 @@ func (x *CreateConversionRequest) GetResolution() ConversionResolution { // CreateConversionResponse returns upload details for the session. type CreateConversionResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState `protogen:"open.v1"` + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). ConversionId string `protobuf:"bytes,1,opt,name=conversion_id,json=conversionId,proto3" json:"conversion_id,omitempty"` UploadBucket string `protobuf:"bytes,2,opt,name=upload_bucket,json=uploadBucket,proto3" json:"upload_bucket,omitempty"` UploadObjectKey string `protobuf:"bytes,3,opt,name=upload_object_key,json=uploadObjectKey,proto3" json:"upload_object_key,omitempty"` @@ -474,8 +476,9 @@ func (x *CreateConversionResponse) GetExpiresAt() *timestamppb.Timestamp { // StartConversionRequest requests conversion of an already uploaded PPTX. type StartConversionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ConversionId string `protobuf:"bytes,1,opt,name=conversion_id,json=conversionId,proto3" json:"conversion_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). + ConversionId string `protobuf:"bytes,1,opt,name=conversion_id,json=conversionId,proto3" json:"conversion_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -519,9 +522,10 @@ func (x *StartConversionRequest) GetConversionId() string { // StartConversionResponse returns the first known status after enqueue. type StartConversionResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - ConversionId string `protobuf:"bytes,1,opt,name=conversion_id,json=conversionId,proto3" json:"conversion_id,omitempty"` - Status ConversionStatus `protobuf:"varint,2,opt,name=status,proto3,enum=officeconvertapi.v1.ConversionStatus" json:"status,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). + ConversionId string `protobuf:"bytes,1,opt,name=conversion_id,json=conversionId,proto3" json:"conversion_id,omitempty"` + Status ConversionStatus `protobuf:"varint,2,opt,name=status,proto3,enum=officeconvertapi.v1.ConversionStatus" json:"status,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -572,8 +576,9 @@ func (x *StartConversionResponse) GetStatus() ConversionStatus { // GetConversionStatusRequest asks for a specific conversion status. type GetConversionStatusRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ConversionId string `protobuf:"bytes,1,opt,name=conversion_id,json=conversionId,proto3" json:"conversion_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). + ConversionId string `protobuf:"bytes,1,opt,name=conversion_id,json=conversionId,proto3" json:"conversion_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -617,7 +622,8 @@ func (x *GetConversionStatusRequest) GetConversionId() string { // GetConversionStatusResponse returns current status and optional error info. type GetConversionStatusResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` + state protoimpl.MessageState `protogen:"open.v1"` + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). ConversionId string `protobuf:"bytes,1,opt,name=conversion_id,json=conversionId,proto3" json:"conversion_id,omitempty"` Status ConversionStatus `protobuf:"varint,2,opt,name=status,proto3,enum=officeconvertapi.v1.ConversionStatus" json:"status,omitempty"` ErrorMessage string `protobuf:"bytes,3,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` @@ -710,8 +716,9 @@ func (x *GetConversionStatusResponse) GetMaxProgress() int32 { // GetSlideDeckRequest fetches a completed deck. type GetSlideDeckRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ConversionId string `protobuf:"bytes,1,opt,name=conversion_id,json=conversionId,proto3" json:"conversion_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). + ConversionId string `protobuf:"bytes,1,opt,name=conversion_id,json=conversionId,proto3" json:"conversion_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -800,8 +807,9 @@ func (x *GetSlideDeckResponse) GetSlideDeck() *SlideDeck { // DeleteConversionRequest requests immediate cleanup. type DeleteConversionRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - ConversionId string `protobuf:"bytes,1,opt,name=conversion_id,json=conversionId,proto3" json:"conversion_id,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). + ConversionId string `protobuf:"bytes,1,opt,name=conversion_id,json=conversionId,proto3" json:"conversion_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -845,9 +853,10 @@ func (x *DeleteConversionRequest) GetConversionId() string { // DeleteConversionResponse confirms cleanup details. type DeleteConversionResponse struct { - state protoimpl.MessageState `protogen:"open.v1"` - ConversionId string `protobuf:"bytes,1,opt,name=conversion_id,json=conversionId,proto3" json:"conversion_id,omitempty"` - Deleted bool `protobuf:"varint,2,opt,name=deleted,proto3" json:"deleted,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). + ConversionId string `protobuf:"bytes,1,opt,name=conversion_id,json=conversionId,proto3" json:"conversion_id,omitempty"` + Deleted bool `protobuf:"varint,2,opt,name=deleted,proto3" json:"deleted,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } diff --git a/proto/officeconvertapi/v1/conversion.proto b/proto/officeconvertapi/v1/conversion.proto index b49bc43..d35234b 100644 --- a/proto/officeconvertapi/v1/conversion.proto +++ b/proto/officeconvertapi/v1/conversion.proto @@ -62,6 +62,7 @@ message Slide { // SlideDeck is the final structured conversion artifact. message SlideDeck { + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). string conversion_id = 1; string source_filename = 2; repeated Slide slides = 3; @@ -78,6 +79,7 @@ message CreateConversionRequest { // CreateConversionResponse returns upload details for the session. message CreateConversionResponse { + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). string conversion_id = 1; string upload_bucket = 2; string upload_object_key = 3; @@ -87,22 +89,26 @@ message CreateConversionResponse { // StartConversionRequest requests conversion of an already uploaded PPTX. message StartConversionRequest { + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). string conversion_id = 1; } // StartConversionResponse returns the first known status after enqueue. message StartConversionResponse { + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). string conversion_id = 1; ConversionStatus status = 2; } // GetConversionStatusRequest asks for a specific conversion status. message GetConversionStatusRequest { + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). string conversion_id = 1; } // GetConversionStatusResponse returns current status and optional error info. message GetConversionStatusResponse { + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). string conversion_id = 1; ConversionStatus status = 2; string error_message = 3; @@ -114,6 +120,7 @@ message GetConversionStatusResponse { // GetSlideDeckRequest fetches a completed deck. message GetSlideDeckRequest { + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). string conversion_id = 1; } @@ -124,11 +131,13 @@ message GetSlideDeckResponse { // DeleteConversionRequest requests immediate cleanup. message DeleteConversionRequest { + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). string conversion_id = 1; } // DeleteConversionResponse confirms cleanup details. message DeleteConversionResponse { + // Session identifier: KSUID in standard base62 text form. Well-formed values are at most 27 characters (see https://github.com/segmentio/ksuid). string conversion_id = 1; bool deleted = 2; } diff --git a/python/packages/server/pyproject.toml b/python/packages/server/pyproject.toml index d54c89f..8cc006c 100644 --- a/python/packages/server/pyproject.toml +++ b/python/packages/server/pyproject.toml @@ -8,6 +8,7 @@ dependencies = [ "connectrpc>=0.6.0", "minio>=7.2.18", "officeconvert", + "svix-ksuid>=0.7.0", "uvicorn>=0.35.0", ] diff --git a/python/packages/server/src/officeconvert_server/service.py b/python/packages/server/src/officeconvert_server/service.py index dde59f5..8093446 100644 --- a/python/packages/server/src/officeconvert_server/service.py +++ b/python/packages/server/src/officeconvert_server/service.py @@ -10,7 +10,6 @@ from pathlib import Path import shutil import tempfile import time -import uuid from connectrpc.code import Code from connectrpc.errors import ConnectError @@ -29,6 +28,7 @@ from officeconvert.conversion import ( RESOLUTION_UHD, ) from officeconvertapi.v1 import conversion_connect, conversion_pb2 +from ksuid import Ksuid from minio.error import S3Error @@ -75,8 +75,9 @@ class ConversionServiceImpl(conversion_connect.ConversionService): if resolution not in _RESOLUTION_PRESET_BY_PROTO: raise ConnectError(Code.INVALID_ARGUMENT, "resolution is invalid") - conversion_id = str(uuid.uuid4()) - bucket_name = f"oc-{conversion_id}" + ksuid = Ksuid() + conversion_id = str(ksuid) + bucket_name = f"oc-{bytes(ksuid).hex()}" upload_key = "input/source.pptx" expires_at = utc_now() + timedelta(seconds=self._config.s3_session_ttl_seconds) diff --git a/python/uv.lock b/python/uv.lock index 6697420..7ba7b1e 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -292,6 +292,7 @@ dependencies = [ { name = "connectrpc" }, { name = "minio" }, { name = "officeconvert" }, + { name = "svix-ksuid" }, { name = "uvicorn" }, ] @@ -300,6 +301,7 @@ requires-dist = [ { name = "connectrpc", specifier = ">=0.6.0" }, { name = "minio", specifier = ">=7.2.18" }, { name = "officeconvert", editable = "packages/officeconvert" }, + { name = "svix-ksuid", specifier = ">=0.7.0" }, { name = "uvicorn", specifier = ">=0.35.0" }, ] @@ -479,6 +481,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2d/d8/d6710bbb38f6a715135f7c8a8e5c6227d69299a2b7e989c81315a08054e7/pyqwest-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:a7b8d8ae51ccf6375a9e82e5b38d2129ee3121acf4933a37e541f4fe04a5f758", size = 4577924, upload-time = "2026-03-06T02:32:31.013Z" }, ] +[[package]] +name = "python-baseconv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/d0/9297d7d8dd74767b4d5560d834b30b2fff17d39987c23ed8656f476e0d9b/python-baseconv-1.2.2.tar.gz", hash = "sha256:0539f8bd0464013b05ad62e0a1673f0ac9086c76b43ebf9f833053527cd9931b", size = 4929, upload-time = "2019-04-04T19:28:57.17Z" } + [[package]] name = "python-pptx" version = "1.0.2" @@ -494,6 +502,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788, upload-time = "2024-08-07T17:33:28.192Z" }, ] +[[package]] +name = "svix-ksuid" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-baseconv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/f0/b534cc208ee8ea010254208345324d2e3d3c5d13a210d2249f9f11840f2f/svix_ksuid-0.7.0.tar.gz", hash = "sha256:e6b824437b5bb76d75163b928cbf9ae6c871d3e713e5de75cde89bae6a19bfe8", size = 5810, upload-time = "2026-03-14T02:44:51.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/51/922e6ad4da6b3f4863ea059db2391259c9247b9fe4f0c08da50eff4e1312/svix_ksuid-0.7.0-py3-none-any.whl", hash = "sha256:6bceae49f85e026c77a13e6de81bf4536c15039ecb160415e954403618c2a06f", size = 5607, upload-time = "2026-03-14T02:44:50.822Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0"