gimmich

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

main.go (7657B)


      1 package main
      2 
      3 import (
      4 	"encoding/json"
      5 	"io"
      6 	"log"
      7 	"log/slog"
      8 	"net/http"
      9 	"strconv"
     10 	"time"
     11 
     12 	"database/sql"
     13 	"os"
     14 
     15 	"github.com/google/uuid"
     16 	_ "github.com/mattn/go-sqlite3"
     17 )
     18 
     19 //go:generate curl -SsL -o immich-openapi-specs.json https://github.com/immich-app/immich/raw/refs/tags/v3.0.3/open-api/immich-openapi-specs.json
     20 //go:generate go tool oapi-codegen -config cfg.yaml immich-openapi-specs.json
     21 
     22 const (
     23 	address = "127.0.0.1:8080"
     24 	api = "/api"
     25 	email = "martin@mfashby.net"
     26 	name = "Martin Ashby"
     27 )
     28 
     29 var (
     30 	userid = must(func() (uuid.UUID, error) { return uuid.Parse("8e251dd2-2d10-484b-bdc3-0128d855f204") })
     31 )
     32 
     33 func main() {
     34     RegisterCustomFunctions()
     35 	db, err := sql.Open("sqlite3_custom", "gimmich.db")
     36 	if err != nil {
     37 		slog.Error("error opening database", "err", err.Error())
     38 		os.Exit(1)
     39 	}
     40 	defer func() {
     41 		_ = db.Close()
     42 	}()
     43 	_, err = db.Exec("create table if not exists foo(col1 int)")
     44 	if err != nil {
     45 		slog.Error("error opening db", slog.Any("err", err))
     46 		os.Exit(1)
     47 	}
     48 	m := http.NewServeMux()
     49 	m.HandleFunc("/.well-known/immich", func(w http.ResponseWriter, r *http.Request) {
     50 		type Api struct {
     51 			Endpoint string `json:"endpoint"`
     52 		}
     53 		type WellKnown struct {
     54 			Api Api `json:"api"`
     55 		}
     56 		doWriteJsonResponse(w, WellKnown{
     57 			Api: Api{
     58 				Endpoint: api,
     59 			},	
     60 		})
     61 	})
     62 	log.Printf("Starting gimmich on %s", address)
     63 	log.Fatal(http.ListenAndServe(address, HandlerWithOptions(&MyServer{}, StdHTTPServerOptions{
     64 		BaseURL: api,
     65 		BaseRouter: m,
     66 	})))
     67 }
     68 
     69 func doReadJsonRequestOrSendErr[k any](w http.ResponseWriter, r *http.Request, p *k) (*k, error) {
     70 	err := json.NewDecoder(r.Body).Decode(p)
     71 	if err != nil {
     72 		badRequest(w)
     73 		return nil, err
     74 	}
     75 	return p, nil
     76 }
     77 
     78 func doWriteJsonResponse(w http.ResponseWriter, o any) {
     79 	doWriteResponse(w, func(w http.ResponseWriter) error {
     80 		slog.Info("writing json response...")
     81 		res, err := json.Marshal(o)
     82 		if err != nil {
     83 			serverError(w)
     84 			return err
     85 		}
     86 		h := w.Header()
     87 		h.Set("content-length", strconv.Itoa(len(res)))
     88 		h.Set("content-type", "application/json")
     89 		w.WriteHeader(200)
     90 		_, err = w.Write(res)
     91 		return err
     92 	})
     93 }
     94 
     95 func doWriteResponse(w http.ResponseWriter, f func(w http.ResponseWriter) error) {
     96 	err := f(w)
     97 	if err != nil {
     98 		slog.Error("error writing response", "err", err.Error())
     99 	}
    100 }
    101 
    102 func badRequest(w http.ResponseWriter) {
    103 	w.WriteHeader(400)
    104 	doWriteResponse(w, func(w http.ResponseWriter) error {
    105 		_, err := w.Write([]byte("bad request"))
    106 		return err
    107 	})
    108 }
    109 
    110 func serverError(w http.ResponseWriter) {
    111 	w.WriteHeader(500)
    112 	doWriteResponse(w, func(w http.ResponseWriter) error {
    113 		_, err := w.Write([]byte("server error"))
    114 		return err
    115 	})
    116 }
    117 
    118 func must[E any](f func() (E, error)) E {
    119 	e, err := f()
    120 	if err != nil {
    121 		log.Fatal(err)
    122 	}
    123 	return e
    124 }
    125 
    126 type MyServer struct{
    127 
    128 }
    129 
    130 // Login implements [ServerInterface].
    131 func (m *MyServer) Login(w http.ResponseWriter, r *http.Request) {
    132 	/*lrb*/_, err := doReadJsonRequestOrSendErr(w, r, &LoginJSONRequestBody{})
    133 	if err != nil {
    134 		return
    135 	}
    136 	// TODO comment back in auth check
    137 	// if lrb.Email == email && lrb.Password == "P@55word" {
    138 		
    139 		doWriteJsonResponse(w, LoginResponseDto{
    140 			AccessToken:          "foobar",
    141 			IsAdmin:              true,
    142 			IsOnboarded:          true,
    143 			Name:                 name,
    144 			ProfileImagePath:     "",
    145 			ShouldChangePassword: false,
    146 			UserEmail:            email,
    147 			UserId:               userid,
    148 		})
    149 	// } else {
    150 	// 	w.WriteHeader(401)
    151 	// 	doWriteResponse(w, func(w http.ResponseWriter) error {
    152 	// 		_, e := w.Write([]byte("unauthorized"))
    153 	// 		return e
    154 	// 	})
    155 	// }
    156 }
    157 
    158 // Logout implements [ServerInterface].
    159 func (m *MyServer) Logout(w http.ResponseWriter, r *http.Request) {
    160 	doWriteJsonResponse(w, LogoutResponseDto{
    161 		RedirectUri: "/auth/login",
    162 		Successful:  true,
    163 	})
    164 }
    165 
    166 // ValidateAccessToken implements [ServerInterface].
    167 func (m *MyServer) ValidateAccessToken(w http.ResponseWriter, r *http.Request) {
    168 	bytes, err := io.ReadAll(r.Body)
    169 	if err != nil {
    170 		slog.Error("ValidateAccessToken", slog.Any("err", err))
    171 		serverError(w)
    172 		return
    173 	}
    174 	for k, v := range r.Header {
    175 		slog.Info("ValidateAccessToken", slog.String("key", k), slog.Any("value", v))
    176 	}
    177 	slog.Info("ValidateAccessToken", slog.String("body", string(bytes)))
    178 	doWriteJsonResponse(w, ValidateAccessTokenResponseDto{
    179 		AuthStatus: true,
    180 	})
    181 }
    182 
    183 // PingServer implements [ServerInterface].
    184 func (m *MyServer) PingServer(w http.ResponseWriter, r *http.Request) {
    185 	doWriteJsonResponse(w, ServerPingResponse{
    186 		Res: "pong",
    187 	})
    188 }
    189 
    190 // GetServerVersion implements [ServerInterface].
    191 func (m *MyServer) GetServerVersion(w http.ResponseWriter, r *http.Request) {
    192 	doWriteJsonResponse(w, ServerVersionResponseDto{
    193 		Major: 3,
    194 		Minor: 2,
    195 		Patch: 2,
    196 	})
    197 }
    198 
    199 // GetServerFeatures implements [ServerInterface].
    200 func (m *MyServer) GetServerFeatures(w http.ResponseWriter, r *http.Request) {
    201 	doWriteJsonResponse(w, ServerFeaturesDto{
    202 		ConfigFile:          false,
    203 		DuplicateDetection:  false,
    204 		Email:               false,
    205 		FacialRecognition:   false,
    206 		ImportFaces:         false,
    207 		Map:                 false,
    208 		Oauth:               false,
    209 		OauthAutoLaunch:     false,
    210 		Ocr:                 false,
    211 		PasswordLogin:       true,
    212 		RealtimeTranscoding: false,
    213 		ReverseGeocoding:    false,
    214 		Search:              false,
    215 		Sidecar:             false,
    216 		SmartSearch:         false,
    217 		Trash:               false,
    218 	})
    219 }
    220 
    221 
    222 // GetServerConfig implements [ServerInterface].
    223 func (m *MyServer) GetServerConfig(w http.ResponseWriter, r *http.Request) {
    224 	hh := r.Header.Get("host")
    225 	if hh == "" {
    226 		hh = "localhost"
    227 	}
    228 	doWriteJsonResponse(w, ServerConfigDto{
    229 		ExternalDomain:   hh,
    230 		IsInitialized:    true,
    231 		IsOnboarded:      true,
    232 		LoginPageMessage: "Hello",
    233 		MaintenanceMode:  false,
    234 		MapDarkStyleUrl:  "",
    235 		MapLightStyleUrl: "",
    236 		MinFaces:         0,
    237 		OauthButtonText:  "",
    238 		PublicUsers:      false,
    239 		TrashDays:        0,
    240 		UserDeleteDelay:  0,
    241 	})
    242 }
    243 
    244 // GetMyUser implements [ServerInterface].
    245 func (m *MyServer) GetMyUser(w http.ResponseWriter, r *http.Request) {
    246 	quotasizebytes := 10_000_000_000
    247 	quotausagebytes := 1_000_000_000
    248 	created := must(func() (time.Time, error) { return time.Parse(time.RFC3339, "2026-09-28T12:00:00Z")} )
    249 	doWriteJsonResponse(w, UserAdminResponseDto{
    250 		AvatarColor:          "blue",
    251 		CreatedAt:            created,
    252 		DeletedAt:            nil,
    253 		Email:                email,
    254 		Id:                   userid,
    255 		IsAdmin:              false,
    256 		License:              nil,
    257 		Name:                 name,
    258 		OauthId:              "",
    259 		ProfileChangedAt:     time.Time{},
    260 		ProfileImagePath:     "",
    261 		QuotaSizeInBytes:     &quotasizebytes,
    262 		QuotaUsageInBytes:    &quotausagebytes,
    263 		ShouldChangePassword: false,
    264 		Status:               "active",
    265 		StorageLabel:         nil,
    266 		UpdatedAt:            created,
    267 	})
    268 }
    269 
    270 // GetMyPreferences implements [ServerInterface].
    271 func (m *MyServer) GetMyPreferences(w http.ResponseWriter, r *http.Request) {
    272 	doWriteJsonResponse(w, UserPreferencesResponseDto{
    273 		Albums:             AlbumsResponse{},
    274 		Cast:               CastResponse{},
    275 		Download:           DownloadResponse{},
    276 		EmailNotifications: EmailNotificationsResponse{},
    277 		Folders:            FoldersResponse{},
    278 		Memories:           MemoriesResponse{},
    279 		People:             PeopleResponse{},
    280 		Purchase:           PurchaseResponse{},
    281 		Ratings:            RatingsResponse{},
    282 		RecentlyAdded:      RecentlyAddedResponse{},
    283 		SharedLinks:        SharedLinksResponse{},
    284 		Tags:               TagsResponse{},
    285 	})
    286 }
    287 
    288 // DeleteSyncAck implements [ServerInterface].
    289 func (m *MyServer) DeleteSyncAck(w http.ResponseWriter, r *http.Request) {
    290 	w.WriteHeader(204)
    291 }
    292