@@ -4,9 +4,12 @@ use sea_orm::entity::prelude::*;
44use sea_orm:: * ;
55use snafu:: prelude:: * ;
66
7- use crate :: database:: category:: { self , CategoryError , CategoryOperator } ;
7+ use std:: str:: FromStr ;
8+
9+ use crate :: database:: category:: { self , CategoryError } ;
810use crate :: database:: operation:: { Operation , OperationId , OperationLog , OperationType , Table } ;
911use crate :: database:: operator:: DatabaseOperator ;
12+ use crate :: extractors:: normalized_path:: { NormalizedPathAbsolute , NormalizedPathComponent } ;
1013use crate :: extractors:: user:: User ;
1114use crate :: routes:: content_folder:: ContentFolderForm ;
1215use crate :: state:: AppState ;
@@ -25,7 +28,9 @@ pub struct Model {
2528 pub id : i32 ,
2629 pub name : String ,
2730 #[ sea_orm( unique) ]
28- pub path : String ,
31+ // TODO: maybe we'd like relative paths in fact? Why did
32+ // we have a leading slash in the first place?
33+ pub path : NormalizedPathAbsolute ,
2934 pub category_id : i32 ,
3035 #[ sea_orm( belongs_to, from = "category_id" , to = "id" ) ]
3136 pub category : HasOne < category:: Entity > ,
@@ -40,10 +45,12 @@ impl ActiveModelBehavior for ActiveModel {}
4045#[ derive( Debug , Snafu ) ]
4146#[ snafu( visibility( pub ) ) ]
4247pub enum ContentFolderError {
43- #[ snafu( display( "There is already a content folder called `{name}`" ) ) ]
48+ #[ snafu( display( "There is already a content folder called `{name}` in the current folder. " ) ) ]
4449 NameTaken { name : String } ,
45- #[ snafu( display( "There is already a content folder in dir `{path}`" ) ) ]
46- PathTaken { path : String } ,
50+ #[ snafu( display( "The folder name is invalid. It must not contain slashes." ) ) ]
51+ NameInvalid ,
52+ #[ snafu( display( "The folder path must appear absolute" ) ) ]
53+ PathInvalid ,
4754 #[ snafu( display( "The Content Folder (Path: {path}) does not exist" ) ) ]
4855 NotFound { path : String } ,
4956 #[ snafu( display( "Database error" ) ) ]
@@ -52,6 +59,8 @@ pub enum ContentFolderError {
5259 Logger { source : LoggerError } ,
5360 #[ snafu( display( "Category operation failed" ) ) ]
5461 Category { source : CategoryError } ,
62+ #[ snafu( display( "Failed to create the folder on disk" ) ) ]
63+ IO { source : std:: io:: Error } ,
5564}
5665
5766#[ derive( Clone , Debug ) ]
@@ -90,14 +99,19 @@ impl ContentFolderOperator {
9099 ///
91100 /// Should not fail, unless SQLite was corrupted for some reason.
92101 pub async fn find_by_path ( & self , path : String ) -> Result < Model , ContentFolderError > {
102+ let path = NormalizedPathAbsolute :: from_str ( & path)
103+ . map_err ( |_e| ContentFolderError :: PathInvalid ) ?;
104+
93105 let content_folder = Entity :: find_by_path ( path. clone ( ) )
94106 . one ( & self . state . database )
95107 . await
96108 . context ( DBSnafu ) ?;
97109
98110 match content_folder {
99111 Some ( category) => Ok ( category) ,
100- None => Err ( ContentFolderError :: NotFound { path } ) ,
112+ None => Err ( ContentFolderError :: NotFound {
113+ path : path. to_string ( ) ,
114+ } ) ,
101115 }
102116 }
103117
@@ -122,35 +136,48 @@ impl ContentFolderOperator {
122136 ///
123137 /// Fails if:
124138 ///
125- /// - name or path is already taken (they should be unique in one folder)
139+ /// - name is already taken (they should be unique in one folder)
126140 /// - path parent directory does not exist (to avoid completely wrong paths)
127141 pub async fn create ( & self , f : & ContentFolderForm ) -> Result < Model , ContentFolderError > {
128- // Check duplicates in same folder
129- let list = if let Some ( parent_id) = f. parent_id {
130- self . list_child_folders ( parent_id) . await ?
131- } else {
132- let category = CategoryOperator :: new ( self . state . clone ( ) , None ) ;
133- category
134- . list_folders ( f. category_id )
135- . await
136- . context ( CategorySnafu ) ?
137- } ;
142+ let name = NormalizedPathComponent :: from_str ( & f. name )
143+ . map_err ( |_e| ContentFolderError :: NameInvalid ) ?;
138144
139- if list. iter ( ) . any ( |x| x. name == f. name ) {
140- return Err ( ContentFolderError :: NameTaken {
141- name : f. name . clone ( ) ,
142- } ) ;
143- }
145+ let category = self
146+ . db ( )
147+ . category ( )
148+ . find_by_id ( f. category_id )
149+ . await
150+ . context ( CategorySnafu ) ?;
144151
145- if list. iter ( ) . any ( |x| x. path == f. path ) {
146- return Err ( ContentFolderError :: PathTaken {
147- path : f. path . clone ( ) ,
148- } ) ;
152+ // Check duplicates in same category/folder
153+ {
154+ let siblings = if let Some ( parent_id) = f. parent_id {
155+ self . list_child_folders ( parent_id) . await ?
156+ } else {
157+ self . db ( )
158+ . category ( )
159+ . list_folders ( f. category_id )
160+ . await
161+ . context ( CategorySnafu ) ?
162+ } ;
163+ if siblings. iter ( ) . any ( |x| x. name == f. name ) {
164+ return Err ( ContentFolderError :: NameTaken {
165+ name : f. name . clone ( ) ,
166+ } ) ;
167+ }
149168 }
150169
170+ // This path is an absolute path, but relative to a category path
171+ let inner_path = if let Some ( parent_id) = f. parent_id {
172+ let parent = self . find_by_id ( parent_id) . await ?;
173+ NormalizedPathAbsolute :: from_str ( & format ! ( "{}/{}" , parent. path, name, ) ) . unwrap ( )
174+ } else {
175+ NormalizedPathAbsolute :: from_str ( & format ! ( "/{}" , name) ) . unwrap ( )
176+ } ;
177+
151178 let model = ActiveModel {
152- name : Set ( f . name . clone ( ) ) ,
153- path : Set ( f . path . clone ( ) ) ,
179+ name : Set ( name. to_string ( ) ) ,
180+ path : Set ( inner_path . clone ( ) ) ,
154181 category_id : Set ( f. category_id ) ,
155182 parent_id : Set ( f. parent_id ) ,
156183 ..Default :: default ( )
@@ -159,6 +186,13 @@ impl ContentFolderOperator {
159186 . await
160187 . context ( DBSnafu ) ?;
161188
189+ let real_path =
190+ NormalizedPathAbsolute :: from_str ( & format ! ( "{}{}" , category. path, inner_path) ) . unwrap ( ) ;
191+
192+ tokio:: fs:: create_dir_all ( & real_path)
193+ . await
194+ . context ( IOSnafu ) ?;
195+
162196 // Should not fail
163197 let model = model. try_into_model ( ) . unwrap ( ) ;
164198
0 commit comments