(ctx context.Context, req *updateFilesRequest)
| 834 | } |
| 835 | |
| 836 | func (s *server) updateFiles(ctx context.Context, req *updateFilesRequest) (*updateFilesResponse, error) { |
| 837 | // Theoretically, the flow here is something like: |
| 838 | // 1. User calls getTempCredentials and gets |
| 839 | // 2. User PUTs some arbitrary number files to that location |
| 840 | // - This seems like an attack vector, but a short expiration should mitigate? |
| 841 | // 3. User calls this endpoint, noting the files they uploaded (at random names) and their checksums |
| 842 | // 4. Server (us! right here!) moves those files into their permanent locations |
| 843 | |
| 844 | gID := db.GraphID(req.GraphUUID) |
| 845 | |
| 846 | dbTxID, err := s.db.Tx(ctx, gID) |
| 847 | if err != nil { |
| 848 | return nil, httperr.Internal("failed to load TX: %w", err) |
| 849 | } |
| 850 | |
| 851 | // This doesn't seem to actually be a part of the API, so it might be fine to |
| 852 | // omit, but the real API does namespace things by user ID. |
| 853 | userID, err := s.getUserID(ctx) |
| 854 | if err != nil { |
| 855 | return nil, httperr.Unauthorized("failed to load user ID: %w", err) |
| 856 | } |
| 857 | var successFiles []string |
| 858 | for id, tup := range req.Files { |
| 859 | srcPath, checksumStr := tup[0], tup[1] |
| 860 | checksum, err := hex.DecodeString(checksumStr) |
| 861 | if err != nil { |
| 862 | return nil, httperr.BadRequest("checksum %q wasn't hex-encoded", checksumStr) |
| 863 | } |
| 864 | if n := len(checksum); n != 16 { |
| 865 | return nil, httperr.BadRequest("checksum was %d bytes, expected 16 bytes", n) |
| 866 | } |
| 867 | dstPath := path.Join(string(userID), req.GraphUUID, id) |
| 868 | moveMeta, err := s.blob.Move(ctx, srcPath, dstPath) |
| 869 | if err != nil { |
| 870 | return nil, httperr.Internal("failed to move temp file: %w", err) |
| 871 | } |
| 872 | if err := s.db.SetFileMeta(ctx, gID, &db.FileMeta{ |
| 873 | ID: db.FileID(id), |
| 874 | BlobPath: path.Join(s.blob.Bucket(), dstPath), |
| 875 | Checksum: checksum, |
| 876 | Size: moveMeta.Size, |
| 877 | LastModifiedAt: moveMeta.LastModified, |
| 878 | // TODO: Figure out transactions more generally, the pattern wasn't obvious to me. |
| 879 | LastModifiedTX: db.Tx(req.Txid), |
| 880 | }); err != nil { |
| 881 | return nil, httperr.Internal("failed to record file: %w", err) |
| 882 | } |
| 883 | successFiles = append(successFiles, dstPath) |
| 884 | } |
| 885 | |
| 886 | curTX := max(req.Txid, int64(dbTxID)) + 1 |
| 887 | if err := s.db.SetTx(ctx, gID, db.Tx(curTX)); err != nil { |
| 888 | return nil, httperr.Internal("failed to update tx: %w", err) |
| 889 | } |
| 890 | |
| 891 | return &updateFilesResponse{ |
| 892 | TXId: curTX, |
| 893 | // NOTE: Not sure in what case we'd want to use this. I guess maybe to only |
nothing calls this directly
no test coverage detected