gimmich

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

main.go (7652B)


      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 	_, err := doReadJsonRequestOrSendErr(w, r, &LoginJSONRequestBody{})
    133 	if err != nil {
    134 		return
    135 	}
    136 	
    137 	// TODO comment back in auth check
    138 	// if lrb.Email == email && lrb.Password == "P@55word" {
    139 		
    140 		doWriteJsonResponse(w, LoginResponseDto{
    141 			AccessToken:          "foobar",
    142 			IsAdmin:              true,
    143 			IsOnboarded:          true,
    144 			Name:                 name,
    145 			ProfileImagePath:     "",
    146 			ShouldChangePassword: false,
    147 			UserEmail:            email,
    148 			UserId:               userid,
    149 		})
    150 	// } else {
    151 	// 	w.WriteHeader(401)
    152 	// 	doWriteResponse(w, func(w http.ResponseWriter) error {
    153 	// 		_, e := w.Write([]byte("unauthorized"))
    154 	// 		return e
    155 	// 	})
    156 	// }
    157 }
    158 
    159 // Logout implements [ServerInterface].
    160 func (m *MyServer) Logout(w http.ResponseWriter, r *http.Request) {
    161 	doWriteJsonResponse(w, LogoutResponseDto{
    162 		RedirectUri: "/auth/login",
    163 		Successful:  true,
    164 	})
    165 }
    166 
    167 // ValidateAccessToken implements [ServerInterface].
    168 func (m *MyServer) ValidateAccessToken(w http.ResponseWriter, r *http.Request) {
    169 	bytes, err := io.ReadAll(r.Body)
    170 	if err != nil {
    171 		slog.Error("ValidateAccessToken", slog.Any("err", err))
    172 		serverError(w)
    173 		return
    174 	}
    175 	for k, v := range r.Header {
    176 		slog.Info("ValidateAccessToken", slog.String("key", k), slog.Any("value", v))
    177 	}
    178 	slog.Info("ValidateAccessToken", slog.String("body", string(bytes)))
    179 	doWriteJsonResponse(w, ValidateAccessTokenResponseDto{
    180 		AuthStatus: true,
    181 	})
    182 }
    183 
    184 // PingServer implements [ServerInterface].
    185 func (m *MyServer) PingServer(w http.ResponseWriter, r *http.Request) {
    186 	doWriteJsonResponse(w, ServerPingResponse{
    187 		Res: "pong",
    188 	})
    189 }
    190 
    191 // GetServerVersion implements [ServerInterface].
    192 func (m *MyServer) GetServerVersion(w http.ResponseWriter, r *http.Request) {
    193 	doWriteJsonResponse(w, ServerVersionResponseDto{
    194 		Major: 3,
    195 		Minor: 2,
    196 		Patch: 2,
    197 	})
    198 }
    199 
    200 // GetServerFeatures implements [ServerInterface].
    201 func (m *MyServer) GetServerFeatures(w http.ResponseWriter, r *http.Request) {
    202 	doWriteJsonResponse(w, ServerFeaturesDto{
    203 		ConfigFile:          false,
    204 		DuplicateDetection:  false,
    205 		Email:               false,
    206 		FacialRecognition:   false,
    207 		ImportFaces:         false,
    208 		Map:                 false,
    209 		Oauth:               false,
    210 		OauthAutoLaunch:     false,
    211 		Ocr:                 false,
    212 		PasswordLogin:       true,
    213 		RealtimeTranscoding: false,
    214 		ReverseGeocoding:    false,
    215 		Search:              false,
    216 		Sidecar:             false,
    217 		SmartSearch:         false,
    218 		Trash:               false,
    219 	})
    220 }
    221 
    222 
    223 // GetServerConfig implements [ServerInterface].
    224 func (m *MyServer) GetServerConfig(w http.ResponseWriter, r *http.Request) {
    225 	hh := r.Header.Get("host")
    226 	if hh == "" {
    227 		hh = "localhost"
    228 	}
    229 	doWriteJsonResponse(w, ServerConfigDto{
    230 		ExternalDomain:   hh,
    231 		IsInitialized:    true,
    232 		IsOnboarded:      true,
    233 		LoginPageMessage: "Hello",
    234 		MaintenanceMode:  false,
    235 		MapDarkStyleUrl:  "",
    236 		MapLightStyleUrl: "",
    237 		MinFaces:         0,
    238 		OauthButtonText:  "",
    239 		PublicUsers:      false,
    240 		TrashDays:        0,
    241 		UserDeleteDelay:  0,
    242 	})
    243 }
    244 
    245 // GetMyUser implements [ServerInterface].
    246 func (m *MyServer) GetMyUser(w http.ResponseWriter, r *http.Request) {
    247 	quotasizebytes := 10_000_000_000
    248 	quotausagebytes := 1_000_000_000
    249 	created := must(func() (time.Time, error) { return time.Parse(time.RFC3339, "2026-09-28T12:00:00Z")} )
    250 	doWriteJsonResponse(w, UserAdminResponseDto{
    251 		AvatarColor:          "blue",
    252 		CreatedAt:            created,
    253 		DeletedAt:            nil,
    254 		Email:                email,
    255 		Id:                   userid,
    256 		IsAdmin:              false,
    257 		License:              nil,
    258 		Name:                 name,
    259 		OauthId:              "",
    260 		ProfileChangedAt:     time.Time{},
    261 		ProfileImagePath:     "",
    262 		QuotaSizeInBytes:     &quotasizebytes,
    263 		QuotaUsageInBytes:    &quotausagebytes,
    264 		ShouldChangePassword: false,
    265 		Status:               "active",
    266 		StorageLabel:         nil,
    267 		UpdatedAt:            created,
    268 	})
    269 }
    270 
    271 // GetMyPreferences implements [ServerInterface].
    272 func (m *MyServer) GetMyPreferences(w http.ResponseWriter, r *http.Request) {
    273 	doWriteJsonResponse(w, UserPreferencesResponseDto{
    274 		Albums:             AlbumsResponse{},
    275 		Cast:               CastResponse{},
    276 		Download:           DownloadResponse{},
    277 		EmailNotifications: EmailNotificationsResponse{},
    278 		Folders:            FoldersResponse{},
    279 		Memories:           MemoriesResponse{},
    280 		People:             PeopleResponse{},
    281 		Purchase:           PurchaseResponse{},
    282 		Ratings:            RatingsResponse{},
    283 		RecentlyAdded:      RecentlyAddedResponse{},
    284 		SharedLinks:        SharedLinksResponse{},
    285 		Tags:               TagsResponse{},
    286 	})
    287 }
    288 
    289 // DeleteSyncAck implements [ServerInterface].
    290 func (m *MyServer) DeleteSyncAck(w http.ResponseWriter, r *http.Request) {
    291 	w.WriteHeader(204)
    292 }
    293